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

羽飞落 / 2024-04-20 / 原文

冒泡排序:

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

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

具体步骤如下:

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

 

快速排序:

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

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

具体步骤如下:

  1. 选择一个基准值,通常是数组的中部或最后一个元素。
  2. 将数组分为两部分,小于基准值的元素放到左边,大于基准值的元素放到右边。
  3. 对左右两个子数组重复步骤1和2,直到每个子数组的元素数量不超过1。
public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int start, int end) {
    while (start < end) {
        // 使用三数取中法选择基准值
        int mid = start + (end - start) / 2;
        int pivot = Math.max(Math.min(arr[start], arr[mid]), Math.min(Math.max(arr[start], arr[mid]), arr[end]));

        int i = start, j = end;
        while (i <= j) {
            while (arr[i] < pivot) i++;
            while (arr[j] > pivot) j--;
            if (i <= j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
                i++;
                j--;
            }
        }

        // 递归地对子数组进行快速排序
        quickSort(arr, start, j);
        start = i;
    }
}
旧版2
 public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int start, int end) {
    if (start >= end) return;
    int j, x = arr[end];//x = 基准值
    for (int i = j = start; i < end; i++) {
        if (arr[i] < x) {//大数略过,小数往后扔
            if (i != j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            j++;
        }
    }
    arr[end] = arr[j];
    arr[j] = x;//基准值归位
    quickSort(arr, start, j - 1);
    quickSort(arr, j + 1, end);
}
旧版1
public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int start, int end) {
    if (start >= end) return;

    int x, l, r;
    x = arr[start];//基准值
    l = start;
    r = end + 1;

    do {
        while (l < end) {//范围1到end
            if (arr[++l] > x) break;//L向右遍历,定位大于x的数
        }
        while (r > start) {//范围end到0
            if (arr[--r] < x) break;//R向左遍历,定位小于x的数
        }
        /*
        到这里有两个定律:
            1、L遇到大数或end才会停,R遇到小数或start才会停
            2、基于定律1,所以L左边所有数一定小于或等于x,R右边所有数一定大于或等于x
        需要考虑三种情况:
            1、L在左R在右:根据定律1,一定是LR都遇到大小数了,直接交换大小数。
            2、L和R重叠:只有当x是数组里最大值时才会出现,L和R一定都在end,直接把x和end(R)互换,x归位。
            3、R在左L在右:基于定律2,R当前指向数必定小于或等于x,R右侧必定大于x,直接x和R互换,x归位。
         */
        if (l < r) {
            //交换大小数
            int temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
        } else {
            //x归位
            arr[start] = arr[r];
            arr[r] = x;
            break;
        }
    } while (true);

    quickSort(arr, start, r - 1);
    quickSort(arr, r + 1, end);
}

 

归并排序:

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

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

具体步骤如下:

  1. 把数组分成若干个小数组,直到每个小数组只有一个元素。
  2. 将相邻的小数组合并成较大的数组,合并时对数组进行排序。
  3. 重复步骤2,直到最后只有一个排序完成的大数组。
public static void mergeSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    int mid = arr.length / 2;
    int[] left = new int[mid];
    int[] right = new int[arr.length - mid];

    System.arraycopy(arr, 0, left, 0, mid);
    System.arraycopy(arr, mid, right, 0, arr.length - mid);

    mergeSort(left);
    mergeSort(right);
    merge(arr, left, right);
}

private static void merge(int[] arr, int[] left, int[] right) {
    int i = 0, j = 0, k = 0;
    while (i < left.length && j < right.length) {
        if (left[i] <= right[j]) {
            arr[k++] = left[i++];
        } else {
            arr[k++] = right[j++];
        }
    }
    while (i < left.length) {
        arr[k++] = left[i++];
    }
    while (j < right.length) {
        arr[k++] = right[j++];
    }
}

 

总结:

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