由上一篇博文可知,冒泡排序时,若待排序序列长度为n,则冒泡排序法时间复杂度为O(n2)。但若待排序序列已经按值有序,则其时间复杂度变为O(n)。由此推想,若待排序序列按值“基本有序”,则冒泡排序法的效率可以明显提高,从另一方面来看,冒泡排序法基本原理较简单。谢尔排序法正是从以上两点对冒泡排序法进行改进得到的。
核心思想
首先确定一个元素间隔数gap(也称增量),然后将参加排序的序列按此间隔数从第一个元素开始一次分为若干个子序列,即分别将所有位置相隔为gap的元素视为一个子序列,在各个子序列中采用某种排列方法进行排序(这里采用冒泡排序法);然后减小排列间隔,并重新将整个序列按新的间隔数分成若干个子序列,再分别对各个子序列进行排序,重复以上步骤,直到间隔数gap=1。
主要特点
对于排序的每一趟以不同的的间隔数对子序列进行排序,因而元素的移动在子序列之间跳跃进行。gap越大,跳跃的跨度就越大。很多情况下,当gap=1时,序列几乎已经按值有序,不需要进行较多元素的移动就能达到排序目的。
举例
趟序 | 间隔数 | 排序结果 | |||||||
---|---|---|---|---|---|---|---|---|---|
位数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
初始 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49' | |
第1趟 | gap1=4 | 49 | 13 | 27 | 49' | 76 | 38 | 65 | 97 |
第2趟 | gap2=2 | 27 | 13 | 49 | 38 | 65 | 49' | 76 | 97 |
第3趟 | gap3=1 | 13 | 27 | 38 | 49 | 49' | 65 | 76 | 97 |
初始时,我们按照gap=4进行划分,则可以分4个子序列,上面表格中相同颜色的格子代表一个子序列。在每个子序列中,所有数值间进行冒泡排序。这里因为每个子序列中只有两个数值,故按照冒泡排序,只需要按大小对两个数值进行交换即可。经过第一趟排序,得到排序结果见表格。再使gap=2,得到第二趟排序结果,依次进行直到gap=1.例子中由于数值较少,故只进行3趟排序就得到了想要的结果。
注意
gap值的选取方法不是固定的,常用方法是每次gap值为上一次的一半,向下取整。
代码
约定:
- 假设数据中有n个数据元素(关键字)。排列算法中,将序列中各关键字值依次存放于类型为keytype的数组元素K[1], K[2], K[3], …, K[n]中。
- 排序结果按照数据元素(关键字)值的大小,从小到大排序。
void SHELLSORT(keytype K[], int n)
{
int i, j, flag, gap = n;
keytype temp;
while ( gap > 1 ) {
gap = gap / 2;
do {
flag = 0;
for ( i=1 ; i<=n-gap ; i++ ) {
j = i + gap;
if ( K[i] > K[j] ) {
temp = K[i];
K[i] = K[j];
K[j] = temp;
flag = 1;
}
}
} while ( flag != 0 );
}
}