注册一亩三分地论坛,查看更多干货!
您需要 登录 才可以下载或查看附件。没有帐号?注册账号 
x
本帖最后由 北美农民 于 2013-3-8 12:24 编辑
大家久等了, 希尔排序(Shell Sort)由Shell在50年代末提出, 其思想是分组插入排序。希尔排序也是我最喜欢的排序之一, 一来感觉很多人不了解希尔排序, 所以可以对很多人卖弄 , 二来,可以加深对 分组策略, 插入排序的理解。
注:若无特殊说明, 排序目的为递增。
何为分组插入排序? 基本算法是:
1:先将数组分成gap组子数组(由变量gap决定,组内相邻元素间隔值为gap)
2:然后每一组子列分别进行插入排序, gap=gap/2
3: 重复步骤1,2, 直到gap=0,数组排序结束。
用实例来说明吧, 设存在长度为10的数组。
第一次分组排序:
gap=10/2=5,于是将数组分成5组。
| 59 | 98 | 33 | 78 | 86 | 23 | 19 | 64 | 23 | 100 | | 1a | 2a | 3a | 4a | 5a | 1b | 2b | 3b | 4b | 5b |
说明: 阿拉伯数字相同的为一组, 一共gap组, 即5组{59, 23} {98, 19} {33, 64} {78, 23} {86, 100}
我们分组目的是, 对分成的gap个子数组的组内进行插入排序。(插入排序请看教程2:http://www.1point3acres.com/bbs/thread-44082-1-1.html)
希尔排序算法为什么分组? 精髓就在这里: 因为插入排序对于基本的case,有序或者大部分有序的case效率是很高的, 其次实现也非常简单。
回到正题, 我们对5组{59, 23} {98, 19} {33, 64} {78, 23} {86, 100}插入排序后, 得到{23, 59} {19, 98} {33, 64} {23, 78} {86, 100}
第二次分组排序:
第一次分组排序后,数组变成如下, gap=5/2=2。
23
| 19 | 33 | 23 | 86 | 59 | 98 | 64 | 78 | 100 | | 1a | 2a | 1b | 2b | 1c | 2c | 1d | 2d | 1e | 2f
|
gap=2于是数组分成2组{23,33,86,98,78} {19,23,59,64,100}, 不知道各位发现了没有, 每组的元素已经非常有序了, 这正是插入排序想看到的。
排序后数组为:
| 23 | 19 | 33 | 23 | 78 | 59 | 86 | 64 | 98 | 100 |
第三次分组排序:
gap=2/2=1, 因此只有一组了, 就是原数组本身:
| 23 | 19 | 33 | 23 | 78 | 59 | 86 | 64 | 98 | 100 | | 1a | 1b | 1c | 1d | 1e | 1f | 1g | 1h | 1i | 1j |
此时再做一次插入排序即可,下一次gap=1/2=0, 排序完毕如下:
19 23 23 33 59 64 78 86 98 100
按照算法步骤, 我们严格实现代码:
void ShellSort(int[] a, int n)
{
int gap=n/2;
while (gap>0)
{
//分组处理
for (int i=0; i<gap; i++)
for (int j=i+gap; j<n; j+=gap)
if (a[j]<a[j-gap])
{
int t=a[j];
int k=j-gap;
while (k>=0 && a[k]>t)
{
a[k+gap]=a[k];
k-=gap;
}
a[k+gap]=t;
}
gap=gap/2;
}
}
改进1: 我们用不着每次让i从0循环到gap-1这个遍历, 直接从gap, gap+1开始就行了。 于是 上面的第二层循环可以免了,代码如下。
void ShellSort(int[] a, int n)
{
int gap=n/2;
while (gap>0)
{
for (int j=gap; j<n; j++)
if (a[j]<a[j-gap])
{
int t=a[j];
int k=j-gap;
while (k>=0 && a[k]>t)
{
a[k+gap]=a[k];
k-=gap;
}
a[k+gap]=t;
}
gap=gap/2;
}
}
改进2:我们在改进1的基础上, 使用插入排序的最简写法, 把最外层while循环改写成for循环。
void ShellSort(int[] a, int n)
{
for (int gap=n/2; gap>0; gap/=2)
for (int j=gap; j<n && a[j]<a[j-gap]; j++)
for (int k=j; k-gap>=0 && a[k]<a[k-gap]; k-=gap)
swap(a[k], a[k-gap]);
}
很简洁, 有木有?!
总结: gap其实怎么设定都不影响算法的正确性, 但我们一般采用gap=n/2,然后每次减半。 其实gap的选择猫腻可不少,这里就不介绍了, 请读者自行研究。送上链接
http://zh.wikipedia.org/wiki/%E5 ... F.E5.BA.8F.E5.88.97
|