关于ShellSort希尔排序

来源:3-1 归并排序法 - Merge Sort

xm0516

2017-08-17

由于课程未给出希尔排序,我便尝试着写一写,花了一个下午效率很低,但是还是云里雾里的,有这么三个问题:

  1. 既然ShellSort的思想是对每个子序列再进行InsertionSort,那么第二层for循环里的i应该i<子序列.length 才对,但是在第三层for中j=j-dk似乎又向我说:没事,上面就写i<n就好。就是有这种直观感性的直觉。请问怎么理解二层中的i<n?子序列的长度明显不会是n,第一次选完步长一个子序列应该只有2个元素才对。

  2. 如果是奇数个的序列的话,肯定有落单的元素,代码是通过什么样的逻辑处理这个单独的人呢?

  3. 我的代码是根据课程中的InsertionSort写的,我想知道到底对不对。。。测试了一下感觉好像没问题,但是不知道到底是不是没问题。


template<typename T>
void ShellSort(T arr[],int n){

    for (int dk = n/2; dk >=1; dk=dk/2)
    /*Shell排序,初始步长使之为序列长度的一般,也就是一开始分两组;
    往后步长自取一半,直到最后为1,对整个序列进行直接插入*/
    {
        for (int i = dk; i < n; i++)
        {
            T e = arr[i];
            int j;
            for (j = i; j > 0 && arr[j - dk] > e; j -= dk)
            {
                arr[j] = arr[j - dk];
            }
            arr[j] = e;
        }
    }

    for (int count = 0; count < n; count++)
    {
        cout << arr[count] << endl;
    }
}
写回答

1回答

liuyubobobo

2017-08-18

由于希尔排序一般不会在面试中问到,所以在这个课程里由于时间限制,我选择不做详细介绍。希望感兴趣的同学自学完成:)


你的代码大体正确,有一个问题,第12行,j>0应该改成j>=dk,才能保证访问arr[j-dk]不越界。完整的第12行的for循环应该是:

for (j = i; j >= dk && arr[j - dk] > e; j -= dk)


根据你提的问题,我觉得你对Shell Sort的理解是有问题的,但是能把代码写的这样正确,相当相当赞,说明调试工夫了得:)


简单回答你的问题如下:

首先,你在代码中的注释是有误的。初始步长dk是n/2,不是将整个数组分成了两组,而是分成了n/2组。比如8个元素:0 1 2 3 4 5 6 7,初始步长dk=4的话,是将这8个元素分成了4组,0 4; 1 5; 2 6; 3 7;从第8行起,是针对这4组数据进行插入排序。


在这里注意,我们不是先处理完一组再处理一组的,而是4组数据轮次处理的。依然以8个元素为例,我们的i从dk=4开始,是因为0 1 2 3分别是四组的第一个元素,在插入排序中第一个元素已经有序了,不用管。然后看i = 4的元素应该插入到0 4这组的哪里;之后看i = 5应该插入到1 5这组数据的哪里;之后看i = 6应该插入到2 6这组数据的哪里。。。 以此类推。通过这个描述,就能看到为什么i的终止条件是i<n,因为每一个元素都肯定属于某一组,我们需要处理每一个元素,用插入排序的思路将它放到这一组的相应位置。

以上的描述也解释了第三重for循环,j的变化为什么是j-=dk;因为j在遍历这一组的其他元素,步长是dk:)


至于落单的元素,完全不用特殊处理啊,只不过我们分组以后每组的元素个数不同而已。第8行i<n的终止条件已经保证了每个元素都会被处理。比如我们有9个元素0 1 2 3 4 5 6 7 8,初始步长dk=4,我们依然分成了4组:1 4 8;1 5; 2 6; 3 7。


如果对上面的过程还不理解,建议自己用一个小数据跟跟看,看看每一步数据是如何变化的:)加油!

4
6
liuyubobobo
回复
xm0516
貌似是循环引用的问答题,最好在每一个肋生命中都标注#ifndef;具体可以查看课程官方github的代码:)
2017-08-30
共6条回复

算法与数据结构(C++版) 面试/评级的算法复习技能包

课程专为:短时间内应对面试、升职测评等艰巨任务打造

11209 学习 · 1617 问题

查看课程