*时间复杂度
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
冒泡排序 | O( n ^ 2) | O( n ^ 2) | O(1) | 是 |
选择排序 | O( n ^ 2) | O( n ^ 2) | O(1) | 不是 |
插入排序 | O( n ^ 2) | O( n ^ 2) | O(1) | 是 |
归并排序 | O( n log n) | O( n log n) | O(n) | 是 |
快速排序 | O( n log n) | O( n ^ 2) | O(log n) | 不是 |
堆排序 | O( n log n) | O( n log n) | O(1) | 不是 |
1. 冒泡排序
两两对比,较大的数往后面沉。
public static void bubbleSort(int[] arr) { for(int i = arr.length - 1; i > 0; i--) { for(int j = 0; j < i; j++) { if(arr[j] > arr[j + 1]) { //两两对比,大的数往后面沉 swap(arr, j, j + 1); } } } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
2. 选择排序
最前面的数分别与后面所有数对比,找出最小的,交换。
public static void selectSort(int[] arr) { for(int i = 0; i < arr.length; i++) { int min = i; for(int j = i + 1; j < arr.length; j++) { //从当前位置往后面找,选择一个最小的数 if(arr[j] < arr[min]) { min = j; } } swap(arr, i, min); } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
3. 插入排序
类似于打扑克牌,已经在手上的牌有序,新抽的牌插入到有序的位置。
第二个数与第一个数比较,排序。第三个数与前两个数比较,排序。
public static void insertSort(int[] arr) { for(int i = 1; i < arr.length; i++) { for(int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { swap(arr, j, j+ 1); } } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
4. 希尔排序
是简单插入排序的优化版,与插入排序的不同之处在于,会优先比较距离较远的元素,希尔排序又叫缩小增量排序。
public static void shellSort(int[] arr) { int temp = 1; while(temp < arr.length / 3) temp = temp * 3 + 1; while(temp >= 1) { for(int i = temp; i < arr.length; i++) { if(arr[i] < arr[i - temp]) { swap(arr, i, i - temp); } } temp = temp / 3; } SelectSort s = new SelectSort(); s.selectSort(arr); } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
5. 归并排序
分治的思想,不断将数组二分,直到不可再分时(即只剩一个元素时)便有序了,然后在将两部分合并的时候,利用外排将两部分合成有序序列
public static void mergeSort(int[] arr) { if(arr == null || arr.length < 2) return; mergeSort(arr, 0, arr.length - 1); } public static void mergeSort(int[] arr, int left, int right) { if(left == right) return; int mid = left + ((right - left) >> 1); //右移一位时除于2,右移 n 位相当于除于 2 的 n 次方 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[] help = new int[right - left + 1]; //辅助数组,空间复杂度O(n) int index = 0; int p1 = left, p2 = mid + 1; //通过外排的方式,按照顺序放入辅助数组中 while(p1 <= mid && p2 <= right) { help[index++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while(p1 <= mid) { help[index++] = arr[p1++]; } while(p2 <= right) { help[index++] = arr[p2++]; } //将辅助数组中的元素复制回原素组 for(int i = 0; i < help.length; i++) { arr[left + i] = help[i]; } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
6. 快速排序
经典快排:选择一个元素作为切分元素,左右两个指针分别向中间遍历,当左遇到大于切分元素,而右小于切分元素时,两者交换,继续遍历,直到左边都小于切分元素,右边都大于切分元素,一趟结束。
性能分析:
- 快排是原地排序,不需要辅助数组,但是递归调用需要辅助栈,快排最好的情况是每次切分元素都正好将数组对半分,这样递归调用的次数是最少的。
- 实现方法:1. 随机快排,每趟快排之前随机选取一个元素与原定切分元素交换。 2. 三数取中,最好的情况是每次都能取到数组的中位数作为切分元素,但是计算中位数显然不可取,折中的方法是:每趟快排选取3个元素,取中间的值作为切分元素
- 三向切分优化:大于有大量重复元素的数组,可以将数组划分为三部分,分别对应小于、等于、大于切分元素。对于大量重复元素的随机数组可以在线性时间内完成排序。
public static void quickSort(int[] arr) { if(arr == null || arr.length < 2) return; quickSort(arr, 0, arr.length - 1); } public static void quickSort(int[] arr, int left, int right) { if(left < right) { swap(arr, right, left + (int)(Math.random()*(right - left + 1))); //随机快排实现:随机生成left-right中的下标,然后与right位置的值交换 int[] result = partition(arr, left, right); //三向切分优化快排,返回等于划分right位置的值的前后下标 quickSort(arr, left, result[0] - 1); quickSort(arr, result[1] + 1, right); } } public static int[] partition(int[] arr, int left, int right) { int less = left - 1; int more = right; int index = left; while(index < more) { if(arr[index] < arr[right]) { //小于基准值,与小于区域的后一个值交换 swap(arr, index++, ++less); } else if(arr[index] > arr[right]) { //大于基准值,与大于区域的前一个值交换 swap(arr, index, --more); } else { //等于基准值时,继续遍历下一个数 index++; } } swap(arr, more, right); return new int[]{less + 1, more}; } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
7.堆排序
堆的底层结构使用数组实现,每个结点的父亲结点为 ( i - 1) / 2 ,孩子结点为 2 * i + 1 和 2 * i + 2。
排序过程:先将数组构建成大顶堆,然后将堆顶元素与最后一个元素交换(最后一个元素不一定最小,但一定会比堆顶元素小),交换之后除掉最后一个元素(即原堆顶元素)重新调整为大顶堆。
public static void heapSort(int[] arr) { if(arr == null || arr.length < 2) return; for(int i = 0; i < arr.length; i++) { heapInsert(arr, i); } int size = arr.length - 1 ; swap(arr, 0, size); while(size > 0) { heapify(arr, 0, size--); swap(arr, 0, size); } } //构建大顶堆 public static void heapInsert(int[] arr, int index) { //不断与父亲节点比较,交换 while(arr[index] > arr[(index - 1) / 2]) { swap(arr, index, (index - 1) / 2); index = (index - 1) / 2; } } //当将堆顶与最后一个数交换后,重建构建大顶堆 public static void heapify(int[] arr, int index, int size) { int left = index * 2 + 1; while(left < size) { int largeSet = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; //选择孩子结点中较大的一个 if(arr[index] > arr[largeSet]) { break; } swap(arr, index, largeSet); index = largeSet; left = index * 2 + 1; } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }