常见的排序算法:冒泡、快排、归并、计数

冒泡排序:

时间复杂度:O(n^2)

冒泡排序就像水里的气泡一样,轻的气泡会一点一点地浮到水面。在这个算法中,我们将待排序的元素比喻成气泡,通过比较相邻元素的值,让较大的元素慢慢“浮”到数组的末端。

具体步骤如下:

  1. 比较相邻的两个元素,如果前一个比后一个大(假设我们要从小到大排序),就交换它们的位置。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后已经排序好的元素。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
public static void bubbleSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    // 外层循环控制排序轮数,每轮将最大的元素移动到正确的位置
    for (int i = 1; i < arr.length; i++) {
        boolean swap = false;// 设置一个标志,用于判断本轮是否发生了交换
        // 内层循环进行相邻元素的比较和交换
        for (int j = 0; j < arr.length - i; j++) {
            // 交换大小数
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
                swap = true;
            }
        }
        // 如果一轮比较下来没有发生交换,说明数组已经有序,可以退出循环
        if (!swap) {
            break;
        }
    }
}

 

快速排序:

时间复杂度:O(n log n) - O(n^2)

快速排序的思想是找一个基准值(pivot),将数组分为两部分,左边都比基准值小,右边都比基准值大。然后对这两部分再递归地进行同样的操作。

具体步骤如下:

  1. 选择一个基准值,通常是数组的中部或最后一个元素。
  2. 将数组分为两部分,小于基准值的元素放到左边,大于基准值的元素放到右边。
  3. 对左右两个子数组重复步骤1和2,直到每个子数组的元素数量不超过1。
public static void quickSort(int[] arr, int start, int end) {
    if (start >= end) {
        return;
    }
    // 使用三数取中法选择基准值,避免最坏情况的发生
    int mid = (start + end) >> 1;
    int pivot = Math.max(Math.min(arr[start], arr[mid]), Math.min(Math.max(arr[start], arr[mid]), arr[end]));

    int l = start - 1, r = end + 1;
    // 双指针遍历,找到小于和大于基准值的元素并进行交换
    do {
        while (arr[++l] < pivot) ;// L向右遍历,找到大于或等于基准值的元素
        while (arr[--r] > pivot) ;// R向左遍历,找到小于或等于基准值的元素
        if (l < r) {
            // L < R:交换大小数
            int temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
        }
    } while (l < r);// R ≤ L:说明arr已完整遍历,跳出循环
    // 对子数组进行递归排序
    quickSort(arr, start, l - 1);
    quickSort(arr, r + 1, end);
}

 

归并排序:

时间复杂度:O(n log n)

归并排序是采用分治法的一个非常典型的应用。它的思想是将数组分成若干个小数组,对每个小数组进行排序,然后将小数组合并成较大的数组,直到最后只有一个排序完成的大数组。

具体步骤如下:

  1. 把数组分成若干个小数组,直到每个小数组只有一个元素。
  2. 将相邻的小数组合并成较大的数组,合并时对数组进行排序。
  3. 重复步骤2,直到最后只有一个排序完成的大数组。
public static int[] mergeSort(int[] arr, int start, int end) {
    if (start >= end) {
        return new int[]{arr[start]};
    }
    // 计算左子数组的末尾位置
    int endL = (start + end) >> 1;
    // 对两个子数组进行递归排序
    int[] lArr = mergeSort(arr, start, endL);
    int[] rArr = mergeSort(arr, endL + 1, end);

    int k = 0, i = 0, j = 0;
    int[] newArr = new int[end - start + 1];
    // 当两个子数组都还有元素时,比较并放入较小的元素到新数组
    while (i < lArr.length && j < rArr.length) {
        newArr[k++] = lArr[i] < rArr[j] ? lArr[i++] : rArr[j++];
    }
    // 如果子数组还有剩余元素,将其复制到新数组
    System.arraycopy(lArr, i, newArr, k, lArr.length - i);
    k += lArr.length - i;
    System.arraycopy(rArr, j, newArr, k, rArr.length - j);
    return newArr;
}

 

计数排序:

时间复杂度:O(n + k)

计数排序是一种简单的排序算法,它的工作原理是数一数每个元素在数组中出现的次数。然后,根据这些计数,将元素按照顺序重新排列到另一个数组中。这种方法特别适用于整数排序,尤其是当整数的范围不是很大时。

public static void countingSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    // 找出数组中的最大值和最小值
    int min = arr[0], max = arr[0];
    for (int i : arr) {
        max = Math.max(max,i);
        min = Math.min(min,i);
    }
    // 计算每个元素的个数,放入计数数组
    int[] ints = new int[max - min + 1];
    for (int i : arr) {
        ints[i - min]++;
    }
    // 重建排序后的数组
    for (int i = 0, j = 0; i < ints.length; i++) {
        while (ints[i]-- > 0) {
            arr[j++] = min + i;
        }
    }
}

 

总结:

每种排序算法各有特点,冒泡排序简单但效率较低,快速排序效率高但需要额外的存储空间,归并排序则是效率和稳定性都比较好的排序算法。

posted @ 2024-04-20 01:00  Yfeil  阅读(15)  评论(0编辑  收藏  举报