• 堆排序的实现原理


    一、什么是堆排序

            堆排序就是将待排序元素以一种特定树的结构组合在一起,这种结构被称为堆。

            堆又分为大根堆和小根堆,所谓大根堆即为所有的父节点均大于子节点,但兄弟节点之间却没有什么严格的限制,小根堆恰恰相反,是所有的根节点均小于子节点。

            所以为了能够实现堆排序,第一个步骤就是将待排序的元素建成堆的形式,其次就是将建好的大根堆(小根堆)与堆的最后一个元素交换,然后再对新的堆进行向下的调整,但是在调整过程中,所有经过交换过的堆底元素不再进行新的调整,直到将倒数第二个元素调整完毕后结束。

            所以说堆排序虽说效率较高,但是它的算法步骤却不如其他排序那么明了,需要将它的每一个算法步骤了解清楚后,才能清晰的解析出来。

    二、堆排序的算法步骤

            在排序家族中,堆排序是一种效率比较高的方法,它的 时间复杂度为O(nlogn),空间复杂度为O(1),它的主要排序步骤为:建堆、交换堆顶、堆底元素再向下调整。

            但是在此之前,我们需要先解析出两个分支算法,分别是向下调整和向上调整。

            顾名思义,向上(下)调整分别是从堆底(顶)为起始向堆顶(底)进行调整,目的则是严格维护堆的结构不被破坏。

            在本文的演示中,我们暂且以大根堆为例。

    2.1向下调整

            首先,我们以【2,6,3,0,7】为例进行演示。

            我们先按照顺序构建堆,如下所示:

            然后我们建立两个int类型的变量parent和child,分别指向2和它的子节点,这里子节点的公式为(parent*2+1)。

            接下来就是要将parent所指的元素和child所指的元素进行比较,如果parent所指元素小于child所指元素则进行交换,再更新两个变量的位置,如果child所指元素不大于parent所指元素,则跳出循环。

                    这样,一趟的向下调整就完成了,下面我们用代码实现:

    1. //向下调整
    2. void Modify_down(int parent , int end)
    3. {
    4. int child = parent * 2 + 1;
    5. while (child <= end)
    6. {
    7. if (child + 1 < end && _v[child] < _v[child + 1])
    8. {
    9. child++;
    10. }
    11. if (_v[child] > _v[parent])
    12. {
    13. swap(_v[parent], _v[child]);
    14. parent = child;
    15. child = child * 2 + 1;
    16. }
    17. else
    18. break;
    19. }
    20. }

    2.2向上调整

            向上调整和向下调整有所不同,需要先找出其孩子节点中最大的那个,比较之后再进行交换操作,以【2,6,7,0,3】为例。

            调整过程中,计算父节点的计算方法为【(child-1)/2】,然后比较兄弟节点,找出最大的那个,如果孩子节点大于父节点,就进行交换,然后更换parent和child的下标位置,如果子节点小于父节点就跳出循环。代码如下:

    1. //向上调整
    2. void Modify_up(int child)
    3. {
    4. int parent = (child - 1) / 2;
    5. while (child > 0)
    6. {
    7. if (_v[child] > _v[parent])
    8. {
    9. swap(_v[parent], _v[child]);
    10. child = parent;
    11. parent = (child - 1) / 2;
    12. }
    13. else
    14. break;
    15. }
    16. }

    2.3堆排序

            完成两个核心的算法后,我们最后只需将堆排序归纳一下即可。

    1、建堆,将堆调整至合法。

    1. //向上调整
    2. void Modify_up(int child)
    3. {
    4. int parent = (child - 1) / 2;
    5. while (child > 0)
    6. {
    7. if (_v[child] > _v[parent])
    8. {
    9. swap(_v[parent], _v[child]);
    10. child = parent;
    11. parent = (child - 1) / 2;
    12. }
    13. else
    14. break;
    15. }
    16. }
    17. //建堆
    18. for (int i = 1; i < _v.size(); i++)
    19. {
    20. Modify_up(i);
    21. }

    2、交换堆顶和堆底元素,然后再进行向下调整堆,在这里对于堆底的下标我们以变量“end”来控制,当end<=0时,则跳出循环。

    1. //向下调整
    2. void Modify_down(int parent , int end)
    3. {
    4. int child = parent * 2 + 1;
    5. while (child <= end)
    6. {
    7. if (child + 1 < end && _v[child] < _v[child + 1])
    8. {
    9. child++;
    10. }
    11. if (_v[child] > _v[parent])
    12. {
    13. swap(_v[parent], _v[child]);
    14. parent = child;
    15. child = child * 2 + 1;
    16. }
    17. else
    18. break;
    19. }
    20. }
    21. int end = _v.size() - 1;
    22. while (end > 0)
    23. {
    24. swap(_v[0], _v[end]);
    25. end--;
    26. Modify_down(0, end);
    27. }

    三、堆排序完整代码

    1. class heapsort
    2. {
    3. public:
    4. heapsort(vector<int>& v)
    5. :_v(v)
    6. {}
    7. void heap_sort()
    8. {
    9. for (int i = 1; i < _v.size(); i++)
    10. {
    11. Modify_up(i);
    12. }
    13. int end = _v.size() - 1;
    14. while (end > 0)
    15. {
    16. swap(_v[0], _v[end]);
    17. end--;
    18. Modify_down(0, end);
    19. }
    20. }
    21. void Print()
    22. {
    23. for (int i = 0; i < _v.size(); i++)
    24. {
    25. cout << _v[i] << " ";
    26. }
    27. }
    28. protected:
    29. //向上调整
    30. void Modify_up(int child)
    31. {
    32. int parent = (child - 1) / 2;
    33. while (child > 0)
    34. {
    35. if (_v[child] > _v[parent])
    36. {
    37. swap(_v[parent], _v[child]);
    38. child = parent;
    39. parent = (child - 1) / 2;
    40. }
    41. else
    42. break;
    43. }
    44. }
    45. //向下调整
    46. void Modify_down(int parent , int end)
    47. {
    48. int child = parent * 2 + 1;
    49. while (child <= end)
    50. {
    51. if (child + 1 < end && _v[child] < _v[child + 1])
    52. {
    53. child++;
    54. }
    55. if (_v[child] > _v[parent])
    56. {
    57. swap(_v[parent], _v[child]);
    58. parent = child;
    59. child = child * 2 + 1;
    60. }
    61. else
    62. break;
    63. }
    64. }
    65. private:
    66. vector<int> _v;
    67. };

    四、、堆排序的适用场景

            堆排序适用于关键字较多的情况,如:在几亿个关键字中找出前十个最大的数据,我们只需建立小根堆,然后依次循环十次就能得到想要的数据了。

  • 相关阅读:
    计算机基础知识45
    【通信原理笔记】【二】随机信号分析——2.1 随机过程与其相关函数
    二十一、MySQL(多表)内连接、外连接、自连接实现
    【JAVA】栈和队列(Part1 栈)
    HTML制作一个汽车介绍网站【大学生网页制作期末作业】
    【JavaSE】面向对象——属性和方法
    【Spring Security 系列】(四)高层级组件
    2101. 引爆最多的炸弹;752. 打开转盘锁;1234. 替换子串得到平衡字符串
    经典论文回顾:Decomposing Images into Layers via RGB-space Geometry
    CrownCAD 2022 特征操作及编辑
  • 原文地址:https://blog.csdn.net/qq_50309255/article/details/139882998