zoukankan      html  css  js  c++  java
  • priority_queue剖析

    priority_queue又叫优先队列,其实应该算是一个容器适配器,存储结构默认设置为vector,特点是每次使用pop()都会弹出最大优先级的那个元素,原因就是这个容器的排序准则是维持一个大根堆(1.根节点要大于等于左右节点,但是左右节点没有要求谁大谁小。2.元素的逻辑模型应该为一颗完全二叉树),在源码中可以窥见一二

     数据元素类型为T,默认使用vector存储元素,排序准则默认为less

    priority_queue最重要的两个函数pop和push定义如下:

      void push(const value_type& x) {
        __STL_TRY {
          /*压入一个新值放入最后*/
          c.push_back(x); 
          /*堆化处理*/
          push_heap(c.begin(), c.end(), comp);
        }
        __STL_UNWIND(c.clear());
      }
      void pop() {
        __STL_TRY {
          /*将根部元素和最后一片叶子交换(此时最后一个元素为根值)然后把除去最后一个元素的剩余元素做重新堆化处理*/
          /*注意这里最大的元素还在最后一个位置*/
          pop_heap(c.begin(), c.end(), comp);
          /*真正弹出元素*/
          c.pop_back();
        }
        __STL_UNWIND(c.clear());
      }

    当插入元素的时候,会先调用底部容器的push_back方法往最后一个位置插入元素,然后调用push_heap重新堆化

    堆的性质:当节点有两个子节点时,假设当前下边为i(i不等于0),则左孩子下标为2i,右边孩子下标为2i+1;当一个点不是顶点时,其父节点下标为n/2。

    但是stl实作里顶点下标是0,使用情况稍微复杂了点,知道什么意思就行。

    template <class RandomAccessIterator, class Distance, class T>
    void __push_heap(RandomAccessIterator first, Distance holeIndex,
                     Distance topIndex, T value) {
      /*找出待检查点的父节点位置*/
      /*待检查点开始从下往上检查*/
      Distance parent = (holeIndex - 1) / 2;
      /*未到达顶端,且父节点小于插入点*/
      while (holeIndex > topIndex && *(first + parent) < value) {
        /*父节点移到子节点(值交换)*/
        *(first + holeIndex) = *(first + parent);
        /*与父节点交换位置*/   
        holeIndex = parent;
        /*重新计算父节点位置*/
        parent = (holeIndex - 1) / 2;
      }   
      /*设置目标值,完成插入操作*/ 
      *(first + holeIndex) = value;
    }
    
    template <class RandomAccessIterator, class Distance, class T>
    inline void __push_heap_aux(RandomAccessIterator first,
                                RandomAccessIterator last, Distance*, T*) {
      __push_heap(first, Distance((last - first) - 1), Distance(0), 
                  T(*(last - 1)));
    }
    
    template <class RandomAccessIterator>
    inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
      __push_heap_aux(first, last, distance_type(first), value_type(first));
    }

     删除元素:优先队列每次删除的都是优先级最大(或最小)的那个节点,stl的priority_queue默认是大根堆规则形成的vector,下边不再多说;

    pop的源码如下,这里选用的是compare为默认值的情况(less)

    /*
      first 首迭代器
      holeIndex 目标索引
      len 待检查长度
      value 原尾部元素
    */
    template <class RandomAccessIterator, class Distance, class T>
    void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
                       Distance len, T value) {
      /*先记下开始检查的节点下标*/
      /*pop操作的话就是根部下标,因为要从顶点往下检查是否有子节点大于父节点*/
      Distance topIndex = holeIndex;
    
      /*
        1.更换顶端节点后重新堆化
      */
    
      /*先计算根节点的大儿子节点*/
      Distance secondChild = 2 * holeIndex + 2;       //(1)
      /*下标没超过堆长度*/
      while (secondChild < len) {
        /*左右子节点去比较,选出大的那一个,secondChild就是大的那一个的下标*/
        if (*(first + secondChild) < *(first + (secondChild - 1)))
          secondChild--;
        /*父节点值与大儿子值交换*/
        *(first + holeIndex) = *(first + secondChild);
        /*下一层检查,父下标变大儿子下标*/
        holeIndex = secondChild;
        /*计算新的大儿子下标
          为什么这里和(1)的计算方式不一样呢,上边是+2,这里是+1
          其实就是因为要兼容上边(1)的时候holeIndex是0的情况,使得每次secondChild默认值是右孩子
        */
        secondChild = 2 * (secondChild + 1);          //(2)
      }
      /*没有右子节点,只有左子节点*/
      if (secondChild == len) {
        *(first + holeIndex) = *(first + (secondChild - 1));
        holeIndex = secondChild - 1;
      }
    
      /*
        2.将原最后一个元素放入合适位置
      */
      __push_heap(first, holeIndex, topIndex, value);
    }
    
    template <class RandomAccessIterator, class T, class Distance>
    inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                           RandomAccessIterator result, T value, Distance*) {
      /*最后一个元素设置为根部的值*/
      *result = *first;
      /*这里的last已经是指向最后一个节点了,不是原来的最后一个节点的下一个节点*/
      /*所以Distance(last-first)是排除最后一个元素后的剩余系列长度*/
      __adjust_heap(first, Distance(0), Distance(last - first), value);
    }
    
    template <class RandomAccessIterator, class T>
    inline void __pop_heap_aux(RandomAccessIterator first,
                               RandomAccessIterator last, T*) {
      __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first));
    }
    
    template <class RandomAccessIterator>
    inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
      __pop_heap_aux(first, last, value_type(first));
    }

    要想深入理解还是得亲手调试几遍,迭代器改成指针,长度什么的改成int,用int数组调试就够了,复制到windows下用vs调(注意:进行pop操作前元素序列必须是有效大根堆)

     关于优先队列还有两个函数可以谈,分别是它的sort排序函数和将一个序列堆化的函数made_heap

    sort_heap:前边讲过每次pop会把优先级最高的放置于尾部,调用while循环pop即可形成一个递增序列

    template <class RandomAccessIterator>
    void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
      /*从尾部调用pop_heap直到根部*/
      while (last - first > 1) pop_heap(first, last--);
    }

    made_heap:将一个序列变成大根堆

    template <class RandomAccessIterator, class T, class Distance>
    void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*,
                     Distance*) {
      /*一个或者0个元素不用排*/
      if (last - first < 2) return;
      /*长度*/
      Distance len = last - first;
      /*找第一个需要重排的节点下标*/
      /*parent这个命名有些歧义,还是较holeIndex较好*/
      Distance parent = (len - 2)/2;
        
      while (true) {
        /*以parent开始检查,直到顶点*/
        /*以parent这个点为每个小堆的顶点去做堆化处理*/
        __adjust_heap(first, parent, len, T(*(first + parent)));
        if (parent == 0) return;
        parent--;
      }
    }

    参考:

    stl源码剖析4.8priority queue

    数据结构于算法第二版 陈卫卫,王庆瑞第五章堆排序

    大话数据结构第九章堆排序

    关于算法还是一句话,调试是关键,硬看不推荐,实际上理解了它的运行模式也差不多了,调一下加深印象,脑袋要炸了,过两天可能就忘了,人之常情,作此记录以待需要可以快速拾起罢了。

  • 相关阅读:
    C# 数组 随机 排序
    安全防护之加盐慢哈希加密
    NLog的介绍使用
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance(xsi:schemaLocation详解)
    如何计算时间复杂度(转)
    ppp协议介绍(转)
    Netlink 介绍(译)
    TIME_WAIT状态的一些总结
    带头结点单链表的翻转(递归)
    压缩前端文件(html, css, js)
  • 原文地址:https://www.cnblogs.com/Cxiangyang/p/13928462.html
Copyright © 2011-2022 走看看