• 深入浅出排序算法之堆排序


    目录

    1. 算法介绍

    2. 执行流程⭐⭐⭐⭐⭐✔

    3. 代码实现

    4. 性能分析


    1. 算法介绍

    堆是一种数据结构,可以把堆看成一棵完全二叉树,这棵完全二叉树满足:任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子小,则这样的堆叫作大顶堆;若父亲小孩子大,则这样的堆叫作小顶堆。

    根据堆的定义知道,代表堆的这棵完全二叉树的根结点的值是最大(或最小)的,因此将一个无序序列调整为一个堆,就可以找出这个序列的最大(或最小)值,然后将找出的这个值交换到序列的最后(或最前),这样,有序序列关键字增加1个,无序序列中关键字减少1个,对新的无序序列重复这样的操作,就实现了排序。这就是堆排序的思想。

    堆排序中最关键的操作是将序列调整为堆。整个排序的过程就是通过不断调整,使得不符合堆定义的完全二叉树变为符合堆定义的完全二叉树。

    2. 执行流程⭐⭐⭐⭐⭐✔

    建堆是先从自下而上,从右往左建

    初始堆的每一个结点都要满足堆的定义,也就是父节点的值大于左右孩子结点的值!!!

    选出最大值,是将根结点和最后一个结点互换,然后继续构建大顶堆!!!

    ⭐⭐⭐堆顶和最后一个元素交换,才算一趟,也是该趟的最终序列结果!!!

    建堆和排序结果是两个阶段,但同属于一趟中。

    图示如下:

    3. 代码实现

    为了三个步骤:

    步骤一:先建堆(大根堆或者小根堆)

    步骤二:交完堆顶和最后一个元素,然后堆的大小减一

    步骤三:向下调整堆

    步骤一只需实现一次,步骤二和步骤三循环执行,得到最终的有序序列。

    1. //开始排序:堆排序分为三个功能 ①开始建堆,②交换,③向下调整,重复②和③步
    2. public static void heapSort(int[] array,int len){
    3. int end = len - 1;//确定最后一个结点的下标
    4. createHeap(array);//建堆
    5. //当只剩下一个结点的时候,就不需要交换
    6. while(end > 0){
    7. //交换
    8. swap(array,0,end);
    9. //向下调整
    10. shiftDown(array,0,end);
    11. //调整完一个结点,下一个
    12. end--;
    13. }
    14. }
    15. //交换数据
    16. public static void swap(int[] array,int i,int j){
    17. int tmp = array[i];
    18. array[i] = array[j];
    19. array[j] = tmp;
    20. }
    21. //堆排序(大根堆)
    22. //从上往下建堆,所以先找父节点,再找孩子结点
    23. public static void createHeap(int[] array){
    24. for(int parent = (array.length - 1 - 1) / 2;parent >= 0;parent--){
    25. shiftDown(array,parent,array.length);
    26. }
    27. }
    28. //向下调整
    29. public static void shiftDown(int[] array,int parent,int len){
    30. //定义一个记录孩子下标的变量(左孩子)
    31. int child = 2 * parent + 1;
    32. //判断父节点和孩子结点的大小,至少左孩子要存在
    33. while(child < len){
    34. //比较左右孩子
    35. if((child + 1) < len && array[child] < array[child + 1]){
    36. child++;
    37. }
    38. //判断父节点和孩子节点
    39. if(array[child] > array[parent]){
    40. swap(array,child,parent);
    41. parent = child;
    42. child = 2 * parent + 1;
    43. }else{
    44. break;
    45. }
    46. }
    47. }
    1. public static void main(String[] args) {
    2. int[] a = {5,4,3,2,1};
    3. Sort.heapSort(a, a.length);
    4. for (int x : a) {
    5. System.out.print(x + " ");
    6. }
    7. }

    4. 性能分析

    时间辅助度空间复杂度
    O(N*logN)O(1)
    数据不敏感数据不敏感

    稳定性:不稳定。

    来上解析,怎么计算这个时间复杂度。

    (1)步骤一的时间复杂度:首先知道有N个结点开始建堆,这个时间复杂度就是O(N),大家可以去看看这篇文章,里面有讲建堆的时间复杂度。链接如下:

    数据结构——堆、堆排序和优先级队列(代码为Java版本)

    (2)步骤二和步骤三循环的时间复杂度:那么我第一个结点交换时,需要向下调整为log(N - 1)层;交换第二个结点后,需要向下log(N - 2),接下来就是log(N - 3),log(N - 4),……,log1。所以总的调整次数是log(N - 1) + log(N - 2) + log(N - 3) + log(N - 4) + …… + log1 = log((N - 1)!)。

    我们可以在网上看到堆排序的时间复杂度是O(N*logN),这是堆排序的大致估算(我们算时间复杂度都是算个大概),其实log((N - 1)!) 约等于 NlogN。下面是我的证明结果:

    ① 使用夹逼准则证明:

    先求上限:\log \left ( n!\right ) = \sum_{i = 1}^{n}\log \left ( i \right )\leqslant \sum_{i=1}^{n}\log \left ( n \right )=\log n^{n}=O\left (n\log n \right )

    再求下限:

    因为 n! \geqslant \left ( \frac{n}{2} \right )^{\frac{n}{2}}

    所以 \log \left ( n! \right )\geqslant \log \left ( \frac{n}{2} \right )^{\frac{n}{2}}= \frac{n}{2}\log \frac{n}{2}= \frac{n}{2}\log n-\frac{n}{2}\log 2

    当 n\geqslant 4 时,\frac{n}{2}\log 2=\frac{1}{4}n\log 4\leqslant \frac{1}{4}n\log n               

    ② 则有:

    \log \left ( n! \right )\geqslant \frac{n}{2}\log n-\frac{n}{2}\log 2\geqslant \frac{n}{2}\log n-\frac{1}{4}n\log n=\frac{1}{4}n\log n\approx \Omega \left ( n\log n \right )     

    ③结论:\log \left ( n! \right ) 既是 n\log n 的低阶函数,又是 n\log n 的高阶函数,因此是 n\log n 的同阶函数!

    (3)由于上面的证明步骤,我们可以知道堆排序的时间复杂度是  O\left ( n\log n \right ) 。

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               

  • 相关阅读:
    Jenkins 2.426.3新版设置中文
    JavaScript系列从入门到精通系列第十八篇:JavaScript中的函数作用域
    分布式和可再生系统建模(simulink)
    C# break 和 return的区别
    Element中Tree树结构组件中实现Ctrl和Shift多选
    kerberos认证相关概念和流程
    C专家编程 第3章 分析C语言的声明 3.3 优先级规则
    Qt 窗口常用位置API函数 & 绘图原理 & 双缓冲机制 总结
    网络安全(补充)
    JAVA小游戏 “拼图”
  • 原文地址:https://blog.csdn.net/ANNE_fly/article/details/134056804