[C++歷練之路]優先級隊列||反向迭代器的模擬實現

W...Y的主頁 😊?

代碼倉庫分享💕


🍔前言:
在C++的宇宙中,優先隊列似乎是一座巨大的寶庫,藏匿著算法的珍寶。而就在這片代碼的天空下,我們不僅可以探索優先隊列的神奇,還能夠揭開反向迭代器的神秘面紗。讓我們一同踏入這個編程的探險之旅,在這里,我們將用C++語言創造出一個能按照優先級排列元素的神奇容器,并且探索反向迭代器的魅力,仿佛是在編碼的星空下追逐著閃爍的代碼流星。準備好了嗎?讓我們邁出第一步,開啟這段驚險又充滿奇跡的模擬之旅。

目錄

了解priority_queue

模擬實現priority_queue

構建基本框架

仿函數的介紹以及第三個參數添加

反向迭代器的模板實現


了解priority_queue

1. 優先隊列是一種容器適配器,根據嚴格的弱排序標準,它的第一個元素總是它所包含的元素中最大的。
2. 此上下文類似于堆,在堆中可以隨時插入元素,并且只能檢索最大堆元素(優先隊列中位于頂部的元素)。
3. 優先隊列被實現為容器適配器,容器適配器即將特定容器類封裝作為其底層容器類,queue提供一組特定的成員函數來訪問其元素。元素從特定容器的“尾部”彈出,其稱為優先隊列的頂部。
4. 底層容器可以是任何標準容器類模板,也可以是其他特定設計的容器類。容器應該可以通過隨機訪問迭代器訪問,并支持以下操作:
empty():檢測容器是否為空
size():返回容器中有效元素個數
front():返回容器中第一個元素的引用
push_back():在容器尾部插入元素

op_back():刪除容器尾部元素
5. 標準容器類vector和deque滿足這些需求。默認情況下,如果沒有為特定的priority_queue類實例化指定容器類,則使用vector。
6. 需要支持隨機訪問迭代器,以便始終在內部保持堆結構。容器適配器通過在需要時自動調用算法函數make_heap、push_heap和pop_heap來自動完成此操作。

?優先隊列其實就是數據結構中的堆,而我們想要進行其實現必須掌握其模板。

默認情況下,priority_queue是大堆,而第一個模板參數class T就是其對應的數據類型,第二個模板參數是其數據結構的類型,缺省值為vector,所以其默認的結構類型就是數組,不是鏈式結構類型的堆,如果在priority_queue中放自定義類型的數據,用戶需要在自定義類型中提供> 或者< 的重載。第三個模板類型就是一種仿函數,其可以操控其創建的是大堆還是小堆。

所以我們要用堆的思想來模擬實現優先隊列!

模擬實現priority_queue

構建基本框架

首先我們可以照貓畫虎,仿照其參數模板進行仿寫:

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{template<class T, class Container = vector<T>>class priority_queue{public:void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};

我們將基本函數框架打好,將優先隊列的基本函數接口完善,這些都是我們復用的vector、list、deque等接口,可以直接從STL中直接調用。?

注意:這里我們在函數模板中未加入第三個參數進行參數,這里我們在最后實現。原模板接口的缺省默認參數為less<T>,是構建大堆的,所以我們模擬中是先建立大堆。、

往vector中push數據時就要建立大堆進行排序,pop數據得使用向下調整對堆中的數據重新排序成為大堆,所以建立大堆就是使用數據結構中的向上調整函數進行操作,而pop數據是用向下調整的方法進行。

如果對堆這一塊的不太了解,可以一下文章:

堆的基本實現——數據結構icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/132794715?spm=1001.2014.3001.5502向上調整:

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[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

向下調整:

void adjust_down(size_t parent)
{//Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && _con[child] < _con[child + 1])//if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}if(_con[child] > _con[parent])//if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

仿函數的介紹以及第三個參數添加

在C++中,仿函數(Functor)是一種重載了函數調用操作符 operator() 的對象。它實際上是一個類或者結構體,通過重載 operator(),使得該對象可以像函數一樣被調用。仿函數可以像函數一樣接受參數,并返回結果,同時可以包含狀態信息,因此它們在C++中被廣泛用于實現函數對象,作為算法的參數傳遞,或者用于定義自定義的操作。

通過仿函數,可以實現自定義的比較、排序、轉換或者其他操作,這些操作可以被算法直接使用,例如在標準庫中的排序算法 std::sort、查找算法 std::find,或者容器類中的自定義排序規則等。使用仿函數可以提供更大的靈活性,使得算法能夠適應不同的需求。

下面是一個簡單的示例,展示了一個自定義的仿函數用于比較兩個整數的大小:

#include <iostream>// 定義一個比較器仿函數
struct Compare {bool operator()(int a, int b) const {return a < b; // 自定義的比較規則:a < b}
};int main() {Compare cmp; // 創建比較器對象int x = 5, y = 10;if (cmp(x, y)) {std::cout << "x is less than y." << std::endl;} else {std::cout << "x is greater than or equal to y." << std::endl;}return 0;
}

在這個示例中,Compare 結構體重載了 operator(),定義了一個比較規則,判斷第一個參數是否小于第二個參數。然后在 main 函數中,創建了一個 Compare 類型的對象 cmp,并使用它進行比較操作。

因此,仿函數是C++中的一種強大機制,可以擴展函數的行為,提供更靈活的功能,并允許開發者以更抽象的方式定義特定操作。

所以我們可以使用仿函數針對第三個參數。

priority_queue函數的第三個默認缺省參數為less<T>,如果我們傳greater<T>才可以創建小堆。而我們模擬的函數中創建大小堆只不過是將其比較符號進行轉換即可。所以我們就可以使用仿函數創建兩個不同類型的進行調用。

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{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{public: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[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){//if (child + 1 < _con.size() && _con[child] < _con[child + 1])if(child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}//if(_con[child] > _con[parent])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};

這樣我們只需要在模擬函數中創建一個模板變量即可在函數中進行調用。

反向迭代器的模板實現

在STL中的所有容器里都有迭代器與反向迭代器,而在每個容器的模擬實現中我們也將其進行復現。string、vector中的迭代器都可以類似與指針,因為其底層的存儲物理空間是連續的,我們可以很好的進行重定義使用。但是list卻不行,因為空間是不連續的,所以我們得重新定義封裝出一個類迭代器的重定義,將其運算符進行重載成合理的進行使用。

而反向迭代器中我們可以將list中封裝的迭代器進行復制粘貼修改,就可以正確使用。

rend指向頭節點,而rbegin指向_head->_prev節點,也就是尾節點即可。?

template<class T, class Ref, class Ptr>
struct __list_reverse_iterator
{typedef list_node<T> node;typedef __list_reverse_iterator<T, Ref, Ptr> self;node* _node;__list_reverse_iterator(node* n):_node(n){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_prev;return *this;}self operator++(int){self tmp(*this);_node = _node->_prev;return tmp;}self& operator--(){_node = _node->_next;return *this;}self operator--(int){self tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};

我們只需要將++、--運算符進行重載即可。將++指向當前節點的_prev節點,而--指向當前節點的_next節點。

那我們看一下STL-list中的反向迭代器是怎么寫的:

class reverse_bidirectional_iterator {typedef reverse_bidirectional_iterator<_BidirectionalIterator, _Tp, _Reference, _Distance>  _Self;
protected:_BidirectionalIterator current;
public:typedef bidirectional_iterator_tag iterator_category;typedef _Tp                        value_type;typedef _Distance                  difference_type;typedef _Tp*                       pointer;typedef _Reference                 reference;reverse_bidirectional_iterator() {}explicit reverse_bidirectional_iterator(_BidirectionalIterator __x): current(__x) {}_BidirectionalIterator base() const { return current; }_Reference operator*() const {_BidirectionalIterator __tmp = current;return *--__tmp;}
#ifndef __SGI_STL_NO_ARROW_OPERATORpointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */_Self& operator++() {--current;return *this;}_Self operator++(int) {_Self __tmp = *this;--current;return __tmp;}_Self& operator--() {++current;return *this;}_Self operator--(int) {_Self __tmp = *this;++current;return __tmp;}
};

STL中的反向迭代器是封裝了正向迭代器,構造一個反向迭代器了。正向迭代器的++就是反向迭代器的--,而正向迭代器的--就是反向迭代器的++。

注意:STL源碼中的復用代碼rbegin()與rend()的源碼為:

直接返回的是其正向迭代器的begin()與end(),所以其解引用的內容就要發生變化:

其結構就不同:

與原來的結構是不同的。

我們可以自己封裝一個類進行使用:

#pragma once
namespace why
{template<class Iterator, class Ref>struct ReverseIterator{typedef ReverseIterator<Iterator, Ref> Self;Iterator _cur;ReverseIterator(Iterator it):_cur(it){}Ref operator*(){Iterator tmp = _cur;--tmp;return *tmp;}Self& operator++(){--_cur;return *this;}Self& operator--(){++_cur;return *this;}bool operator!=(const Self& s){return _cur != s._cur;}};
}

?但是這樣復用正向迭代器與剛才的寫法所表達的寫法是一樣的,為什么還要這樣單獨創建一個類呢?因為list的正向迭代器就是進行封裝的,可以復用。但是string、vector的正向迭代器就是指針就不能進行此操作了,所以我們必須復用。


總結:

在這段代碼的奇妙旅程中,我們成功地創造了一個C++中的優先隊列,仿佛編織了一個可以按照優先級排序元素的魔法網。這個隊列不僅僅是一段代碼,更是算法的交響樂,奏響著排序、插入、刪除的優美旋律。而更加令人驚嘆的是,我們在這個編碼的仙境中,還揭開了反向迭代器的神秘面紗,為我們的容器增添了一抹獨特的色彩。

通過這個模擬實現,我們深入理解了C++中優先隊列的本質,并感受到了反向迭代器的便利之處。這不僅是一次代碼之旅,更是對數據結構和算法的深刻思考,是對編程藝術的一次追求和探索。

或許,在未來的編程征途中,你會在實際項目中運用這些知識,創造出更為強大、高效的代碼。無論何時何地,優先隊列和反向迭代器的魔法都將伴隨著你,成為解決問題的得力工具。

讓我們懷著對編碼奇跡的敬畏之心,結束這段代碼的冒險。愿你的代碼之路充滿創造與探索,愿你的算法之舞永遠翩翩起舞。編碼的世界里,冒險永不止步,期待著你下一次的代碼奇跡。

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

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

相關文章

shutil和fileinput模塊:文件操作的最佳實踐

在Python中&#xff0c;shutil和fileinput模塊是處理文件和輸入/輸出(I/O)操作的有力工具。shutil模塊提供了一種在Python中操作文件的高級接口&#xff0c;而fileinput模塊則允許我們輕松地讀取多個輸入流。 shutil模塊 shutil模塊是Python的標準庫之一&#xff0c;提供了很…

【【Linux系統下常用指令學習 之 二 】】

Linux系統下常用指令學習 之 二 文件查詢和搜索 文件的查詢和搜索也是最常用的操作&#xff0c;在嵌入式 Linux 開發中常常需要在 Linux 源碼文件中查詢某個文件是否存在&#xff0c;或者搜索哪些文件都調用了某個函數等等。 1、命令 find find 命令用于在目錄結構中查找文件…

BUUCTF [ACTF新生賽2020]outguess 1

BUUCTF:https://buuoj.cn/challenges 題目描述&#xff1a; 得到的 flag 請包上 flag{} 提交。 密文&#xff1a; 下載附件&#xff0c;得到一堆文件。 解題思路&#xff1a; 1、根據題目和flag.txt文件提示&#xff0c;猜測為outguess隱寫。 outguess下載安裝 kail 終端命…

數字鄉村:科技賦能農村產業升級

數字鄉村&#xff1a;科技賦能農村產業升級 數字鄉村是指通過信息技術和數字化手段&#xff0c;推動農業現代化、農村經濟發展和農民增收的一種新模式。近年來&#xff0c;隨著互聯網技術的飛速發展&#xff0c;數字鄉村開始在全國范圍內迅速興起&#xff0c;為鄉村經濟注入了新…

leedcode 刷題 - 除自身以外數組的乘積 - 和為 K 的子數組

I238. 除自身以外數組的乘積 - 力扣&#xff08;LeetCode&#xff09; 給你一個整數數組 nums&#xff0c;返回 數組 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘積 。 題目數據 保證 數組 nums之中任意元素的全部前綴元素和后綴的乘積都在…

AdaBoost提升分類器性能

目錄 AdaBoost算法原理 AdaBoost工作詳情 初始權重分配 第一輪 第二輪 后續輪次 最終模型 AdaBoost的API解釋 AdaBoost 對房價進行預測 AdaBoost 與決策樹模型的比較 結論 AdaBoost算法原理 在數據挖掘中&#xff0c;分類算法可以說是核心算法&#xff0c;其中 Ada…

gitee推薦-PHP面試準備的資料

該內容為giee項目。PHP-Interview: 這個項目是自己準備PHP面試整理的資料。包括PHP、MySQL、Linux、計算機網絡等資料。方便自己以后查閱&#xff0c;會不定期更新&#xff0c;歡迎提交pr&#xff0c;如果錯誤&#xff0c;請指出&#xff0c;謝謝 在線預覽地址&#xff1a;Intr…

leetcode面試經典150題——31 無重復字符的最長子串(方法二極簡代碼!!!)

題目&#xff1a; 無重復字符的最長子串 描述&#xff1a; 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長子串 的長度。 示例 1: 輸入: s “abcabcbb” 輸出: 3 解釋: 因為無重復字符的最長子串是 “abc”&#xff0c;所以其長度為 3。 leetcode鏈接 方法…

【LeetCode刷題筆記】DFSBFS(三)

圖的基礎知識 鄰接矩陣是一個二維表,其中橫縱坐標交叉的格子值為 1 的表示這兩個頂點是連通的,否則是不連通的。

Python-csv庫進行數據保存和讀寫

在 Python 中使用 CSV 文件非常簡單&#xff0c;Python 提供了內置的 csv 模塊來處理 CSV 文件。你可以使用 csv 模塊來讀取、寫入和操作 CSV 文件中的數據。 基礎使用 讀取 CSV 文件 python import csv# 打開 CSV 文件進行讀取 with open(file.csv, moder) as file:reader …

NVM得介紹和詳細使用教程

NVM???????&#xff08;Node Version Manager&#xff09;是一個用于管理多個Node.js版本的工具。它允許您在同一臺計算機上輕松地切換和管理不同的Node.js版本。以下是NVM的介紹和詳細使用教程&#xff1a; 安裝NVM&#xff1a; 首先&#xff0c;您需要在計算機上安裝N…

C#串口通信從入門到精通(27)——高速通信下解決數據處理慢的問題(20ms以內)

前言 我們在開發串口通信程序時,有時候會遇到比如單片機或者傳感器發送的數據速度特別快,比如10ms、20ms發送一次,并且每次發送的數據量還比較大,如果按照常規的寫法,我們會發現接收的數據還沒處理完,新的數據又發送過來了,這就會導致處理數據滯后,軟件始終處理的不是…

python樹的雙親存儲結構

這種存儲結構是一種順序存儲結構&#xff0c;采用元素形如“[結點值&#xff0c;雙親結點索引]”的列表表示。通常每個結點有唯一的索引(或者偽地址&#xff09;,根結點的索引為0&#xff0c;它沒有雙親結點&#xff0c;其雙親結點的索引為-1。例如&#xff0c;所示的樹對應的雙…

123. 股票買賣的最佳時機III(2次交易)

題目 題解 class Solution:def maxProfit(self, prices: List[int]) -> int:N len(prices)# 狀態定義 dp[i][j][k]代表在第i天&#xff0c;被允許完成j次交易時&#xff0c;持有或者不持有的最大利潤。k0代表不持有&#xff0c;k1代表持有dp [[[0 for k in range(2)] for…

醫學生秋招攻略,面試時一定要注意這些方面!

醫學生別拖了&#xff0c;今年秋招已經過去一波熱度了&#xff0c;趕早不趕晚&#xff01;在籌備第二輪秋招以及明年的春招的醫學生一定要注意以下事項。 1.清晰目標 搜集秋招訊息 一定要早點多做準備&#xff0c;想清楚未來的目標&#xff0c;是繼續深造還是就業做醫生或者是…

FileReader與URL.createObjectURL實現圖片、視頻上傳預覽

之前做圖片、視頻上傳預覽常用的方案是先把文件上傳到服務器&#xff0c;等服務器返回文件的地址后&#xff0c;再把該地址字符串賦給img或video的src屬性&#xff0c;這才實現所謂的文件預覽。實際上這只是文件“上傳后再預覽”&#xff0c;這既浪費了用戶的時間&#xff0c;也…

java開發合同相關

【點我-這里送書】 本人詳解 作者:王文峰,參加過 CSDN 2020年度博客之星,《Java王大師王天師》 公眾號:JAVA開發王大師,專注于天道酬勤的 Java 開發問題中國國學、傳統文化和代碼愛好者的程序人生,期待你的關注和支持!本人外號:神秘小峯 山峯 轉載說明:務必注明來源(…

集合的分類

Python內建的集合類&#xff0c;有有序和無序之分&#xff0c;還有可修改和不可修改之分。 1 有序和無序 有序是說某數據集合中的每個元素都有一個位置信息&#xff0c;通常用index表示&#xff0c;可以借助這種集合類型名和位置信息訪問集合里的某元素值&#xff0c;在Pytho…

【開源】基于Vue.js的用戶畫像活動推薦系統

項目編號&#xff1a; S 061 &#xff0c;文末獲取源碼。 \color{red}{項目編號&#xff1a;S061&#xff0c;文末獲取源碼。} 項目編號&#xff1a;S061&#xff0c;文末獲取源碼。 目錄 一、摘要1.1 項目介紹1.2 項目錄屏 二、功能模塊2.1 數據中心模塊2.2 興趣標簽模塊2.3 活…

[Android]使用Git將項目提交到GitHub

如果你的Mac還沒有安裝Git&#xff0c;你可以通過Homebrew來安裝它&#xff1a; brew install git 方式一&#xff1a;終端管理 1.創建本地Git倉庫 在項目的根目錄下&#xff0c;打開終端&#xff08;Terminal&#xff09;并執行以下命令來初始化一個新的Git倉庫&#xff1…