《大话数据结构》笔记——第9章 排序(二)

文章目录

  • 9.6 希尔排序
    • 9.6.1 希尔排序原理
    • 9.6.2 希尔排序算法
    • 9.6.3 希尔排序复杂度分析
  • 9.7 堆排序
    • 9.7.1 堆排序算法
    • 9.7.2 堆排序复杂度分析
  • 9.8 归并排序
    • 9.8.1 归并排序算法
    • 9.8.2 归并排序复杂度分析
  • 9.9 快速排序
    • 9.9.1 快速排序算法
    • 9.9.2 快速排序复杂度分析
  • 9.10 总结回顾
  • 9.11 结尾语

声明:

本博客是本人在学习《大话数据结构》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。

本博客已标明出处,如有侵权请告知,马上删除。

9.6 希尔排序

给大家出一道智力题。请问 “VII” 是什么?

嗯,很好,它是罗马数字的 7 。现在我们要给它加上一笔,让它变成 8( VIII ),应该是非常简单,只需要在右侧加一竖线即可。

现在我请大家试着对罗马数字 9 ,也就是 “IX” 增加一笔,把它变成 6 ,应该怎么做

我这里有 3 种另类的方法可以实现它。

  1. 观察发现 “X” 其实可以看作是一个正放一个倒置两个 “V” 。因此我们,给 “IX” 中间加一条水平线,上下颠倒,然后遮住下面部分,也就是说,我们所谓的加上一笔就是遮住一部分,于是就得到 “VI” ,如图 9-6-1 所示。

    《大话数据结构》笔记——第9章 排序(二)_第1张图片

  2. 在 “IX” 前面加一个 “S” ,此时构成一个英文单词 “SIX” ,这就等于得到一个 6 了。哈哈,我听到下面一片哗然,我刚有没有说一定要是 “VI” 呀,我只说把它变成 6 而已,至于是罗马数字还是英文单词,我可没有限制。显然,你们的思维受到了我前面举例的 “VII” 转变为 “VIII” 的影响,如图 9-6-2 所示。

    《大话数据结构》笔记——第9章 排序(二)_第2张图片

  3. 在 “IX” 后面加一个 “6” ,得到 “1X6” ,其结果当然是数字 6 了。大家笑了,因为这个想法实在是过分,把字母 “I” 当成了数字 1 ,字母 “X” 看成了乘号。可谁又规定说这是不可以的呢?只要没违反规则,得到 6 即可,如图 9-6-3 所示。

    《大话数据结构》笔记——第9章 排序(二)_第3张图片

智力题的答案介绍完了。大家会发现,看似解决不了的问题,还真不一定就没有办法,也许只是暂时没想到罢了

我们都能理解,优秀排序算法的首要条件就是速度(还有其他的要求,速度是第一位)。于是人们想了许许多多的办法,目的就是为了提高排序的速度。而在很长的时间里,众人发现尽管各种排序算法花样繁多(比如前面我们提到的三种不同的排序算法),但时间复杂度都是 O(n^2) , 似乎没法超越了(这里的排序是指内排序)。此时,计算机学术界充斥着 “排序算法不可能突破 O(n^2)” 的声音。就像刚才大家做智力题的感觉一样,“不可能” 成了主流。

终于有一天,当一位科学家发布超越了 O(n^2) 新排序算法后,紧接着就出现了好几种可以超越 O(n^2) 的排序算法,并把内排序算法的时间复杂度提升到了 O(logn) 。“不可能超越 O(n^2)” 彻底成为了历史。

从这里也告诉我们,做任何事,你解决不了时,想一想 “Nothing is impossible!” ,虽然有点违心,但这样的思维方式会让你更加深入地思考解决方案,而不是匆忙的放弃。

9.6.1 希尔排序原理

现在,我要讲解的算法叫希尔排序( Shell Sort )。希尔排序是 D.L.Shell 于 1959 年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是 O(n^2) 的,希尔排序算法是突破这个时间复杂度的第一批算法之一。

我们前一节讲的直接插入排序,应该说,它的效率在某些时候是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,此时直接插入很高效。还有就是记录数比较少时,直接插入的优势也比较明显。可问题在于,两个条件本身就过于苛刻,现实中记录少或者基本有序都属于特殊情况。

不过别急,有条件当然是好,条件不存在,我们创造条件也是可以去做的。于是科学家希尔研究出了一种排序方法,对直接插入排序改进后可以增加效率

如何让待排序的记录个数较少呢?很容易想到的就是将原本有大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序时,再对全体记录进行一次直接插入排序。

此时一定有人开始疑惑了。这不对呀,比如我们现在有序列是 { 9, 1, 5, 8, 3, 7, 4, 6, 2 } ,现在将它分成三组,{ 9, 1, 5 } , { 8 , 3, 7 } , { 4, 6, 2 } ,哪怕将它们各自排序排好了,变成 {1, 5, 9 } ,{3, 7, 8 } ,{2, 4, 6 } ,再合并它们成 {1, 5, 9, 3, 7, 8, 2, 4, 6 } ,此时,这个序列还是杂乱无序,谈不上基本有序,要排序还是重来一遍直接插入有序,这样做有用吗?需要强调一下,所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像 { 2, 1, 3, 6, 4, 7, 5, 8, 9 } 这样可以称为基本有序了。但像 { 1, 5, 9, 3, 7, 8, 2, 4, 6 } 这样的 9 在第三位, 2 在倒数第三位就谈不上基本有序。

问题其实也就在这里,我们分割待排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。而如上面这样分完组后就各自排序的方法达不到我们的要求。因此,我们需要采取跳跃分割的策略:将相距某个 “增量” 的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。

9.6.2 希尔排序算法

好了,为了能够真正弄明白希尔排序的算法,我们还是老办法——模拟计算机在执行算法时的步骤,还研究算法到底是如何进行排序的。

希尔排序算法代码如下。

/* 对顺序表 L 作希尔排序 */
void ShellSort(SqList *L)
{
   
    int i,j;
    int increment=L->length;
    do
    {
   
        increment=increment/3+1;            /* 增量序列 */
        for(i=increment+1;i<=L->length;i++)
        {
   
            if (L->r[i]<L->r[i-increment])    
            {
    
                /* 需将L->r[i]插入有序增量子表 */ 
                L->r[0]=L->r[i];             /* 暂存在L->r[0] */
                for(j=i-increment;j>0 && L->r[0]<L->r[j];j-=increment)
                {
   
                    L->r[j+increment]=L->r[j]; /* 记录后移,查找插入位置 */
                }
                L->r[j+increment]=L->r[0]; /* 插入 */
            }
        }
    } while(increment>1);
}
  1. 程序开始运行,此时我们传入的 SqList 参数的值为 length=9 ,r[10]= { 0, 9, 1, 5, 8, 3, 7, 4, 6, 2 } 。这就是我们需要等待排序的序列,如图 9-6-4 所示。

    《大话数据结构》笔记——第9章 排序(二)_第4张图片

  2. 第 5 行,变量 increment 就是那个“增量”,我们初始值让它等于待排序的记录数。

  3. 第 6〜22 行是一个 do 循环,它提终止条件是 increment 不大于 1 时,其实也就是增量为 1 时就停止循环了。

  4. 第 8 行,这一句很关键,但也是难以理解的地方,我们后面还要谈到它,先放一放。这里执行完成后, increment=9/3+1=4 。

  5. 第 9〜21 行是一个 for 循环,i 从 4+1=5 开始到 9 结束。

  6. 第 11 行,判断 L.r[i] 与 L.r[i-increment] 大小,L.r[5]=3 小于 L.r[i- increment]=L.r[1]=9 ,满足条件,第 14 行,将 L.r[5]=3 暂存入 L.r[0] 。第 15〜18 行的循环只是为了将 L.r[1]=9 的值赋给 L.r[5] ,由于循环的增量是 j - = increment ,其实它就循环了一次,此时 j= -3 。第 19 行,再将 L.r[0]=3 赋值给 L.r[j+increment]=L.r[-3+4]=L.r[1]=3 。如图 9-6-5 所示,事实上,这一段代码就干了一件事,就是将第 5 位的 3 和第 1 位的 9 交换了位置。

    《大话数据结构》笔记——第9章 排序(二)_第5张图片

  7. 循环继续,i=6,L.r[6]=7>L.r[i-increment]=L.r[2]=1 ,因此不交换两者数据。如图 9-6-6 所示。

    《大话数据结构》笔记——第9章 排序(二)_第6张图片

  8. 循环继续,i=7,L.r[7]=4

    《大话数据结构》笔记——第9章 排序(二)_第7张图片

  9. 循环继续,i=8 , L.r[8]=6

    《大话数据结构》笔记——第9章 排序(二)_第8张图片

  10. 循环继续ÿ

你可能感兴趣的:(#,《大话数据结构》,数据结构)