常见的排序算法:冒泡、快排、归并
冒泡排序:
时间复杂度:O(n^2)
冒泡排序就像水里的气泡一样,轻的气泡会一点一点地浮到水面。在这个算法中,我们将待排序的元素比喻成气泡,通过比较相邻元素的值,让较大的元素慢慢“浮”到数组的末端。
具体步骤如下:
- 比较相邻的两个元素,如果前一个比后一个大(假设我们要从小到大排序),就交换它们的位置。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后已经排序好的元素。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
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,直到每个子数组的元素数量不超过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)
归并排序是采用分治法的一个非常典型的应用。它的思想是将数组分成若干个小数组,对每个小数组进行排序,然后将小数组合并成较大的数组,直到最后只有一个排序完成的大数组。
具体步骤如下:
- 把数组分成若干个小数组,直到每个小数组只有一个元素。
- 将相邻的小数组合并成较大的数组,合并时对数组进行排序。
- 重复步骤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++];
}
}
总结:
这三种排序算法各有特点,冒泡排序简单但效率较低,快速排序效率高但需要额外的存储空间,归并排序则是效率和稳定性都比较好的排序算法。