希尔排序
算法思想
- 先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;
- 然后取d2<d1,重复上述分组和排序操作;
- 直至di=1,即所有记录放进一个组中排序为止。
如下图,该列表有九个项。如果我们使用三的增量,有三个子列表,每个子列表可以通过插入排序进行排序:
完成这些排序后,我们得到如下图 所示的列表。虽然这个列表没有完全排序,但发生了很有趣的事情。 通过排序子列表,我们已将项目移动到更接近他们实际所属的位置:
接下来使用增量为 1 的插入排序; 换句话说,标准插入排序。注意,通过执行之前的子列表排序,我们减少了将列表置于其最终顺序所需的移位操作的总数。对于这种情况,我们只需要四次移位完成该过程:
动画演示:
实现
C++
void shellSort(vector<int> &array)
{
int len = array.size();
int i, j, gap; // gap为步长,每次减为原来的一半。
for (gap = len / 2; gap > 0; gap /= 2)
{
// 共gap个组,对每一组都执行直接插入排序
for (i = 0; i < gap; i++)
{
for (j = i + gap; j < len; j += gap)
{
// 如果array[j] < array[j-gap],则寻找array[j]位置,并将后面数据的位置都后移。
if (array[j] < array[j - gap])
{
int tmp = array[j];
int k = j - gap;
while (k >= 0 && array[k] > tmp)
{
array[k + gap] = array[k];
k -= gap;
}
array[k + gap] = tmp;
}
}
}
}
}
python
def shellSort(alist):
sublistcount = len(alist)//2
while sublistcount > 0:
#一次将分段的起始元素开始输入到分段插入排序中
for startposition in range(sublistcount):
gapInsertionSort(alist,startposition,sublistcount)
sublistcount = sublistcount // 2
#本质上实现的是插入排序
def gapInsertionSort(alist,start,gap):
for i in range(start+gap,len(alist),gap):
currentvalue = alist[i]
position = i
while position>=gap and alist[position-gap]>currentvalue:
alist[position]=alist[position-gap]
position = position-gap
alist[position]=currentvalue
总结
希尔排序的增量:
希尔排序的增量数列可以任取,需要的唯一条件是最后一个一定为1(因为要保证按1有序)。
稳定性:
Shell排序是一个多次插入的过程,在一次插入中我们能确保不移动相同元素的顺序,但在多次的插入中,相同元素完全有可能在不同的插入轮次被移动,最后稳定性被破坏,因此,Shell排序不是一个稳定的算法。
适用场景:
Shell排序虽然快,但是毕竟是插入排序,其数量级并没有后起之秀--快速排序快。在大量数据面前,Shell排序不是一个好的算法。但是,中小型规模的数据完全可以使用它。
复杂度:
\(O(N * \log N)\)