C++ STL實現的優先隊列( priority_queue )

本文參考的源碼版本:gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)。

priority_queue 本質是容器適配器,它對內部容器的元素有自己的管理方式,而 priority_queue 實際維護的是一個二叉堆。STL中 priority_queue 的操作是基于完全二叉樹,使用隨機訪問迭代器訪問元素,二叉堆在創建時按照層序遍歷的順序將數據放入容器中,因此創建 priority_queue 時使用的容器需要具有隨機訪問的特性。

priority_queue 是一個類模板,有三個模板參數,第一個數據的類型,第二個是容器的類型,默認為 vector ,第三個是用于比較操作的函數對象,默認是 less ,即小于比較。

template<typename _Tp, typename _Sequence = vector<_Tp>,typename _Compare  = less<typename _Sequence::value_type> >class priority_queue {};

完全二叉樹

如果對滿二叉樹的結點進行編號, 約定編號從根結點起, 自上而下, 自左而右。則深度為 k 的, 有 n 個結點的二叉樹, 當且僅當其每一個結點都與深度為 k 的滿二叉樹中編號從 1 至 n 的結點一一對應時, 稱之為完全二叉樹,換言之如果只刪除了滿二叉樹最底層最右邊的連續若干結點形成的樹稱為完全二叉樹。滿二叉樹是完全二叉樹的特列。完全二叉樹具有以下特性:

  • 具有 n 個節點的完全二叉樹的深度為:

k=[log2n]floor+1k = [log_2n]_{floor} + 1 k=[log2?n]floor?+1

  • iii 個結點的編號范圍為: 1≤i≤n1 ≤ i ≤ n1in ;
  • 如果 i=1i = 1i=1iii 為根結點,無雙親;如果 i>1i > 1i>1 ,結點iii 的雙親結點為:

[i/2]floor[i/2]_{floor}[i/2]floor?

  • 如果2i<=n2i<=n2i<=n ,結點i的左孩子為結點 2i2i2i;否則無左孩子

  • 如果 2i+1<=n2i+1<=n2i+1<=n,結點i的右孩子為結點 2i+12i+12i+1,否則無右孩子

  • 結點 iii 所在的層次為:

    ki=[log2i]floor+1k_i = [log_2i]_{floor} + 1 ki?=[log2?i]floor?+1

  • 如果 i>1i > 1i>1, iii 為奇數時,結點 iii 為右子結點; iii 為偶數時,結點 iii 為左子結點

STL的 priority_queue 在實現的時候保證了堆頂的元素始終位于容器的第一個位置,相當于二叉堆的完全二叉樹的位置是從 0 開始計數,與完全二叉樹的計數有細微差別。

a7ee5383dc0877a65db01ce4030da6a7.png

堆有序化( reheapifying ) 中的上浮和下沉

當在二叉堆的最后一個位置插入新的元素時,新加入的元素可能會破壞堆的有序性,此時需要對新加結點與其父結點進行比較,如果大于父結點的值,那么就需要交換新加結點和父結點,如此重復比較,直到不再比父結點大時終止。這個過程就是堆有序化過程中的上浮操作。

當在移除二叉堆的堆頂元素時,被移除的元素破壞了堆的有序性,此時需要對堆頂的兩個子結點中選擇較大的值作為新的堆頂,而被選擇的子結點則不能再作為子結點,則需繼續比較其兩個子結點,如此重復比較,直到到達堆底,或者兩個子結點的值都比根結點小。這個過程就是堆有序化過程中的下沉操作。

#include <vector>
#include <random>
#include <chrono>using namespace std;class PriorityQueue {
public:void Push(int value) {v_.emplace_back(value);push_hole(v_.size() - 1,value);}void Pop() {int back = v_.back();int size = v_.size();int hole = 0;int right_child = 2 * (hole + 1); // 計算右孩子結點的位置while (right_child < size) {// 左孩子小于右孩子if (v_[right_child - 1] < v_[right_child]) {v_[hole] = v_[right_child];hole = right_child;} else { // 左孩子大于等于右孩子v_[hole] = v_[right_child - 1];hole = right_child - 1;}right_child = 2 * (hole + 1);}push_hole(hole, back);v_.pop_back(); // 最后才從容器中刪除元素}int Top() const {return v_[0];};;int Empty() const {return v_.empty();}friend ostream &operator<<(ostream &os, const PriorityQueue &rhs) {for (const auto &v: rhs.v_) {os << v << " ";}os << endl;return os;}private:void push_hole(int hole,  int value) {int parent = (hole - 1) / 2; // 根據完全二叉樹的特性計算父結點的位置// 父結點比新加的值小,交換父結點和新加入的值while (parent >= 0 && v_[parent] < value) {swap(v_[parent], v_[hole]);hole = parent;parent = (hole - 1) / 2;}v_[hole] = value;}private:vector<int> v_;
};int main() {PriorityQueue pq;default_random_engine  e(chrono::system_clock::now().time_since_epoch().count());uniform_int_distribution<int> d(1,1000);for (int i = 0; i < 100; i++) {pq.Push(d(e));}
//    vector<int> v{11,10,3,20,15,15,1,17,9,2,0,
//                  12,25,11,12,30,0,0,0,60,15,
//                  22,63,77,60,1,1,2,6,7,4,2,8};
//    for (int val : v) {
//        pq.Push(val);
//    }cout << "Container: " << pq;cout << "Top: ";while (!pq.Empty()) {int top = pq.Top();pq.Pop();cout << top << " ";}return 0;
}

priority_queue 的 push 操作

priority_queuepush 操作本質上就是二叉堆有序化的上浮,在真正的 push 前會先在容器的最后一個位置挖一個洞,以作為后續重排容器元素的中轉位置。push 操作步驟如下:

  1. 先將要添加的數據添加到容器最后一個位置,相當于挖個洞;
  2. 然后對容器進行重排操作,重排操作的過程大致如下:
    • 初始時,洞的位置就是容器最后一個位置;
    • 如果容器本身是空的,那么在洞的位置添加新的元素后,該元素就堆頂的元素;
    • 如果容器本身不是空的,那么就通過洞的位置找到其父結點的位置,然后使用父結點的值與新加入的值進行比較,如果父結點的元素值小于新加入的值,那么就將父結點的值移到挖的洞中,這樣中間位置就形成了一個洞,此時更新洞的位置。然后重復操作,直至洞的位置不再更新。
  3. 將新加入的值添加到洞中,完成一次 push 操作。

示例:

Push.gif

下面是 push 重排的關鍵源碼部分,第一個參數是容器的首元素迭代器,第二個參數是洞的初始位置,第三個參數是堆頂,即容器第一個位置,第四個參數是新添加的值,第五個參數是用于進行比較操作的函數對象。從源碼能夠看出,在比較操作的是時候,父結點作為了運算符的左側運算對象,父結點比新加的結點小才會進行相應的操作,也就不難理解為什么 less 比較操作維護的卻是大堆頂。

template<typename _RandomAccessIterator,typename _Distance, typename _Tp,typename _Compare>
void __push_heap(_RandomAccessIterator __first,_Distance __holeIndex,_Distance __topIndex,_Tp __value,_Compare& __comp) {_Distance __parent = (__holeIndex - 1) / 2; // 計算父結點索引// 比較父結點和要push的值大小關系while (__holeIndex > __topIndex && __comp(__first + __parent, __value)) {*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent));__holeIndex = __parent;__parent = (__holeIndex - 1) / 2;}// 將要push的值添加到洞里面*(__first + __holeIndex) = _GLIBCXX_MOVE(__value);}

priority_queue 的 pop 操作

priority_queuepush 操作本質上就是二叉堆有序化的下沉,pop 操作步驟如下:

  1. 首先記錄容器最后一個位置的元素值;
  2. 將堆頂的元素移動到容器最后一個位置上,這樣堆頂的位置就相當于形成了一個洞;
  3. 然后調整堆,調整堆的過程大致如下:
    • 首先根據堆頂找到其右孩子結點,然后比較右孩子結點和左孩子結點的值,將兩者中較大的值放置到堆頂;
    • 被移走的數值則形成了一個洞,因此需要繼續向下比較,直到最后不再更新洞的位置
  4. 將之前記錄的最后一個位置的元素值 push 到最后一個孔的位置;
  5. 最后從容器中移除最后一個位置的元素。

示例:

Pop.gif

下面是 pop 調整堆的關鍵源碼部分,第一個參數是容器的首元素迭代器,第二個參數是洞的初始位置,第三個參數是容器中除去要移除的元素后剩余的個數,第四個參數記錄的pop前容器最后一個位置的元素,第五個參數是用于進行比較操作的函數對象。

template<typename _RandomAccessIterator, typename _Distance,typename _Tp, typename _Compare>
void __adjust_heap(_RandomAccessIterator __first,_Distance __holeIndex,_Distance __len,_Tp __value,_Compare __comp)
{const _Distance __topIndex = __holeIndex;_Distance __secondChild = __holeIndex;while (__secondChild < (__len - 1) / 2){// 計算某個父結點的右孩子結點索引__secondChild = 2 * (__secondChild + 1);// 比較左右兩個孩子結點的大小,若左孩子結點大則更新索引if (__comp(__first + __secondChild,__first + (__secondChild - 1)))__secondChild--;*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));__holeIndex = __secondChild;}// 剩余元素個數為偶數,說明完全二叉樹的最后一個位置不是右孩子結點,就只能是左孩子結點// 而如果此時的洞是最后一個位置的父結點,那么就只能將左孩子結點的值移動到父結點處。if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2){__secondChild = 2 * (__secondChild + 1);*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first+ (__secondChild - 1)));__holeIndex = __secondChild - 1;}__decltype(__gnu_cxx::__ops::__iter_comp_val(_GLIBCXX_MOVE(__comp)))__cmp(_GLIBCXX_MOVE(__comp));// 將之前記錄的容器最后一個位置的值填入洞中std::__push_heap(__first, __holeIndex, __topIndex,_GLIBCXX_MOVE(__value), __cmp);
}

原文地址

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/255999.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/255999.shtml
英文地址,請注明出處:http://en.pswp.cn/news/255999.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Python函數】——sort,sorted

1、sorted和sort的常規使用 2、關于自定義比較函數 3、試驗 from functools import cmp_to_key ll [(2,3,10),(1,2,3),(5,6,7),(2,5,10),(2,4,10)]# 根據一個維度進行排序&#xff0c;這里根據第一維排序 ll1 sorted(ll,key lambda x:x[0]) print(根據一個維度進行排序&a…

生成相關矩陣

U是X&#xff08;差異矩陣&#xff09;各列向量取方向后形成的矩陣&#xff0c;CU^T * U 即相關矩陣&#xff0c;即各列向量兩兩的夾角&#xff0c;&#xff08;夾角越小說明關聯度越高&#xff09; clc avg_e66;avg_m66;avg_s76; x1[61 63 78 65 63] -avg_e; x2[53 73 61 84 5…

Java關于Properties用法的總結(一)

最近項目中有一個這樣的需求&#xff0c;要做一個定時任務功能&#xff0c;定時備份數據庫的操表&#xff0c;將表數據寫入txt文件。因為文件的讀寫路徑可能需要隨時改動&#xff0c;所以寫死或者寫成靜態變量都不方便&#xff0c;就考慮使用配置文件&#xff0c;這里總結些配置…

【tensorflow】——tensorboard可視化計算圖以及參數曲線圖loss圖

參考文獻&#xff1a; https://zhuanlan.zhihu.com/p/71328244 目錄 1.可視化計算圖 2.可視化參數 3. 遠程tensorboard 4、報錯 真是出來混遲早是要還的&#xff0c;之前一直拒絕學習Tensorboard&#xff0c;因為實在是有替代方案&#xff0c;直到發現到了不得不用的地步…

Lab01:Xv6 and Unix utilities

實驗測試方法 實驗的測試方法主要有2個&#xff1a; 進入到Xv6系統中&#xff0c;執行相應的命令使用實驗提供的評分測試 對于單個實驗&#xff0c;可以使用 make GRADEFLAGSapplication grade其中application為要測試的實驗應用&#xff0c;例如sleep實驗對應的評分測試命令…

jQuery學習- 位置選擇器

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>位置選擇器</title><script src"js/jquery.js"></script><script type"text/javascript">$(function(){//獲取第一個li$(&quo…

數據類型之元組

存多個值&#xff0c;對比列表來說&#xff0c;元組不可變&#xff08;是可以當做字典的key的&#xff09;&#xff0c;主要是用來讀 與列表類型比&#xff0c;只不過[]換成()age(11,22,33,44,55) #本質agetuple((11,22,33,44,55)) print(type(age)) age[0]12 t(1,2,[a,b]) pri…

cocos2d-x3.6 連連看連通畫線

我的博客&#xff1a;http://blog.csdn.net/dawn_moon 網上看到非常多人寫的連連看&#xff0c;都沒有畫連線的實現。事實上要話連線挺簡單的。cocos2d-x 提供了一個非常方便的繪圖形的類。DrawNode。這個類封裝了非常多畫線條&#xff0c;多邊形的方法。非常方便&#xff0c;非…

阿里云大數據計算服務MaxCompute(上篇)

關于阿里云大數據計算服務MaxCompute的詳細內容&#xff1a; 阿里云大數據計算服務MaxCompute使用教程 &#xff08;MaxCompute&#xff08;原ODPS&#xff09;是一項大數據計算服務&#xff0c;它能提供快速、完全托管的PB級數據倉庫解決方案&#xff0c;使您可以經濟并高效的…

Vue3、TypeScript 實現圖片數量及大小隨寬度自適應調整

前言 過了這么久&#xff0c;想起自己還有個博客&#xff0c;更點內容吧&#xff01; 來&#xff0c;上需求&#xff01; 最近在做個前端界面&#xff0c;要求在一行中展示一些圖片&#xff0c;展示的圖片數量隨著窗口寬度大小進行變化&#xff0c;除此之外還有以下要求&…

【tensorFlow】——圖像數據增強、讀取圖像、保存圖像

#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/4/13 10:54 # @Author : @linlianqin # @Site : # @File : 數據增強(distorted).py # @Software: PyCharm # @description:一些基于TensorFlow的數據處理方法import tensorflow as tf import cv2 im…

數據分析方法有哪些_數據分析方法

數據分析方法有哪些_數據分析方法 隨著大數據的到來&#xff0c;數據分析師成為大數據時代一顆冉冉升起的新星&#xff0c;現在企業越來越重視大數據&#xff0c;數據分析師這個職業也成為企業爭搶的對象。那么數據分析師的分析數據的方法都有哪些呢&#xff1f; 1、數據分析遵…

蘋果Iphone/Ipad--L2T虛擬教程

1 Iphone和Ipad同為IOS&#xff0c;設置方法相同。首先進入IOS系統的“設置”程序。 2 點擊“通用”進入通用設置&#xff0c;點擊“”; 3 選擇"添加設置 "&#xff1b; 4 選擇L2TP方式&#xff0c;填寫必要信息&#xff1a;描述、服務器地址 、您注冊充值的賬號及密…

記憶化搜索的應用

記憶化搜索的應用 一般來說&#xff0c;動態規劃總要遍歷所有的狀態&#xff0c;而搜索可以排除一些無效狀態。更重要的是搜索還可以剪枝&#xff0c;可能剪去大量不必要的狀態&#xff0c;因此在空間開銷上往往比動態規劃要低很多。 如何協調好動態規劃的高效率與高消費之間的…

【深度學習】——DNN后向傳播、CNN后向傳播文章匯總

深度神經網絡&#xff08;DNN&#xff09;模型與前向傳播算法 深度神經網絡&#xff08;DNN&#xff09;反向傳播算法(BP) 卷積神經網絡CNN的前向和后向傳播&#xff08;一&#xff09; 卷積神經網絡CNN的前向和后向傳播&#xff08;二&#xff09; 有batch normalization的卷積…

ajaxReturn 之前dump調試,導致$.ajax不能正常運行

ajaxReturn 之前dump調試&#xff0c;導致$.ajax不能正常運行 以后調試的時候&#xff0c;注意下這個情況轉載于:https://www.cnblogs.com/bushe/p/5180317.html

Veebot-自動靜脈抽血機器人

Veebot-自動靜脈抽血機器人 我們可能都有過被抽血的經驗。護士讓你握緊拳頭&#xff0c;用一根橡皮條壓住你上臂的血管&#xff0c;在你的肘部內側尋找你的靜脈&#xff0c;有時還需要拍打幾下&#xff0c;摸到隆起的靜脈血管&#xff0c;一針下去。有時候碰到技術好的護士&…

idea 轉普通項目為maven 項目

1、項目上右鍵 Add Framework Support。 2、選擇maven&#xff0c;點擊OK。 轉載于:https://www.cnblogs.com/mayanze/p/8042489.html

HDOJ5547 SudoKu

題目鏈接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid5547 題目大意&#xff1a;填數獨。。。 思路&#xff1a;爆搜 1 #include <stdio.h>2 #include <string.h>3 #include <iostream>4 #include <algorithm>5 using namespace std;6 boo…

【深度學習之ResNet】——深度殘差網絡—ResNet總結

目錄 論文名稱&#xff1a;Deep Residual Learning for Image Recognition 摘要&#xff1a; 1、引言 2、為什么會提出ResNet殘差網絡呢&#xff1f; 3、深度殘差網絡結構學習&#xff08;Deep Residual learning&#xff09; &#xff08;1&#xff09;殘差單元 &#xf…