zoukankan      html  css  js  c++  java
  • 优先级队列

    示例:

      1.夜间门诊

      2.多任务操作系统

      3.Huffman编码

    A 问题模式: 循优先级访问(call by priority),  按照这种优先级通过调度器将最高优先级的元素输入给服务端。

      

    B 操作接口:

    template <typename T>struct PQ {
        virtual void insert(T) = 0;  //按照优先级次序插入词条
        virtual T getMax() = 0 ; //取出优先级最高的词条 
        virtual T delMax()  = 0; //删除优先级最高的词条
    };
    

     Stack和Queue,都是PQ的特例——其优先级完全取决于元素的插入次序。对于栈而言,越是晚到的元素,其优先级越高,更早的出栈被处理。而队列相反。

    C 实现尝试:

     efficiency and cost

     C1  实现为Vector:  将每个新加入的元素放在向量的末尾。

         插入的时间成本:O(1)   获得元素值的时间成本是:O(n)   删除元素的时间成本是: O(n)

     C2  实现为Sorted Vector:  即优先级最大的元素放入向量的末尾。

        插入的时间成本:O(n)   获得元素值的时间成本是:O(1)   删除元素的时间成本是: O(1)

     C3  实现为List: 

        插入的时间成本:O(1)   获得元素值的时间成本是:O(n)   删除元素的时间成本是: O(n)

     C4  实现为Sorted List: 

       插入的时间成本:O(n)   获得元素值的时间成本是:O(1)   删除元素的时间成本是: O(1)

     C5  实现为BBST:  AVL Splay Red-black   杀鸡用了牛刀

         PQ = 1 x insert() + 0.5 x search() + 0.5 x remove() = 2/3 x BBST

     若只需要查找极值元素,则不必维护所有元素之间的全序关系,偏序足以。找一种实现简单、维护成本更低的实现方式。

    D最终尝试:  完全二叉堆

     以向量为形,以树形结构为神,完全二叉树

        逻辑上,等同于完全二叉树。物理上,直接借助向量实现。

     逻辑节点与物理元素依层次遍历次序彼此对应:

       

       完全二叉堆实现:

    template <typename T> class PQ_ComplHeap : pubilc PQ<T>, public Vector<T> {
    
    protected:
        Rank percolateDown(Rank n, Rank i); //下滤
        Rank percolateUp(Rank i); //上滤
        void heapify(Rank n); //Floyd 建堆方法
    public:
        PQ_ComplHeap(T* A, Rank n)  {//批量构造
           copyForm(A,0,n); heapify(n);     }
        
        void insert(T); //按照比较器确定的优先次序,插入词条
        T getMax() {return _elem[0] ; } //读取优先级最高的词条
        T delMax();  //删除优先级最高的词条
    };
         
    

       堆序性:

      H[i] <= H[Parent(i)] : 任何一个节点在数值上都不会超过它的父亲。所以根据优先级队列的特性,最大元必然位于根节点位置。所以H[0]是全局最大元素。

    template <typename T> T
    PQ_Complate<T> :: getMax() {return _elem[0];}
    

     E完全二叉堆插入(上滤操作):  

       E1:算法

      为插入词条e,只需要将e作为末元素接入向量:1.结构性自然保持。2.若堆序性也被破坏,则完成。否则只能是e与其父节点违反堆序性,则只需要将e与其父节点互换位置即可。

      

       

       如果交换后,仍然违反逆序性,则只需要将e与新的父节点互换位置即可。如此反复交换的过程满足单调性。这一过程即所谓的上滤过程。

       而一旦过程终止,则堆序性在整个完全二叉堆中得到了完全的恢复。     

      E2实例:

      

       E3实现:

         

       E4效率:

       O(logn),但其中swap操作包含3次赋值操作。所以效率为3*logn.

       所以当新插入节点与其父节点需要交换时,我们只需将新插入词条e先备份,然后将其不断上移,最后与其父亲交换。

        

     F全二叉堆删除:  

       F1:算法

      最大元素始终在堆顶,所以我们只需将其摘除。

      在摘除之后,完全二叉堆的堆序性被破坏,此时我们先将末尾元素放置于堆顶。

      最后采用下滤操作:  

        1)e与孩子中的大者互换,若堆序性完成,则结束。

        2)否则e与孩子中的最大者继续交换,若堆序性完成,则结束。

     

        F2:实例

        

       F3:实现

      

       F4:效率

      其效率与树的高度O(h)具有关系,所以其时间复杂度应为3* O(logn),同样在这里,常数项系数可以得到改进,可以先保存根节点,然后再下滤,最终只做一次实质上的交换操作(3次赋值)

      在上滤过程中,新插入的节点e只需要与其父节点进行比价。在下滤过程中,根节点要与其孩子做两次比较。这在二叉堆中并不至关重要,但是在多叉堆中就至关重要。

     G全二叉堆批量建堆:  

       G1:算法

        对于任给的n个元素,我们希望将它们构建成一个堆,这样一个过程称之为heapification

       G2:自上而下的上滤实现   

    PQ_ComplHeap(T*A, Rank n) {copyFrom(A,0,n); heapify(n);}
    template <typename T> void PQ_ComplHeap<T>: heapify(Rank n) {
      for (int i =1; i < n; ++i)  //按层次遍历次序逐一
         percolateUp(i); //经过上滤插入各个节点
    }    
    

       G3:自上而下的上滤的效率

        最坏情况下,即每个节点都需要上滤至根节点,所需成本线性正比于其深度。即便只考虑最底层(n/2个节点),深度均为O(logn),累计耗时O(nlogn),但是这么长的时间消耗足以全排序。

       G4:自下而上的下滤实现   

      

        G5:自下而上的下滤实例   

       

         G6:自下而上的下滤效率:

        最坏情况下,每个内部节点所需的调度时间,正比于其高度而非深度。高度是从下往上数。而深度与高度相反,从上往下数。

        对每个节点的高度求和,为O(n). 可以接受

        对每个节点的深度求和,为O(nlogn). 不可以接受  

        为什么会出现这种情况呢?因为深度是从上往下数的,而一个完全二叉堆,其处于下部的节点数较多,所以其累计的总和就更大。而以高度作为计算,位于完全二叉堆上部的节点较少,所以其累计的总和较少。

     

    H堆排序

      还记得选择排序嘛!

      我们始终将整个序列分为两部分:一部分是已经排好序的部分,另一部分是待排序部分。所以我们反复遍历待排序部分的最大值,O(n2)

      H1使用完全二叉堆来替代原有的待排序部分:

      在此,我们使用完全二叉堆,来替代原有的待排序部分。

      初始化:heapify(),O(n),建堆

      迭代:delMax(),O(logn),取出堆顶并调整复原

      不变性:H <= S

       H2在向量内完成堆排序:

      已排序的部分构成向量的后端,而与之互补的前缀恰好构成一个完全二叉堆,堆中的最大元必然是0号元素,而接下来与之兑换的x必然是已排序单元秩为-1的元素,所以我们首先取出最大的元素,然后用x取而代之。

      然后将备份的最大元植入x,然后对新的根节点作下滤调整。

      

     

       规整为两部分:交换和下滤,直至堆变空。除了交换需要常数个辅助空间外,除此我们不需要更多的辅助空间。

      H2堆排序实现:

     

      H2堆排序实例:

    I左式堆:

      I1堆合并:

        方法1:

          A.insert(B.delMax())   O(m*(logm + log(n+m))) = O(m*log(n+m))

          方法2:

          union(A,B).heapify(n+m) = O(m + n)

      I2堆单侧倾斜:

         保持堆序性,附加新条件。使得在堆合并过程中,只需要调整很少的部分节点,    O(logn)

         新条件  = 单侧倾斜: 节点分布偏向于左侧,合并操作只涉及右侧。

         如果真的存在这样一个堆,那么它断然不再是完全二叉堆,堆序性才是其本质性要求,而结构性则不是其本质要求,所以为了效率在此牺牲其结构性。

      I3空节点路径长度:从该节点到达一个没有两个孩子的节点的最短距离。NULL的NPL为-1,而左式堆的核心约束条件是左倾即任意节点的左0孩子的NPL大于等于右孩子的NPL。   

        npl(x) = 1 + npl(rc(x)),满足左倾性的堆,称之为左式堆 。左倾性与堆序性相容但是不矛盾。左式堆的子堆,必然是左式堆。左式堆倾向于更多节点分布于左侧分支。

        引入所有的外部节点:消除一度节点,转为真二叉树

        npl(x) = x到外部节点的最近距离

        npl(x) = 以x为根的最大满子树的高度、

        npl(Null) = 0; npl(x) = 1 + min(npl(lc(x), npl(rx(x))));

      I3右侧链:

        rChain(x) : 从节点x出发,向右不断前行所确立的那个分支就称之为右侧链。

        rChain(root)的终点,必然是全堆中最浅的外部节点。 

       I4左式堆的合并算法:左式堆不再满足结构性,物理结构不再保持紧凑性。

        以外部形式给出合并两个左式堆的函数:

      先将a的右式堆与b合并,合并所得的结果作为a的右子堆,然后比较a的左式堆和右式堆的npl值,如果比它小,则互换位置。

       I5左式堆的合并算法实现:

       I5左式堆的合并算法实例:

              https://next.xuetangx.com/learn/THU08091002048/THU08091002048/1158035/video/784339

      

       I5左式堆的插入算法:

        插入就是合并

       I5左式堆的删除算法:

       删除也是合并

     

  • 相关阅读:
    108. Convert Sorted Array to Binary Search Tree
    107. Binary Tree Level Order Traversal II
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    104. Maximum Depth of Binary Tree
    103. Binary Tree Zigzag Level Order Traversal
    102. Binary Tree Level Order Traversal
    系统和进程相关信息
    文件I/0缓冲
    系统编程概念(文件系统mount等函数的使用)
  • 原文地址:https://www.cnblogs.com/ccpang/p/12060906.html
Copyright © 2011-2022 走看看