性质
稳定性
稳定性指相等的元素经过排序之后相对顺序是否发生了改变。
时间复杂度
对于基于比较的排序算法的时间复杂度,较好的性能是\(O(nlogn)\) ,坏的性能是\(O(n^2)\) 。一个排序算法的理想性能是\(O(n)\) ,但是平均而言不可能达到。
选择排序
Selection
sort 是较为简单的一种排序算法,每次找到第\(i\) 小的元素,然后将这个元素与数组的第\(i\) 个位置上的元素交换。换句话说:每次找到未完成排序的数组中最小的值,然后将其与边界(已排序和未排序元素的边界)进行交换。
由于swap操作的存在,因此该算法是一种不稳定的排序算法
选择排序的最优、平均、最坏时间复杂度都是\(O(n^2)\) 。两层for
Java:
public static void selection (int [] arr) { int n = arr.length; for (int i = 1 ; i < n; i++) { int minIndex = i; for (int j = i + 1 ; j < n; j++) { if (arr[j] < arr[minIndex]){ minIndex = j; } } swap(arr, i, minIndex); } }
冒泡排序
Bubble
Sort 由于在算法的执行过程中,较小的元素像气泡一样慢慢浮到数组的顶端,故称为冒泡排序。
工作原理:每次检查相邻的两个元素,如果满足排序条件,则交换。经过\(i\) 次扫描,数组末尾的\(i\) 项必然是第\(i\) 大的元素,因此冒泡排序最多需要扫描\(n - 1\) 遍数组就能完成排序。
稳定的排序算法
序列完全有序时,冒泡排序只需遍历一次数组,不用执行任何交换操作,时间复杂度位$O
(n) $
最坏情况下,需要进行$ $次交换操作。
平均时间复杂度为\(O(n)\)
1 2 3 4 5 6 7 8 9 10 11 12 public static void bubbleSort (int [] arr) { boolean flag = true ; while (flag) { flag = false ; for (int i = 1 ; i < arr.length -1 ; i++) { if (arr[i] > arr[i + 1 ]) { flag = true ; swap(arr, i, i + 1 ); } } } }
插入排序
Insertion
Sort :将待排序元素划分为已排序和未排序两部分,每次从未排序元素中选择一个插入到已排序的的元素中的正确位置。
案例:打扑克牌时,从桌上抓一张牌,按牌的大小插入正确的位置。
稳定的排序算法
最优的时间复杂度是\(O(n)\)
插入排序的最坏、平均时间复杂度为\(O(n^2)\)
1 2 3 4 5 6 7 8 9 public static void insertionSort (int [] arr) { for (int i = 1 ; i < arr.length; i++) { for (int j = i - 1 ; j >= 0 ; j--) { if (arr[j] > arr[j + 1 ]) { swap(arr, j, j + 1 ); } } } }
计数排序
Counting Sort :是一种线性时间的排序算法。
工作原理:使用一个额外的数组\(C\) ,其中第\(i\) 个元素是待排序数组\(A\) 中值等于\(i\) 的元素的个数,然后通过数组\(C\) 来将\(A\) 中的元素排到正确的位置。
分为三个步骤:
计算每个数出现的次数
求除每个数出现次数的前缀和
利用出现次数的前缀和,从右至左计算每个数的排名
稳定的排序算法
时间复杂度为\(O(n + w)\) ,其中\(w\) 代表待排序数据的值域大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static int [] countingSort(int [] A){ int k = 100 ; int [] C = new int [k]; int [] B = new int [A.length]; for (int i = 0 ; i < A.length; i++) { C[A[i]]++; } for (int i = 1 ; i < k; i++) { C[i] = C[i] + C[i -1 ]; } for (int j = A.length -1 ; j >= 0 ; j--) { int a = A[j]; B[C[a] - 1 ] = a; C[a]--; } return B; }
归并排序
merge sort :是一种采用了分治思想的排序算法。
主要分为三个步骤:
将数组划分为两部分
递归地对两个子序列进行归并排序
合并两个子序列
性质:
是一种稳定的排序算法
最优、最坏、平均时间复杂度均为\(O(nlogn)\)
归并排序的空间复杂度为\(O(n)\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static void mergeSort (int [] arr, int low, int high) { int mid = (low + high) / 2 ; if (low < high) { mergeSort(arr, low, mid); mergeSort(arr, mid + 1 , high); merge(arr, low, mid, high); } }private static void merge (int [] arr, int low, int mid, int high) { int [] temp = new int [high - low + 1 ]; int i = low; int j = mid + 1 ; int k = 0 ; for (; i <= mid && j <= high; k++) { if (arr[i] < arr[j]) temp[k] = arr[i++]; else temp[k] = arr[j++]; } while (i <= mid) temp[k++] = arr[i++]; while (j <= high) temp[k++] = arr[j++]; for (int l = 0 ; l < temp.length; l++) arr[low + l] = temp[l]; }
堆排序
Heap
Sort :利用数据结构中的堆设计的一种排序算法。
工作原理:对所有待排序元素建堆,
利用最大堆(最小堆)的特性,依次取出堆顶元素,就可以得到排好序的数组。
当前节点的下为\(i\) 时,它的父结点、左子节点、右子节点的获取方式如下:
1 2 3 4 //向下舍入 parent = floor((i - 1) /2); leftChild = 2* i + 1; rightChild = 2 * i + 2;
不稳定的排序算法
最优、平均、最坏时间复杂度均为\(O(nlogn)\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static void heapSort (int [] arr) { int len = arr.length; for (int i = len / 2 - 1 ; i >= 0 ; i--) { heapify(arr, i, len); } for (int j = arr.length -1 ; j > 0 ; j--) { swap(arr, 0 , j); heapify(arr, 0 , j); } } private static void heapify (int [] arr, int i, int length) { int temp = arr[i]; for (int k = i * 2 + 1 ; k < length; k = k * 2 + 1 ) { if (k + 1 < length && arr[k] < arr[k + 1 ]) { k++; } if (arr[k] > temp){ arr[i] = arr[k]; i = k; }else { break ; } } arr[i] = temp; }
快速排序
Quick
Sort :简称为快排,也成为分区交换排序。是一种广泛运用的排序算法。
基本原理:通过分治思想实现排序。
选取基准值,以基准值为界,将比它大的和比它小的分别放在两边。
对子序列进行递归快排,直至序列为空或者只有一个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static void quickSort (int [] arr, int start, int end) { if (start >= end) { return ; } int pivotIndex = partition(arr, start, end); quickSort(arr, start, pivotIndex - 1 ); quickSort(arr, pivotIndex + 1 , end); } private static int partition (int [] arr, int start, int end) { int pivot = arr[start]; int left = start; int right = end; while (left != right) { while (left < right && arr[right] > pivot) { right--; } while (left < right && arr[left] <= pivot) { left++; } if (left < right) { swap(arr, left, right); } } arr[start] = arr[left]; arr[left] = pivot; return left; }
希尔排序
Shell
Sort :最小增量排序,是插入排序的一种改进版本。
工作原理:对不相邻的记录进行比较和移动。
将待排序数组分成若干个子序列(每个子序列的元素在原始数组中间距相同)
对这些子序列进行插入排序
减少每个子序列中元素之间的间距,重复上述过程直至间距减少为1。
不稳定的排序算法
最优时间复杂度为\(O(n)\) 。
平均时间复杂度和最坏时间复杂度与间距序列的选取有关,已知最好的最坏时间复杂度为\(O(nlog^2n)\) 。
空间复杂度为\(O(n)\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void shellSort (int [] arr, int length) { int h = 1 ; while (h < length / 3 ) { h = 3 * h + 1 ; } while (h >= 1 ) { for (int i = h; i < length; i++) { for (int j = i; j >= h && arr[j] < arr[j - h]; j -= h) { swap(arr, j, j - h); } } h = h / 3 ; } }
基数排序
Radix Sort :是一种非比较型整数排序算法。
工作原理:将待排序的元素拆分为\(k\) 个关键字,然后先对第\(k\) 个关键字进行稳定排序,再对第\(k -
1\) 个关键字进行稳定排序,……最后对第一个关键字进行稳定排序。
大白话:先根据个位排序,再根据十位排序,再按照百位排序……
基数排序中需要借助一种稳定算法 完成内层对关键字的排序。
基数排序根据两种不同的排序顺序,可以分为:
LSD(Least significant digital):从低位开始
MSD(Most significant digital):从高位开始
性质:
基数排序是一种稳定的排序算法。
如果每个关键字的值域不大,可以使用计数排序 作为内层排序,此时复杂度为\(O(kn +
\sum\limits_{i=1}^{k}w_i)\) ,其中\(w_i\) 为第\(i\) 个关键字值域的大小,如果关键字值域很大,就可以使用基于比较的\(O(nklogn)\) 的排序。
空间复杂度\(O(k + n)\)
桶排序
Bucket
Sort :是排序算法的一种,适用于待排序数据值域较大但分布较为均匀的情况。
步骤:
设置一个定量的数组当作空桶
遍历序列,将元素一个个的放入对应的桶中
对每个不是空的桶中元素取出来,按序放入序列。
性质:
如果使用稳定的内层排序,并且将元素插入桶中时不改变元素的相对顺序,那么桶排序是一种稳定的排序算法。
桶排序的平均时间复杂度为\(O(n + n^2/k +
k)\) ,(将值域平均分成n块 + 排序 + 重新合并元素),当$k n \(时为\) O(n)$。
最坏时间复杂度为\(O(n^2)\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 private static int indexFor (int a, int min, int step) { return (a - min) / step; } public static void bucketSort (int [] arr) { int max = arr[0 ], min = arr[0 ]; for (int a : arr) { if (max < a) max = a; if (min > a) min = a; } int bucketNum = max / 10 - min / 10 + 1 ; List buckList = new ArrayList <List<Integer>>(); for (int i = 1 ; i <= bucketNum; i++) { buckList.add(new ArrayList <Integer>()); } for (int i = 0 ; i < arr.length; i++) { int index = indexFor(arr[i], min, 10 ); ((ArrayList<Integer>) buckList.get(index)).add(arr[i]); } ArrayList<Integer> bucket = null ; int index = 0 ; for (int i = 0 ; i < bucketNum; i++) { bucket = (ArrayList<Integer>) buckList.get(i); insertSort(bucket); for (int k : bucket) { arr[index++] = k; } } } private static void insertSort (List<Integer> bucket) { for (int i = 1 ; i < bucket.size(); i++) { int temp = bucket.get(i); int j = i - 1 ; for (; j >= 0 && bucket.get(j) > temp; j--) { bucket.set(j + 1 , bucket.get(j)); } bucket.set(j + 1 , temp); } }
参考
OI WIKI
Wikipedia