zoukankan      html  css  js  c++  java
  • 堆排序

    算法思想

    • 大根堆
      堆其实是一种近似完全二叉树的数据结构,当树中所有的父节点的值都大于它的子节点的值时,这样的二叉树又
      被称为大根堆;

    • 堆的存储方法
      堆(二叉树)既可以使用数组表示,也可以使用链式节点表示,但是堆排序适用于对数组进行排序,无法对无序的
      链式二叉树进行排序;

    • 数组表示二叉树时数组中元素的节点关系

      • 数组下标从0开始
        设有数组A[n],当数组下标从0开始时,从A[n/2]到A[n-1]均为叶节点;当下标i小于n/2时,数组元素A[i]的
        左孩子为A[i+1],右孩子为A[i+2],但元素A[n/2-1]无右孩子
        例:
        20190816105854.png
      • 数组下标从1开始
        设有数组A[n],当数组下标从1开始时,从A[n/2+1]到A[n]均为叶节点;当下标i小于n/2+1时,数组元素A[i]
        的左孩子为A[i],右孩子为A[i+1],但元素A[n/2]无右孩子
        例:
        20190816110057.png
    • 保持大根堆性质
      设数组A[n],对于任意下标元素Ai,其左右子树都是符合大根堆定义,但是A[i]有可能小于
      左右孩子节点,此时要取左右孩子节点中最大的节点A[max]与A[i]进行交换,然后在对A[max]进行同样的操作
      直到A[i]的子树符合大根堆的定义;
      例:
      20190816130511.png
      可以看出保持大根堆性质的调整是一个自"根"向"叶"的过程
      代码实现:(数组下标从0开始)

      void KeepMaxHeapify(int * pAry, int nStartIndex, int nHeapSize)
      {
        int nIndexOfLeftChild = 2 * nStartIndex + 1;
        int nIndexOfRightChild = nIndexOfLeftChild + 1;
        int nIndexOfLagestChild = nStartIndex;
        if (nIndexOfLeftChild < nHeapSize &&
            pAry[nIndexOfLeftChild] > pAry[nStartIndex])
        {
          nIndexOfLagestChild = nIndexOfLeftChild;
        }
      
        if (nIndexOfRightChild < nHeapSize &&
            pAry[nIndexOfRightChild] > pAry[nIndexOfLagestChild])
        {
          nIndexOfLagestChild = nIndexOfRightChild;
        }
      
        if (nIndexOfLagestChild != nStartIndex)
        {
          int nTemp = pAry[nStartIndex];
          pAry[nStartIndex] = pAry[nIndexOfLagestChild];
          pAry[nIndexOfLagestChild] = nTemp;
          KeepMaxHeapify(pAry, nIndexOfLagestChild,nHeapSize);
        }
      }
      

       
      时间复杂度分析:
      这个算法的递归调用前代码的时间复杂度可以看作常量,其时间复杂度为T(n)=T(x)+Θ(1),其中x表示某个根节
      点子树中的节点个数;堆是一个近似的完全二叉树,假设处于底层半满的最坏情况下,设树高为k,节点总数为n,
      则n=2(k)-1+2(k-1)=3*2^(k-1)-1,完全二叉树根节点的左子树中节点总个数,与等高的满二叉树根节点的
      左子树节点总个数相同,所以当前完全二叉树根节点左子树节点总个数为2^k-1,此时当前完全二叉树根节点的
      左子树的节点总个数占整个完全二叉树节点个数的比例为(2k-1)/(3*2(k-1)-1)=2/3,所以在这种最差情况
      下完全二叉树的左子树的节点个数为2n/3,假设这种最差的情况发生在完全二叉树(堆)中的任意节点的子树中,
      这也就意味着表达式中的x=2n/3,所以时间复杂度T(n)=T(2n/3)+Θ(1),则T(n)=O(lgn);根据二叉树的性质可以知
      高度为k的二叉树节点数最多为n=2^k-1个节点,可以推出高度k至少为lg(n+1),lg(n+1)和lg(n)相差无几,所
      以可以认为k=lg(n),那么可以推出T(n)=O(lgn)=O(k);

    • 将无序数组建转换成大根堆
      从数组中下标最大的非叶节点开始调整,直至根节点被调整后,大根堆建立完成,对于下标从0开始的数组,
      从下标为n/2-1的节点开始调整,直至调整到下标为0的节点(根节点),对于下标从1开始的数组,从下标n/2开始
      直至调整到下标为1的节点。

      例:
      20190816215259.png
      元素共10个,下标最大的非叶节点的下标为4,调整完成后如下:
      20190816214103.png
      接着对下标为3的根节点进行调整:
      20190816214337.png
      对下标为2的节点进行调整:
      20190816214451.png
      对下标为1的节点进行调整:
      20190816214729.png
      调整后右子树不满足大根堆定义,则调整右子树:
      20190816214927.png
      对下标为0的节点进行调整:
      20190816215125.png
      调整后左子树不满足大根堆定义,则调整左子树:
      20190817152748.png
      20190817152711.png

      代码实现:

      void BuildMaxHeap(int * pUnSortAry, int nSize)
      {
        int nMaxIndexOfNonleaf = nSize / 2 - 1;
        for (int nIndex = nMaxIndexOfNonleaf; nIndex >= 0; nIndex--)
        {
          KeepMaxHeapify(pUnSortAry, nIndex, nSize);
        }
      }
      

       
      时间复杂度分析:
      对于一个有n个节点的堆,高度为k的节点,最多有n/2^(k+1)个,而堆的高度为lgn,所以BuildMaxHeap的时间
      复杂度为为:
      20190817145717.png
      可推得为O(n);

    • 堆排序
      从大根堆的性质可以看出,根节点是当前数组中的最大值,为了进行升序排列,将根节点与数组中最后一个元素
      进行交换,然后将这个最大节点从堆中剔除,在从新根节点开始进行调整,调整完成后在将新的根节点与数组中的倒数第二个节点交换,然后将这个最大节点从堆中剔除并调整再交换,......,直至堆中只剩根节点
      例:
      20190817153237.png
      将根节点16与数组中下标为9的节点交换后:
      20190817153325.png
      由上图看出,此时以1为根节点的二叉树不满足大根堆定义,则调整:
      20190817154038.png
      将根节点14与下标为8的节点交换后:
      20190817154236.png
      由上图看出,此时以1为根节点的二叉树不满足大根堆定义,则调整:
      20190817155042.png
      将根节点10与下标为7的节点交换后:
      20190817155213.png
      由上图看出,此时以2为根节点的二叉树不满足大根堆定义,则调整:
      20190817155454.png
      将根节点9与下标为6的节点交换后:
      20190817155654.png
      由上图看出,此时以2为根节点的二叉树不满足大根堆定义,则调整:
      20190817155817.png
      将根节点8与下标为5的节点交换后:
      20190817160017.png
      由上图看出,此时以1为根节点的二叉树不满足大根堆定义,则调整:
      20190817160155.png
      将根节点7与下标为4的节点交换后:
      20190817160420.png
      由上图看出,此时以2为根节点的二叉树不满足大根堆定义,则调整:
      20190817160539.png
      将根节点4与下标为3的节点交换后:
      20190817160853.png
      由上图看出,此时以1为根节点的二叉树不满足大根堆定义,则调整:
      20190817161040.png
      将根节点3与下标为2的节点交换后:
      20190817161328.png
      由上图看出,此时以1为根节点的二叉树不满足大根堆定义,则调整:
      20190817161407.png
      将根节点2与下标为1的节点交换后:
      20190817161622.png
      此时堆中只剩一个节点,数组已经排序完成

      实现代码:

      void HeapSort(int *pUnsortAry, int nSize)
      {
        BuildMaxHeap(pUnsortAry, nSize);
        for (int nLastIndex = nSize - 1; nLastIndex > 0; nLastIndex--)
        {
          int nTemp = pUnsortAry[nLastIndex];
          pUnsortAry[nLastIndex] = pUnsortAry[0];
          pUnsortAry[0] = nTemp;
          KeepMaxHeapify(pUnsortAry, 0, nLastIndex);
        }
      }
      

       
      时间复杂度分析:
      BuildMaxHeap时间复杂度为O(n),执行n-1次KeepMaxHeapify的时间复杂度为(n-1)lgn,所以总的时间复杂度
      T(n)=O(n)+(n-1)lgn=nlgn;

  • 相关阅读:
    .net core 在 Docker 上的部署
    js 运算的内置函数
    vux 项目的构建
    微信小程序开发资料
    HttpClient 调用WebAPI时,传参的三种方式
    jsplumb 中文教程
    MyEclipse最新版-版本更新说明及下载
    如何用VSCode调试Vue.js
    vs2017开发Node.js控制台程序
    Objc的底层并发API
  • 原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11369114.html
Copyright © 2011-2022 走看看