常见的排序算法:冒泡、快排、归并、计数
冒泡排序:
时间复杂度:O(n^2)
冒泡排序就像水里的气泡一样,轻的气泡会一点一点地浮到水面。在这个算法中,我们将待排序的元素比喻成气泡,通过比较相邻元素的值,让较大的元素慢慢“浮”到数组的末端。
具体步骤如下:
- 比较相邻的两个元素,如果前一个比后一个大(假设我们要从小到大排序),就交换它们的位置。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后已经排序好的元素。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
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,直到每个子数组的元素数量不超过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)
归并排序是采用分治法的一个非常典型的应用。它的思想是将数组分成若干个小数组,对每个小数组进行排序,然后将小数组合并成较大的数组,直到最后只有一个排序完成的大数组。
具体步骤如下:
- 把数组分成若干个小数组,直到每个小数组只有一个元素。
- 将相邻的小数组合并成较大的数组,合并时对数组进行排序。
- 重复步骤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; } } }
总结:
每种排序算法各有特点,冒泡排序简单但效率较低,快速排序效率高但需要额外的存储空间,归并排序则是效率和稳定性都比较好的排序算法。
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签