背景知识:
在学习排序算法前,我们需要知道两个概念:排序算法的稳定性、逆序对
在一组待排序记录中,如果存在任意两个相等的记录R和S,且在待排序记录中R在S前,如果在排序后R依然在S前,即他们前后位置在排序前后不发生改变,则称该排序算法是稳定的。
(排序按从小到大规则的情况下)如果i<j,但是在数组中a[i]>a[j] 则称(i,j)是一对逆序对。
简单插入排序:
思想:
将待排序的一组序列分为已排好的和未排好的两个部分;初始状态时,已排序的序列仅仅包含第一个元素,未排序序列包含除第一个元素以外的元素。然后每次从未排序序列中拿出一个插入到已排序的序列中(按照大小顺序)。如此反复,直到未排序序列中元素个数为0。
代码实现:
void simpleInsertSort(int *a, int N){
for (int p=1; p<N; p++) {
int temp = a[1];
int i;
for (i = p; i>0 && a[i-1]>temp; i--) {
a[i]=a[i-1];
}
a[i] = temp;
}
}
该排序算法是稳定的,如果序列基本有序,则使用插入排序是高效的。
简单插入排序效率不高的一个重要原因在于每次只交换相邻的两个元素,这样只能消去一对逆序对。
如果想要排序算法提高效率,则需要一次消灭一对以上逆序对 =》交换间隔较远的2个元素。
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
下面来看一个简单插入排序的升级版:希尔排序:
思想:
将待排序列的元素按一定的间隔划分为若干子序列,对这些子序列进行插入排序。其中“间隔”定义为一组增量序列,用来分割待排序列,即位置之差等于当前增量的元素归属于同一个子序列,并进行插入排序。排完后,再选取下一个增量,重新划分序列再次排序,直到增量变为1。
void shellSort(long long a[], int N){
int Sedgewick[] = {64769,36289,16001,8929,3905,2161,929,505,209,109,41,19,5,1,0};//分割待排序列的增量
int si,interval,p,i;
long long temp;
for (si = 0; Sedgewick[si]>=N; si++){}//该循环的目的:找到比数组数量少 的 最大增量
for (interval = Sedgewick[si]; interval>0; interval = Sedgewick[++si]) {//增量切换
for (p=interval; p<N; p++) { //实现一个增量间隔的插入排序
temp = a[p];
for (i=p; (i>=interval)&&(a[i-interval]>temp); i-=interval) {
a[i] = a[i-interval];
}
a[i]=temp;
}
}
}
那么我们应该选取怎样的增量数组以使算法的效率更高呢?有这么两种:
Hibbard增量序列:{2^k-1,...,7,3,1}
Sedgewick增量序列:{9x4^i - 9x2^i+1} 或 {4^i - 3x2^i+1}
经验表明,希尔排序对规模以万记的待排序列会体现出比较好的效率。同时希尔排序因为选取不同增量进行排序时,可能会导致相同元素相对位置的改变,所以不是稳定的。
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔(shell)排序是不稳定的。