核心思想
希尔排序(Shell Sort)是Donald Shell命名的,是第一个突破O(n2)时间的排序算法之一。希尔排序的核心在于选取恰当的增量d,对于线性表
实现
希尔排序相较于普通插入排序,多的就在于增量上。因此实现时,增加了一个函数指针参数,用来设置增量。最外层的while
循环终止条件是增量d == 0
,在此之前必然经历了增量为1
的插入排序,因此序列就有序了。在外层while
循环内,可以看到和普通插入排序极为相似的地方,区别就在于并不是保证整体有序,而是保证以增量为d
的子序列有序。在实现过程中,和之前一样,也要注意插入排序内层循环两个终止条件要保持一致;以及j = i - d
的含义。
下面就给出一个C++实现供参考。
/**
* 希尔增量函数
* @param int x 原增量
* @return int 经过一次迭代后新的增量
*/
int shell_increment(int x){
return x / 2;
}
/**
* 希尔排序
* @param std::vector<_Ty> & a 需要排序的表
* @param int(*increment_func)(int x) 增量序列函数的指针
*/
template<typename _Ty>
void shell_sort(std::vector<_Ty> & a, int(*increment_func)(int x)){
int i, j, d;
int round = 0; //指示进行了第几次迭代,观察输出用
d = (*increment_func)(a.size()); //求出初始增量值
while (d > 0){ //保证增量d > 0。最后一次迭代增量必然为1。
for (i = d; i < a.size(); i++){
_Ty tmp = a[i];
//同10.2(1)中有讲,要保证两个循环终止条件的一致性
//都要将终止时的指针(下标)指向应该放入的位置的前一个元素
for (j = i - d; j >= 0 && a[j] > tmp; j -= d)
a[j + d] = a[j];
a[j + d] = tmp;
}
//输出每一次迭代后的表
std::cout << "Round " << ++round << " : " << " d = " << d << std::endl;
output_list(a);
//减小增量
d = (*increment_func)(d);
}
}
对于上例的输入数据5 4 11 18 1 70 35 90 100 2
,可以看出最外层每一次迭代的结果。
第一次迭代保证了5-70
,4-35
…这样增量为5的有序,第二次保证了1-5-11-35-100
,2-4-18-70-90
增量为2的有序。但是在最后一次迭代之前,始终不能保证整体有序。
Round 1 : d = 5
5 4 11 18 1 70 35 90 100 2
Round 2 : d = 2
1 2 5 4 11 18 35 70 100 90
Round 3 : d = 1
1 2 4 5 11 18 35 70 90 100
时间空间复杂度以及稳定性
希尔排序的空间复杂度和普通插入排序类似,都是O(1)。对于空间复杂度来说,由于增量序列选取的不同,时间复杂度也是不同的,而且也较难分析。一般情况下认为是一种次二次(nt,1<t<2)的时间复杂度,对于例子中的Shell Increment,有着最坏的O(n2)运行时间。希尔排序的效率通常情况下好于普通插入排序,因为普通插入排序对于数据近乎有序的时候效率很高,而希尔排序将后面元素较多时的插入排序的输入变得尽量有序。
稳定性方面,希尔排序是不稳定的。可以举出反例说明,这里就略去了。