赞
踩
我们知道当一个序列基本有序时,直接插入会变得很高效。因为此时只需少量的移动元素,操作集中在元素的比较上。基于这种想法,我们就试图把一个序列在进行直接插入前调整得尽量有序。这就是希尔排序(Shell Sort)的核心思路。(Shell只是算法发明者的名字,无特殊含义)
那到底该怎么做呢?
希尔排序一反以前的做法,插入为何只在相邻的元素之间进行,不相邻的同样可以进行。于是,希尔排序也被形象地称为”跳着插“。
那应该隔几个元素进行插入呢?
这就说到希尔排序的另一个名字:缩小增量排序(Diminishing Increment Sort)。实际上这个增量序列或者说步长序列是可以提前指定的。不同的序列可以极大地影响到算法效率,至于最好的步长序列貌似还在研究。不过可以肯定的是最后的一个步长一定是1。因为最后一次是直接插入。
先来看一种最简单、也最常用的步长序列:n/2、n/2/2、... 1 (n是待排序的元素个数)。也是就说,初始步长是n/2,以后每次减半,直到步长为1。
利用这种步长序列,举一个例子:开始序列中加下划线的字体表示每一趟待排序的数字。
原序列: 21 12 4 9 9 5 78 1 (n=8)
下标: 0 1 2 3 4 5 6 7
第一趟:步长step=4,0、4号元素直接插入排序
开始 21 12 4 9 9 5 78 1
结束 9 12 4 9 21 5 78 1
第二趟:步长step=2, 0、2、4、6号元素直接插入排序
开始 9 12 4 9 21 5 78 1
结束 4 12 9 9 21 5 78 1
第三趟:步长step=1,0、1、2、3、4、5、6、7、8号元素直接插入排序(显然这是整体直接插入排序)
开始 4 12 9 9 21 5 78 1
结束 1 4 5 9 9 12 21 78
如何理解每一趟排序:
仔细观察上述过程,一定可以理解上述四点。理解之后,就不难写出代码了。
下面给出这种排序的代码:
- void ShellSort1(int a[], int n) //希尔排序
- {
- if(a && n>1)
- {
- int i,j,step;
- for(step = n/2; step; step >>= 2)
- {
- for(i = step; i < n; i += step)
- for(j = i; j; j -= step)
- if(a[j] < a[j - step])
- swap(a[j], a[j - step]);
- }
- }
- }
若是,看过插入排序:交换排序,相信很容易看懂上述代码
。
若是指定步长,代码是这样的:
- void ShellSort2(int a[], int n, int step[], int nn)
- {
- if(a && n>1 && step && nn>1)
- {
- int i,j,k;
- for(k = 0; k < nn; k++)
- {
- for(i = step[k]; i < n; i += step[k])
- for(j = i; j; j -= step[k])
- if(a[j] < a[j - step[k]])
- swap(a[j], a[j - step[k]]);
- }
- }
- }
其中step数组代表步长序列,这里的代码与上一个相比,只是用step[i]替换了step,其它并无变化。
测试走起……
插入排序小结:
目前关于插入排序的文章写了5篇,具体实现涉及到了7种方法。于是,特地来总结一下:
一、什么是插入排序?
把一个元素插入到一个已经有序的序列中去,就叫插入排序。
二、插入排序的复杂度(时间和空间)?
这个得看具体的插入策略。比如最简单的直接插入。
空间复杂度 O(1)(只需一个临时变量)
时间复杂度
在最差的情况下,此时元素处于逆序。对于下标为i的元素,把它保存在临时变量中需移动一次,回填移动一次,由于逆序,前面的i个元素都得往后移动一次,共i次,显然也需比较i次。
故共需移动 求和i+2(i=1...n-1)=(n-1)(n+4)/2
共需比较 求和i(i=1...n-1)=n(n-1)/2
以上两者相加得 (n-1)(n+2)=O(n^2)
在最好的情况下,此时元素处于顺序。对于下标为i的元素,把它保存在临时变量中需移动一次,回填移动一次,由于正序,前面的i个元素不需移动,且只需比较一次。
故共需移动 2(n-1)
共需比较 n-1
以上两者相加得 3(n-1)=O(n)
若是计算平均时间复杂度,数学推理比较复杂,结论是 O(n^n)。
其它几种插入策略,其时间复杂度更加难以推理,不过总体上都是O(n^n)的。快一点的如二路插入、希尔排序(n(logn)^2),量级上不会改变。
提高插入排序的效率,可以从两个方面入手:减少比较次数、减少移动次数。
其中 二路插入是减少了移动次数,折半插入是减少了比较次数,表插入是用指针变化代替元素移动(因为指针变化代价比较低)。
转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/29222095
若是对你有所帮助,顶一个哦!
专栏目录看这里:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。