快速、归并、堆、希尔、ArrayList排序

发布于:2025-09-06 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、快速排序

描述:快速排序(Quick Sort)是一种高效的分治排序算法,其工作原理如下:

基本思想

  • 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小
  • 分别对这两部分记录继续进行排序,以达到整个序列有序
  • 采用分治法策略,递归地对子数组进行排序

工作原理

  1. 选择基准元素:从数组中选择一个元素作为基准(pivot),通常选择最后一个元素
  2. 分区操作:重新排列数组,使得比基准小的元素放在基准左边,比基准大的元素放在基准右边
  3. 递归排序:对基准左右两个子数组递归地进行快速排序
  4. 基准定位:每次分区操作后,基准元素会放到其最终的正确位置上
  5. 终止条件:当子数组长度小于等于1时,递归终止
    public static void main(String[] args) {
        int [] a = {5,3,2,4,1};
        quickSort(a, 0, a.length - 1);
    	System.out.println(Arrays.toString(a));
    }
        
    // 快速排序
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {                           // 递归终止条件:子数组长度大于1
            int pi = partition(arr, low, high);     // 获取分区点
            quickSort(arr, low, pi - 1);            // 递归排序左半部分
            quickSort(arr, pi + 1, high);           // 递归排序右半部分
        }
    }
    
    public static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];                      // 选择最后一个元素作为基准
        int i = (low - 1);                          // 小于基准的元素的索引
        
        for (int j = low; j < high; j++) {          // 遍历从low到high-1的元素
            if (arr[j] <= pivot) {                  // 如果当前元素小于等于基准
                i++;                                // 扩展小于基准的区域
                int temp = arr[i];                  // 交换元素
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        
        int temp = arr[i + 1];                      // 将基准放到正确位置
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;                               // 返回基准的索引
    }

特点

  • 时间复杂度
    • 最好情况:O(n log n) - 每次分区都能平均分割
    • 平均情况:O(n log n)
    • 最坏情况:O(n²) - 每次选到最大或最小元素作为基准
  • 空间复杂度:O(log n) - 递归调用栈空间
  • 稳定性:不稳定排序算法
  • 适用场景:大规模数据集,对性能要求较高的场景

二、归并排序

描述:归并排序(Merge Sort)是一种基于分治思想的排序算法,其工作原理如下:

基本思想

  • 将数组不断二分,直到每个子数组只有一个元素
  • 然后将这些子数组两两合并,合并过程中保持有序
  • 最终得到一个完全有序的数组

工作原理

  1. 分解:将数组从中间分成两个子数组,递归地对两个子数组进行分解
  2. 解决:当子数组长度为1时,认为其已排序
  3. 合并:将两个已排序的子数组合并成一个有序数组
  4. 递归:重复上述过程,直到整个数组有序
    public static void main(String[] args) {
        int [] a = {5,3,2,4,1};
        mergeSort(a, 0, a.length - 1);
        System.out.println(Arrays.toString(a));
    }
	// 归并排序
    public static void mergeSort(int[] arr, int left, int right) {
        // 当 left < right 时继续分解数组
        if (left < right) {
            // 计算中间位置,将数组分为两部分
            int mid = (left + right) / 2;
            // 递归排序左半部分
            mergeSort(arr, left, mid);
            // 递归排序右半部分
            mergeSort(arr, mid + 1, right);
            // 合并已排序的两部分
            merge(arr, left, mid, right);
        }
    }

    // 合并两个已排序的子数组
    public static void merge(int[] arr, int left, int mid, int right) {
        // 计算左右子数组的长度
        int n1 = mid - left + 1;  // 左子数组长度
        int n2 = right - mid;     // 右子数组长度

        // 创建临时数组存储左右子数组
        int[] leftArr = new int[n1];
        int[] rightArr = new int[n2];

        // 将原数组元素复制到临时数组中
        for (int i = 0; i < n1; i++)
            leftArr[i] = arr[left + i];  // 复制左子数组
        for (int j = 0; j < n2; j++)
            rightArr[j] = arr[mid + 1 + j];  // 复制右子数组

        // 初始化左右子数组的索引以及合并后数组的索引
        int i = 0, j = 0, k = left;

        // 比较两个子数组的元素,将较小的元素放入原数组
        while (i < n1 && j < n2) {
            if (leftArr[i] <= rightArr[j]) {
                arr[k] = leftArr[i];  // 左子数组元素较小,放入原数组
                i++;  // 左子数组索引后移
            } else {
                arr[k] = rightArr[j]; // 右子数组元素较小,放入原数组
                j++;  // 右子数组索引后移
            }
            k++;  // 原数组索引后移
        }

        // 将左子数组剩余元素复制到原数组
        while (i < n1) {
            arr[k] = leftArr[i];
            i++;
            k++;
        }

        // 将右子数组剩余元素复制到原数组
        while (j < n2) {
            arr[k] = rightArr[j];
            j++;
            k++;
        }
    }

特点

  • 时间复杂度
    • 最好情况:O(n log n)
    • 平均情况:O(n log n)
    • 最坏情况:O(n log n)
  • 空间复杂度:O(n) - 需要额外的数组空间用于合并
  • 稳定性:稳定排序算法
  • 适用场景:大规模数据排序,对时间复杂度有稳定要求的场景

三、堆排序

描述:堆排序(Heap Sort)是一种基于二叉堆数据结构的比较排序算法。它利用堆这种数据结构所设计的一种排序算法,是选择排序的优化版本。

基本思想

  • 将待排序数组构建成一个最大堆(或最小堆)
  • 此时数组的第一个元素就是最大(或最小)值
  • 将堆顶元素与末尾元素交换,然后重新调整剩余元素为堆
  • 重复此过程,直到所有元素排序完成

工作原理

  1. 构建初始堆:将无序数组调整为最大堆结构
  2. 交换堆顶与末尾元素:将堆顶最大值与末尾元素交换,使最大值"沉底"
  3. 重新调整堆:对剩余未排序元素重新构建最大堆
  4. 重复过程:重复步骤2-3,直到所有元素排序完成
    // 堆排序
    public static void heapSort(int[] arr) {
        int n = arr.length;
        
        // 构建最大堆:从最后一个非叶子节点开始,自下而上调整
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }
        
        // 逐个从堆顶取出元素
        for (int i = n - 1; i > 0; i--) {
            // 将当前最大元素(堆顶)移到数组末尾
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            
            // 重新调整剩余元素为最大堆
            heapify(arr, i, 0);
        }
    }
    
    // 调整堆结构,使其满足最大堆性质
    public static void heapify(int[] arr, int n, int i) {
        int largest = i;        // 假设当前节点为最大值
        int left = 2 * i + 1;   // 左子节点索引
        int right = 2 * i + 2;  // 右子节点索引
        
        // 如果左子节点存在且大于当前最大值
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }
        
        // 如果右子节点存在且大于当前最大值
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }
        
        // 如果最大值不是当前节点,则交换并继续调整
        if (largest != i) {
            int swap = arr[i];
            arr[i] = arr[largest];
            arr[largest] = swap;
            
            // 递归调整受影响的子树
            heapify(arr, n, largest);
        }
    }

特点

  • 时间复杂度
    • 最好情况:O(n log n)
    • 平均情况:O(n log n)
    • 最坏情况:O(n log n)
  • 空间复杂度:O(1) - 原地排序算法
  • 稳定性:不稳定排序算法(相同元素的相对位置可能改变)
  • 适用场景:对空间复杂度有要求,且数据量较大的排序场景

四、希尔排序

描述:希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为递减增量排序算法。它通过将原始数组按照一定的间隔(gap)分成若干子序列,对这些子序列分别进行插入排序,然后逐步缩小间隔,最终当间隔为1时,进行一次完整的插入排序。

基本思想

  • 设置一个间隔序列,通常初始间隔为数组长度的一半
  • 按照间隔将数组分组,对每组进行插入排序
  • 逐步缩小间隔,重复上述过程
  • 当间隔为1时,进行最后一次插入排序,完成排序

工作原理

  1. 选择间隔:选择一个间隔序列,通常从数组长度的一半开始
  2. 分组排序:按照当前间隔将数组分组,对每组执行插入排序
  3. 缩小间隔:将间隔减小(通常除以2),重复分组排序过程
  4. 最终排序:当间隔为1时,进行一次完整的插入排序
    public static void main(String[] args) {
        int [] a = {5,3,2,4,1};
        shellSort(a);
    	System.out.println(Arrays.toString(a));
    }
	// 希尔排序
    public static void shellSort(int[] arr) {
        int n = arr.length;
        
        // 初始间隔为数组长度的一半,逐步缩小间隔
        for (int gap = n / 2; gap > 0; gap /= 2) {
            // 对每个间隔内的元素进行插入排序
            for (int i = gap; i < n; i++) {
                int key = arr[i];  // 当前要插入的元素
                int j = i;
                
                // 在间隔为gap的子序列中进行插入排序
                while (j >= gap && arr[j - gap] > key) {
                    arr[j] = arr[j - gap];  // 将大于key的元素向后移动gap个位置
                    j -= gap;
                }
                
                arr[j] = key;  // 插入当前元素到正确位置
            }
        }
    }

特点

  • 时间复杂度
    • 最好情况:O(n log n) - 数组已经基本有序
    • 平均情况:O(n^1.3) - 根据间隔序列的选择而变化
    • 最坏情况:O(n²) - 使用不当的间隔序列
  • 空间复杂度:O(1) - 原地排序算法
  • 稳定性:不稳定排序算法(相同元素的相对位置可能改变)
  • 适用场景:中等规模数据排序,比插入排序更高效,实现相对简单

五、 ArrayList 排序

ArrayList本身是一种动态数组数据结构,它本身并不直接提供排序功能。在Java中,对ArrayList进行排序通常使用以下方式:

ArrayList排序方式
  1. 使用Collections.sort()方法

    • 对于ArrayList,通常使用Collections.sort()方法进行排序
    • 该方法内部使用的是优化的归并排序(Timsort算法)
  2. 使用Arrays.sort()方法

    • 如果将ArrayList转换为数组,可以使用Arrays.sort()方法
    • 对于基本类型数组,使用双轴快速排序(Dual-Pivot Quicksort)
    • 对于对象数组,使用Timsort算法
ArrayList排序的具体实现
// 示例:ArrayList排序用法
ArrayList<Integer> list = new ArrayList<>();
list.add(5);
list.add(3);
list.add(2);
list.add(8);
list.add(1);

list.sort(Comparator.naturalOrder()); //升序
list.sort(Collections.reverseOrder()); //倒叙
// 使用Collections.sort()排序
Collections.sort(list); // 内部使用Timsort算法 //升序
Collections.sort(list, Collections.reverseOrder()); //倒叙
Timsort算法特点
  • 混合排序算法:结合了归并排序和插入排序的优点
  • 稳定排序:相等元素的相对位置不会改变
  • 自适应性:对于部分有序的数据效率很高
  • 时间复杂度
    • 最好情况:O(n) - 数据已经有序
    • 平均情况:O(n log n)
    • 最坏情况:O(n log n)
  • 空间复杂度:O(n)
Collections.sort()方法的实现逻辑如下:
  1. 将ArrayList转换为数组
  2. 调用Arrays.sort()方法对数组进行排序
  3. 将排序后的数组元素重新放回ArrayList
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

对于对象类型(如Integer、String等),Arrays.sort()使用Timsort算法,这是一种高度优化的归并排序变体。因此,可以说ArrayList在Java中排序时主要使用的是Timsort算法,它是一种稳定、高效的排序算法,实际应用中常见的部分有序数据。


网站公告

今日签到

点亮在社区的每一天
去签到