c++ --- priority_queue的使用以及簡單實現

C++ --- priority_queue

  • 前言
  • 一、priority_queue的使用
  • 二、priority_queue的簡單實現
    • 1.整體結構
    • 2.主要方法
      • push
      • pop
      • top
      • empty
      • size
  • 三、構造
    • 迭代器區間構造
    • 默認構造
  • 四、仿函數

前言

priority_queue是C++容器之一,意為優先級隊列,雖說叫做隊列,但是其底層結構是堆,唯一和隊列有關是使用此容器時包含的標準頭文件是queue,通過此容器的學習,會學習到一個新的知識,仿函數。

一、priority_queue的使用

priority_queue的接口和stack與queue的基本一樣,主要是push,pop,top,empty,size這些接口,只是底層的結構不同。

需要注意的是底層默認創建大堆結構,需要變成創建小堆結構,這里需要使用新的知識,仿函數,仿函數的詳細介紹留在簡單實現板塊;還有同樣因為數據有特殊的順序,所以底層不支持迭代器遍歷

// 在這里仿函數是第三個模板參數,控制的是創建堆的結構
// greater --- 更大的
// less --- 更小的
// 不過標準庫里的是less控制大堆,greater控制小堆,是反過來的,這一點需要注意一下
// 標準庫提供的接口和前面的stack,queue相似,主要就是push,pop,top,size,empty這幾個
// 使用起來也是差不多的,同樣因為數據有特殊的順序,所以底層不支持迭代器遍歷
// 盡管堆的底層是數組,但是底層沒有實[]的重載。// 創建一個堆的對象
//priority_queue<int> hp;
priority_queue<int, vector<int>, greater<int>> hp;hp.push(4);
hp.push(2);
hp.push(6);
hp.push(7);
hp.push(1);
hp.push(8);// 循環取堆頂元素打印
while (!hp.empty())
{cout << hp.top() << " ";hp.pop();
}
cout << endl;

打印結果:
此時仿函數是greater,創建的是小堆,循環取堆頂元素則是升序排列。
在這里插入圖片描述

二、priority_queue的簡單實現

1.整體結構

priority_queue的底層是一個堆結構,并且堆結構是由數組實現的完全二叉樹,所以這里直接使用容器適配器,默認適配vector,來作為priority_queue的結構。

template<class T, class Container = vector<T>>
class priority_queue
{
public://………………// 各種方法
private:Container _con;
};

2.主要方法

priority_queue的主要接口就是push,pop,top,empty,size。

push

由于堆結構本身就有性質,要么是大堆,要么就是小堆,所以這里在堆尾插入完數據后需要調整數據的位置,以滿足堆的結構。

// 在堆尾入數據
void push(const T& x)
{// 插入數據_con.push_back(x);// 向上調整算法Adjust_up(size() - 1);}// 向上調整算法
void Adjust_up(size_t child)
{// 已知孩子節點求雙親節點size_t parent = (child - 1) / 2;// 最壞的情況child調整至根節點才結束while (child > 0){// 大堆,誰大誰向上調整// 小堆,誰小誰向上調整if (_con[child] < _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

在向上調整算法中,參數接收的是孩子節點的下標,根據二叉樹的性質可知雙親節點的下標 = (孩子節點 - 1)/ 2,求出雙親節點的下標后比較它們兩個的大小,孩子節點大(大堆)/ 小(小堆)于雙親節點,則進行交換,隨后更新child和parent的位置,指向下一棵子樹,若插入后滿足所需要的堆結構,則直接跳出循環,最壞的情況下child調整至根節點才結束,所以這里的循環結束條件是child <= 0。

pop

由于堆底層是數組,并且堆的pop操作是在堆頂進行的,所以相當于進行頭刪操作,這樣效率較低,首先先將堆頂數據和堆尾數據進行交換,這樣一來需要刪除的數據就在堆尾,而數組刪除最后一個數據效率很高,直接將其刪除掉即可,然后調整堆結構即可,這里使用向下調整算法。

// 在堆頂出數據
void pop()
{// 刪除堆頂元素,先將堆頂和堆尾元素交換,再刪除swap(_con[0], _con[_con.size() - 1]);_con.pop_back();// 向下調整算法Adjust_down(0);
}// 向下調整算法
void Adjust_down(size_t parent)
{// 已知雙親節點求左孩子節點,右孩子節點即左孩子 + 1size_t child = parent * 2 + 1;// 最壞的情況是根節點調整到葉子節點while (child < size()){// 首先右孩子得存在合法// 如果右孩子大于/小于左孩子,則child走到大/小的一方if (child+1 < size() && _con[child + 1] < _con[child]){++child;}// 大堆,誰大誰向上調整// 小堆,誰小誰向上調整if (_con[child] < _con[parent]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

在向下調整算法中,參數接收的是根節點的下標,根據二叉樹的性質可知左孩子節點 = 雙親節點 * 2 + 1,右孩子則是左孩子 + 1;在此算法中額外多出一步是比較左右孩子的大小關系,當堆結構為大堆時,child走到大的那一個孩子處,同理當堆結構為小堆時,child走到小的那一個孩子處(這一步要注意右孩子的下標需要存在合法),之后就和向上調整算法里的一樣,孩子節點和雙親結點比較,誰大 / 小 就進行交換,隨后更新child和parent的位置,走到下一棵子樹的位置繼續進行調整,若滿足所需要的堆結構,則直接跳出循環,最壞的情況是根節點調整到葉子節點才結束,所以這里的循環結束條件是child >= 堆的szie。

top

此接口和下面的兩個接口都直接使用適配即可。

// 取堆頂元素
const T& top() const
{return _con[0];
}

empty

// 判空
bool empty()
{return _con.empty();
}

size

// 取有效元素個數
size_t size() 
{return _con.size();
}

三、構造

迭代器區間構造

priority_queue支持迭代器區間構造,并且底層實現的是函數模板形式,所以我們也去實現函數模板形式。

// 迭代器區間構造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)// 先將此迭代器區間入堆 --- _con是容器,支持直接迭代器區間構造:_con(first,last)
{// 然后就是向下調整建堆 --- 因為此時還不是堆結構for (size_t i = (size() - 1 - 1) / 2; i > 0; i--){Adjust_down(i);}
}

這里初始化列表里直接復用適配器的迭代器區間構造即可,然后雖入了數據,但是此時不是一個堆結構,同樣需要進行調整,這里采用的是向下調整算法,至于為什么不使用向上調整算法,因為向上調整算法的時間復雜度高于向下調整算法,時間復雜度詳情請回顧我的數據結構 — 堆 的這篇博客(link)

在向下調整建堆中,由于是向下調整,雙親節點和孩子節點進行比較向下調整,所以這里的for循環的循環變量 i 也就是雙親節點的下標,并且二叉樹的最后一層節點是葉子節點,所以雙親節點的最后一層是倒數第二層,size() - 1 是最后一個位置的下標(不能保證此位置上有節點,此位置是右孩子),再減一就是左孩子的下標,已知孩子求雙親就是:(size() - 1 - 1) / 2,直至調整至根節點,所以循環結束條件就是i <= 0。

默認構造

由于我們自己寫了一種構造,編譯器就不再生成默認構造了,而默認構造能滿足我們的需求,所以這里強制讓編譯器生成默認構造。

// 由于我們自己寫了一種構造,編譯器就不生成默認構造了
// 所以這里強制讓編譯器生成
priority_queue() = default;

四、仿函數

所謂仿函數,也叫做函數對象,它其實是一個類,其中重載了運算符operator(),使得這個類能夠像函數一樣被調用。
回到堆的代碼中,在向上或者向下調整算法里面,我們控制大堆小堆結構是通過孩子和雙親大小關系來確定的,但是這個關系是固定死的,要么大于,要么小于,這里就可以使用仿函數,不用寫死關系,通過調用仿函數創建的對象去調用運算符(),在此重載內部再去確定比較關系即可。

// 這個就是仿函數 / 函數對象
// 仿函數代替的就是C語言的函數指針
// 簡單來說就是一個類,通過這個類類型創建的對象去模擬函數那樣被調用
template<class T>
struct less
{bool operator()(const T& x, const T& y){return x < y;}
};template<class T>
struct greater
{bool operator()(const T& x, const T& y){return x > y;}
};// 使用仿函數控制大小比較關系
// 需要多增加一個模板參數
template<class T, class Container = vector<T>,class Compare = less<T>>
class priority_queue

回到向上調整算法中,在方法內部通過Compare創建了一個對象com,在孩子和雙親比較的邏輯里替換了之前的固定寫死一方的大小關系,變成通過com對象去調用運算符(),這樣就看起來像是調用了函數,這就是仿函數。

// 向上調整算法
void Adjust_up(size_t child)
{Compare com;size_t parent = (child - 1) / 2;while (child > 0){//if (_con[child] < _con[parent])if (com(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

調用關系如下圖所示:
這里的參數x接收的就是parent,參數y接收的就是child。
在這里插入圖片描述
同理向下調整算法里parent和child的比較邏輯,child和child+1的比較邏輯也是如此替換。
需要注意的是對于類模板而言使用仿函數時這里傳遞的是類型,而對函數模板而言使用仿函數時傳遞的則是對象。

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

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

相關文章

MySQL梳理三:查詢與優化

MySQL查詢優化完整指南&#xff1a;從理論到實踐 本文從MySQL查詢的基礎機制出發&#xff0c;深入探討單表查詢訪問方法、聯表查詢策略、成本計算原理、基于規則的優化技術&#xff0c;最后通過實際案例展示慢SQL的診斷和優化過程。 目錄 一、單表查詢的訪問方法二、聯表查詢機…

從零開始的python學習(九)P129+P130+P131+P132+P133

本文章記錄觀看B站python教程學習筆記和實踐感悟&#xff0c;視頻鏈接&#xff1a;【花了2萬多買的Python教程全套&#xff0c;現在分享給大家&#xff0c;入門到精通(Python全棧開發教程)】 https://www.bilibili.com/video/BV1wD4y1o7AS/?p6&share_sourcecopy_web&v…

LCL濾波器及其電容電流前饋有源阻尼設計軟件【LCLAD_designer】

本文主要介紹針對阮新波著《LCL型并網逆變器的控制技術》書籍 第二章&#xff08;LCL濾波器設計&#xff09;及第五章&#xff08;LCL型并網逆變器的電容電流反饋有源阻尼設計&#xff09;開發的一款交互式軟件【LCL&AD_designer】&#xff0c;開發平臺MATLAB_R2022b/app d…

【Conda】配置Conda鏡像源

Conda 鏡像源配置指南 適用系統&#xff1a;Windows 10&#xff08;含 Miniconda / Anaconda&#xff09; & Linux&#xff08;Ubuntu / CentOS / Debian 等&#xff09;1. 為什么要設置鏡像源 在中國大陸直接訪問 repo.anaconda.com 經常遇到速度慢、連接超時、SSL 錯誤等…

八股取士--docker

基礎概念類 1. 什么是Docker&#xff1f;它解決了什么問題&#xff1f; 解析&#xff1a; Docker是一個開源的容器化平臺&#xff0c;用于開發、交付和運行應用程序。 主要解決的問題&#xff1a; 環境一致性&#xff1a;解決"在我機器上能跑"的問題資源利用率&#…

C++:STL中的棧和隊列的適配器deque

學習完string類、容器vector和容器list&#xff0c;再去學習其他容器的學習成本就非常低&#xff0c;容器的使用方法都大差不差&#xff0c;而棧和隊列的底層使用了適配器&#xff0c;去模擬實現就沒有那么麻煩&#xff0c;適配器也是一種容器&#xff0c;但是這種容器兼備棧和…

9類主流數據庫 - 幫你更好地進行數據庫選型!

作者&#xff1a;唐叔在學習 專欄&#xff1a;數據庫學習 標簽&#xff1a;數據庫選型、MySQL、Redis、MongoDB、大數據存儲、NoSQL、數據庫優化、數據架構、AI數據庫 大家好&#xff0c;我是你們的老朋友唐叔&#xff01;今天咱們來聊聊程序員吃飯的家伙之一 —— 數據庫。在這…

推送本地項目到Gitee遠程倉庫

文章目錄前言前面已加學習了下載gitee軟件&#xff0c;網址在上一篇文章。在gitee創建賬號與倉庫。現在來學習如何講本地項目推送到Gitee遠程倉庫一、流程總結前言 前面已加學習了下載gitee軟件&#xff0c;網址在上一篇文章。在gitee創建賬號與倉庫。現在來學習如何講本地項目…

CMake 命令行參數完全指南(5)

?**40. --version**? ?解釋?&#xff1a;顯示CMake版本 ?示例?&#xff1a; cmake --version # 輸出&#xff1a;cmake version 3.25.2?**41. --warn-uninitialized**? ?解釋?&#xff1a;警告未初始化的變量 ?適用場景?&#xff1a;檢測腳本錯誤 ?示例?&#xf…

基于Python實現生產者—消費者分布式消息隊列:構建高可用異步通信系統

深入剖析分布式消息隊列的核心原理與Python實現&#xff0c;附完整架構設計和代碼實現引言&#xff1a;分布式系統的通信基石在微服務架構和云原生應用普及的今天&#xff0c;服務間的異步通信成為系統設計的核心挑戰。當單體應用拆分為數十個微服務后&#xff0c;服務間通信呈…

【大模型核心技術】Agent 理論與實戰

一、基本概念 LLM 特性&#xff1a;擅長理解和生成文本&#xff0c;但采用 “一次性” 響應模式&#xff0c;本質上是無記憶的生成模型。Agent 本質&#xff1a;包含 LLM 的系統應用&#xff0c;具備自主規劃、工具調用和環境反饋能力&#xff0c;是將 LLM 從 “聊天機器人” 升…

Maven - 依賴的生命周期詳解

作者&#xff1a;唐叔在學習 專欄&#xff1a;唐叔的Java實踐 標簽&#xff1a;Maven依賴管理、Java項目構建、依賴傳遞性、Spring Boot依賴、Maven最佳實踐、項目構建工具、依賴沖突解決、POM文件詳解 文章目錄一、開篇二、Maven依賴生命周期2.1 依賴聲明階段&#xff1a;POM文…

從零打造大語言模型--處理文本數據

從零打造大語言模型 第 1 章&#xff1a;處理文本數據 章節導讀 在把文本投喂進 Transformer 之前&#xff0c;需要兩步&#xff1a;① 將字符流切分成離散 Token&#xff1b;② 把 Token 映射成連續向量。 1.1 理解詞嵌入&#xff08;Word Embedding&#xff09; 嵌入向量 一…

【Spring】Bean的生命周期,部分源碼解釋

文章目錄Bean 的生命周期執行流程代碼演示執行結果源碼閱讀AbstractAutowireCapableBeanFactorydoCreateBeaninitializeBeanBean 的生命周期 生命周期指的是一個對象從誕生到銷毀的整個生命過程&#xff0c;我們把這個過程就叫做一個對象的聲明周期 Bean 的聲明周期分為以下 …

[spring-cloud: 服務發現]-源碼解析

DiscoveryClient DiscoveryClient 接口定義了常見的服務發現操作&#xff0c;如獲取服務實例、獲取所有服務ID、驗證客戶端可用性等&#xff0c;通常用于 Eureka 或 Consul 等服務發現框架。 public interface DiscoveryClient extends Ordered {/*** Default order of the dis…

QML 基礎語法與對象模型

QML (Qt Meta-Object Language) 是一種聲明式語言&#xff0c;專為創建流暢的用戶界面和應用程序邏輯而設計。作為 Qt 框架的一部分&#xff0c;QML 提供了簡潔、直觀的語法來描述 UI 組件及其交互方式。本文將深入解析 QML 的基礎語法和對象模型。 一、QML 基礎語法 1. 基本對…

HTTPS的概念和工作過程

一.HTTPS是什么HTTPS也是一個應用層協議&#xff0c;是在HTTP協議的基礎上引入了一個加密層&#xff08;SSL&#xff09;HTTP協議內容都是按照文本的方式明文傳輸的&#xff0c;這就導致傳輸過程中可能出現被篡改的情況最著名的就是十多年前網絡剛發展的時期&#xff0c;出現“…

Unity —— Android 應用構建與發布?

文章目錄1 ?Gradle模板??&#xff1a;了解Gradle模板的作用及使用方法&#xff0c;以增強對構建流程的控制。?2 ?Gradle模板變量??&#xff1a;參考文檔——自定義Gradle模板文件中可用的變量列表。2.1 修改Unity應用的Gradle工程文件2.1.1 通過Gradle模板文件2.1.2 導出…

【iOS】strong和copy工作流程探尋、OC屬性關鍵字復習

文章目錄前言strong和copy的區別為什么要用copy&#xff1f;什么時候用什么修飾&#xff1f;strong&#xff08;ARC自動管理&#xff09;strong修飾變量的底層流程圖底層代碼核心實現小結copy底層流程圖對比與strong的關鍵不同之處內部調用關系&#xff08;偽代碼&#xff09;小…

程序代碼篇---多循環串口程序切換

上位機版&#xff08;Python&#xff09;要實現根據串口接收結果高效切換四個 while 循環函數&#xff0c;我們可以采用狀態機模式&#xff0c;配合非阻塞串口讀取來設計程序結構。這種方式可以實現快速切換&#xff0c;避免不必要的資源消耗。下面是一個高效的實現方案&#x…