稳定排序算法会将相等的元素值维持其相对次序。如果一个排序算法是稳定的,当有两个有相等的元素值 R 和 S,且在原本的列表中 R 出现在 S 之前,那么在排序过的列表中 R 也将会是在 S 之前。
1.冒泡排序
原理:
相邻元素比较。仅适用于对于含有较少元素的数列进行排序。
for(i:0~n-1)
flag=true;
for(j:0~n-i-1)
if()
交换
flag = false
if(flag)
break;
性能:
取决于输入
最坏:/2次比较、交换
最好:n-1次比较、0交换
平均:
稳定
2.选择排序
原理:
每次选择出最佳。原地操作几乎是选择排序的唯一优点,当空间复杂度(space complexity)要求较高时,可以考虑选择排序,实际适用的场合非常罕见。
for(i:0~n-1)
for(j:i~n-1)
性能:
与输入无关
最坏:/2次比较、n交换
最好:/2次比较、0交换
不稳定
3.1.插入排序
原理:
对比之前的元素。算法的内循环是紧密的,对小规模输入来说是一个快速的原地排序算法。
for(i:1~n-1)
for(j:i~0)
性能:
取决于输入
最坏:/2次比较、n交换
最好:n-1次比较、0交换
平均:
稳定
3.2.希尔排序
原理:
对比之前的元素,每h个元素对比(i与i-h、i-2h、i-3h。。。对比)。
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了,此时插入排序较快。假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为 O(n2) 的排序(冒泡排序或插入排序),可能会进行 n 次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少量比较和交换即可移到正确位置。
while(h<n/3)
h=3h+1
while(h>=1)
for(i:h~n)
for(j:i~h)
h/=3;
性能:
取决于输入
最坏:nn次比较、n交换
最好:n-1次比较、0交换
平均:nn
不稳定
4.快排
原理:
先切分,再排序(递归)
while(true)
while(a[++i] < v)
if(i == hi)
break;
while(a[--j] > v)
if(j == lo)
break;
if(i>= j)
break;
echo(a,i,j)
echo(a,lo,j)
性能:
与划分是否对称有关
最坏:
最好:nlogn
平均:nlogn
空间:递归辅助栈nlogn
不稳定
5.归并排序
原理:
先排序再合并 自顶向下判断(左半->左半...->左半 -> 合并->右半 -> 合并->合并->...->合并)
sort(int[] a, int lo, int hi){
if(hi <= lo)
return
int mid = lo + (hi-lo)/2
sort(a, lo, mid)
sort(a, mid+1, hi)
merge(a, lo,mid, hi)
}
merge(int[] a, int lo, int mid, int hi){
int i = lo, j = mid+1
aux[] = a[]
for(k : lo~hi)
if(i>mid)
a[k] = aux[j++]
else if(j>hi)
a[k] = aux[i++]
else if(aux[j]<aux[i])
a[k] = aux[j++]
else
a[k] = aux[i++]
}
性能:
最坏:nlogn
最好:n
平均:nlogn
空间:n
稳定
6.堆排序
原理:
二叉堆有两种,最大堆和最小堆。最大堆特性是指除了根以外的每个节点 i ,有 A(Parent(i)) ≥ A[i] ,即某个节点的值至多是和其父节点的值一样大。最小堆特性是指除了根以外的每个节点 i ,有 A(Parent(i)) ≤ A[i] ,最小堆的最小元素在根部。
在堆排序算法中,我们使用的是最大堆。最小堆通常在构造有限队列时使用。
堆可以被看成一棵树,节点在堆中的高度定义为从本节点到叶子的最长简单下降路径上边的数目;定义堆的高度为树根的高度。因为具有 n 个元素的堆是基于一棵完全二叉树,因而其高度为 Θ(lg n) 。
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。
上浮:
private void swim(int k) {
while (k > 1 && less(k / 2, k)) {
exch(k / 2, k);
k = k / 2;
}
}
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) j++;
if (!less(k, j)) break;
exch(k, j);
k = j;
}
}
public static void sort(Comparable[] a){
int N = a.length;
for(int k = N/2; k >= 1; k--){
sink(a, k, N);
}
while(N > 1){
exch(a, 1, N--);
sink(a, 1, N);
}
}
性能:
最坏:nlogn
最好:nlogn
平均:nlogn
空间:最差n,最好1
不稳定
更多:https://www.cnblogs.com/gaochundong/p/comparison_sorting_algorithms.html