希尔排序,就是插入排序的改进,具体改进的办法,就是利用增量对排序队列进行划分,再使用插入排序,如以下:
利用分组排序后,再进行最后的排序
其伪代码为:
for path=0 to length_dlta
for i=path to n //一趟分段直接插入排序
temp = data[i]
j=i-path
while j>=0 and temp<data[j]
data[j+path]=data[j]
j-=path
data[j+path]=temp
实现代码如下:C++实现
template<class T> void ShellSortT(T a[], int iLength)
{
int increment = iLength / 2;
int cols[] = {1391376, 463792, 198768, 86961, 33936, 13776, 4592,
1968, 861, 336, 112, 48, 21, 7, 3, 1};
for(int k = 0 ; k< 16; k++)
{
//int increment = cols[k];
for(int i= increment; i< iLength; i++)
{
T key = a[i];
int j = i-increment;
while( j>=0 && a[j] > key)
{
a[j+increment] = a[j];
j = j - increment;
}
a[j+increment] = key;
}
increment = increment / 2;
}
}
int main(int argc, char* argv[])
{
int iData[5] = {6,8,5,7,4};
ShellSortT(iData, 5);
}
关于时间复杂度,收到递增量的影响
增量序列
- 希尔序列:[1,2,4,8,16,32,...,2k,...][1,2,4,8,16,32,...,2k,...]
该序列由希尔本人提出。这种序列在最坏情况下的时间复杂度为O(n2)O(n2)。原因很复杂,可以参考《数据结构 邓俊辉》著。 PS序列:[1,3,7,15,31,63,...,2k−1,...][1,3,7,15,31,63,...,2k−1,...]
这个序列中各项两辆互素。采用这个序列,希尔排序的效率可以改进至O(n3/2)O(n3/2),其中n为排序规模。Pratt序列[1,2,3,4,6,8,9,12,16,...,2p3q,...][1,2,3,4,6,8,9,12,16,...,2p3q,...]
其中各项除2 和3 外均不含其它素数因子。采用Pratt序列,希尔排序时间复杂度至多为O(nlog2n)O(nlog2n)。- Knuth序列(没找到出处)
Knuth提出了他自己的增量序列,遵循公式(3k-1)/ 2或[1,4,14,40,121,…]
算法分析
空间复杂度: 仅使用了常数个辅助单元,因而空间复杂度为O(1)O(1)。
时间复杂度:ShellSort排序准确的说是一类排序。当使用不同的增量序列进行布置时,效率差异极大。这个int [] cols数组的选取(增量序列)选取非常的关键,究竟什么样的布置方案(增量序列cols)才是最好,目前还是一个数学难题,所以时间复杂度分析也比较困难,前人对这方面的研究也比较多,可以查阅相关论文。这里仅仅可以讨论下PS,Pratt和Donald Shell序列的复杂性。 对于Shell的序列,复杂度是O(n2)O(n2),而对于Pratt的序列,它是O(nlog2n)O(nlog2n)。 PS序列,其中复杂性是O(n3/2)O(n3/2)。
稳定性:(不稳定)
希尔排序的关键是 把排序序列 用相隔某个“增量” 将元素组成一个“列”,每个“列”各自进行插入排序,从而实现元素的跳跃式移动,使得排序效率提高。当相同的关键字被划分到不同的“列”时,可能会改变它们之间的相对次序,因此希尔排序是一个不稳定的算法。
文章中引用到: