• (超详解)堆排序+(图解)


    目录:

            1:如何建堆(两种方法)

            2:两种方法建堆的时间复杂度分析与计算

            3:不同类型的排序方式我们应该如何建堆


    文章正式开始:

            1:如何建堆

               在实现堆排序之前我们必须得建堆,才能够实现堆排序

                    首先在讲解如何建堆之前让我们先来回顾一下堆的概念,堆是一种完全二叉树,它有两种形式,一种是大根堆,另外一种是小根堆。

                    大根堆:所有的父亲结点大于或等于孩子结点。

                    小根堆:所有的父亲结点小于或等于孩子结点。 

            本文在介绍堆排序的时候我们都默认排升序。

            方法1:我们采用向上调整算法建堆        

                     我们知道向上调整算法的前提是前面的数必须是堆,所以我们就形成了一种思路:

            第一个数我们可以看成是一个堆,那么从第二个数开始我们就依次采用向上调整算法,这样最后我们的数字就会形成一个堆。

            图解:

                    

                    向上调整建堆的代码如下,如果不理解可以自己尝试画图:

            

                    

    1. //假设排升序,建大堆
    2. void HeapSort(int* a, int n)
    3. {
    4. //先建堆,用向上调整算法
    5. for (int i = 1; i < n; i++)
    6. {
    7. AdjustUp(a, i);
    8. }
    9. }

             方法2:采用向下调整的思路建堆

                    向下调整的前提:要调整的对象左右子树都得是堆

                   那么我们如何通过一个数组来原地建堆呢?

                    其实我们可以这样想,叶子结点既可以看作是大堆,也可以看作是小堆,所以我们可以从后面往前面来建堆。

                    思路:找到倒数第一个非叶子结点,这样我们可以保证左右子树都是堆,才能够对整个堆使用向下调整算法的思想。

                 那么最后一个非叶子结点如何才能找到呢?这里不就是我们要记住的一个特点吗,通过孩子结点来算父亲结点。

                    parent=(child-1)/2;

                    我们先找到最后一个结点的下标,然后通过结点算父亲的公式不就可以算出来了吗

                    所以倒数第一个非叶子结点的下标不就是 (n-1-1)/2吗 ?

                    图解过程:

                    

     

             2:两种方法建堆复杂度的分析

                    首先我们直接公布结论: 

                    向上调整算法的时间复杂度为O(N*logN),向下调整算法的时间复杂度为O(N),所以建堆在复杂度的层面来说向下调整算法是优于向上调整算法的。

            向上调整算法的时间复杂度分析:

            我们知道向上调整算法是依次将后一个元素向上进行调整,那么最坏的情况下就是我们所插入一个数就要调整到根节点处。

            

            同理向下调整的复杂度分析

                       

             为啥同样都是建堆的过程,可是为啥向下调整算法的时间复杂度优于向上调整算法呢

            因为向下调整算法时,最后一层结点不需要向下调整,且最后一层的结点比较多,从下往上,结点个数变少,乘以的层数变多,但是主要取决于时间复杂度的是结点个数多的。

            而向上调整算法,最后一层结点的个数多,且需要调整的层数也最高,导致向上调整的时间复杂度高。

    3.堆排序

            在讲了前面两种算法的基础上我们就可以来谈一谈我们的堆排序了,堆排序并不是我们所讲的数据结构,虽然说堆数据结构也可以看出堆的升序与降序,但是我们可能并不是只要打印这个数组出来,我们可能还会进行一些算法,比如2分查找....。

            堆排序的思路:

                    1:首先对数组进行建堆。

                    2:将最后一个元素与第一个元素交换,在向下进行调整

                    3:循环往复的进行,最后排除来的就是我们所需要的结果了。

                那我们在排升序的时候应该见建大堆,还是小堆呢?

            相信许多人在看到要排升序的时候,可能第一反应的是建小堆,因为小堆中的第一个数是所有元素中最小的那个数,但是当我们建立小堆的时候,那我们的第二个小的数字如何取呢?

            且当我们将第一个元素排好之后,后面的元素的关系都不对了,就会形成兄弟变父子,父子叔侄变兄弟,那么我们可能还需要建一次堆,那么总体的时间复杂度为N*(N*logN),

            所以我们排升序需要建大堆,排降序需要建小堆。 

            而我们为什么可以这样子做呢?

            我们假设有一个数组我们已近将他建成大堆了,那么我们很明显知道根节点最大,那么我们就可以这样子做。

            将最大的根结点与最后一个数字进行交换,由于我们只是交换了根结点与最后一个元素,其他的结构没有动,所以就可以使用向下调整,然后在对前n-1个元素进行向下调整,整个的时间复杂度为logn。每次选一个大的,我们向将大的排在最后,循环进行就可以排成我们所需要的结果了。

            代码实现

    1. void HeapSort(int* a, int n)
    2. {
    3. //向下调整算法建堆,建大堆,排升序
    4. for (int i = (n-1-1)/2; i >=0; i--)
    5. {
    6. AdjustDown(a, n, i);
    7. }
    8. int end = n - 1;
    9. while (end > 0)
    10. {
    11. Swap(&a[0], &a[end]);
    12. AdjustDown(a, end, 0);
    13. end--;
    14. }
    15. }

                    

            也可以使用向上调整建堆进行堆排序:

            

    1. 假设排升序,建大堆
    2. //void HeapSort(int* a, int n)
    3. //{
    4. // //先建堆,用向上调整算法
    5. // for (int i = 1; i < n; i++)
    6. // {
    7. // AdjustUp(a, i);
    8. // }
    9. //
    10. // //将最后一个数与根节点交换
    11. // //在进行向下调整,循环执行
    12. // int end = n - 1;
    13. // /*while (end > 0)
    14. // {
    15. // Swap(&a[end], &a[0]);
    16. // AdjustDown(a, end, 0);
    17. // --end;
    18. // }*/
    19. //
    20. //
    21. //
    22. //
    23. //}

            本章完!!!

            感谢观看。

  • 相关阅读:
    有关直方图的常用操作
    Spring源码深度解析:七、bean的加载① - doGetBean概述
    rsync下行同步+inotify实时同步部署
    matlab 二维或三维三角剖分
    ChatGPT AIGC 实现数据分析可视化三维空间展示效果
    Effective C++条款03:尽可能使用const(Use const whenever possible)
    九、Linux高阶命令
    【Unity 实用工具篇】✨ | 编辑器扩展插件 Odin Inspector,进阶功能学习
    365天深度学习训练营-第6周:好莱坞明星识别
    阿里云产品试用系列-Serverless 应用引擎 SAE
  • 原文地址:https://blog.csdn.net/2201_75964502/article/details/133017420