希尔排序的简单实现


希尔排序

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。

既然是插入排序的改进版本,我们就先来看一看插入排序。

插入排序的原理就和打牌时我们抓牌一样,每次摸到一张新的牌,我们都会将它插入到已经有序的牌序中,使之仍然有序。例如 我们 依次抽到 4 2 9 J 10  8 ,那么我们的牌序依次为 4 ,  2 4 , 2 4 9 , 2 4 9 J , 2 4 8 9 10 J,

抽到10 为例,我们从后往前比较, 10 J 比较,10J小, 所以 J 往后挪一个位置,然后 910 比, 10 更大,说明找到了应在的位置,插入。

抽到最后的8 , 我们从后往前比较, 8 < J  ,J往后挪,  8 < 10 , 10 往后挪, 8<9 ,9往后挪 , 8 >4,找到位置插入。


插入排序就是模拟了上述比较的过程。

可以看出,如果每次摸到一张新的牌,如果手中已有的最后一张牌比 刚摸得牌小,那么就不用继续比较了,这正好对应了插入排序的内层循环。

对应的先来看一下插入排序的源码

void Insertion_Sort(ElemType *A,int n){
	for(int i = 1;i<n;i++){
		ElemType tmp = A[i];
		int j;
		for( j=i; j > 0 && A[j-1] > tmp ;j--) //① //当前序列的最后 往前与tmp比较
			A[j] = A[j-1]; //遇到逆序就交换
		A[j] = tmp; 
	}
}

我们知道插入排序的平均时间复杂度为O(n^2), 最佳时间复杂度为O(n),其效率的差异其实 就在内层循环的执行次数上。可见如果初始序列有序或者说基本有序,那么插入排序的效率就非常高了,最佳就是O(n)。

但是插入排序每次只能交换相邻的一对元素,也就是说每次只能消除一个逆序对,而对于N个数,科学的研究表明:其逆序对的数目大致在 n(n-1) /4个,所以平均效率不可能超过O(n^2).

于是乎聪明的科学家们想到如果不交换相邻的元素那? 而是去交换相隔比较远的元素那?那么我们每次交换消除的逆序对就不止一个了!

希尔排序就是利用了这样的性质。


希尔排序设置了一个增量,每次通过缩小增量序列进行插入排序。

这个增量就是交换元素的间隔。


如上图

增量为5,也就是说5间隔的有 81 35 41 , 我们将这个子序列执行插入排序,变成 35 41 81 ,还有 94 17 75 变成了 17 75 94 , 11 95 15 变成了 11 15 96,

然后3间隔的进行排序,3间隔的排完序之后可以看出序列已经基本有序了,然后再将增量缩小为1,执行一次基本的插入排序,算法完成。


源代码

void shellSort(ElemType *a,int n){
	for(int d = n/2;d>0;d/=2){ //希尔排序最初的增量分割
		for(int i=d;i<n;i++){
			ElemType tmp = a[i];
			int j;
			for(j=i;j>=d && a[j-d] >tmp;j-=d)
				a[j] = a[j-d];
			a[j] = tmp;
		}
	}
}

对比插入排序,和希尔排序,可以看出希尔排序只是多了一层for循环,用来控制增量,而里面两层就是最基本的插入排序,只是将交换的位置 由 a[ j ] a [ j - 1 ] 变成了 a[ j ] a[ j - d ],

d就是当前的增量。

增量的选取

当然对增量的选取是十分重要的,上述代码是希尔最初的增量分割,每次将增量缩小一半,也就是(增量序列:1,2,4,8,16...)但是当遇到下面这种情况,时间复杂度就O(n^2)了。




显然,对于增量的选取只要最后一次增量变为1,那么一定可以使得序列有序。

已知的最好增量序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),该序列的项来自

这两个公式算出。

感兴趣的同学自行实现或者问度娘吧。

posted @ 2015-11-06 20:20  编程菌  阅读(231)  评论(0编辑  收藏  举报