C++實(shí)現(xiàn)優(yōu)先隊(duì)列的示例詳解
前言
首先,啊,先簡(jiǎn)單介紹一下優(yōu)先隊(duì)列的概念,學(xué)數(shù)據(jù)結(jié)構(gòu)以及出入算法競(jìng)賽的相信都對(duì)隊(duì)列這一數(shù)據(jù)結(jié)構(gòu)十分熟悉,這是一個(gè)線性的數(shù)據(jù)結(jié)構(gòu).
針對(duì)隊(duì)列這一特殊數(shù)據(jù)結(jié)構(gòu),有時(shí)需考慮隊(duì)列元素的優(yōu)先級(jí)的關(guān)系,即根據(jù)用戶自定義的優(yōu)先級(jí)排序,出隊(duì)時(shí)優(yōu)先彈出優(yōu)先級(jí)更高(低)的元素,優(yōu)先隊(duì)列能更好地滿足實(shí)際問(wèn)題中的需求,而在優(yōu)先隊(duì)列的各種實(shí)現(xiàn)中,堆是一種最高效的數(shù)據(jù)結(jié)構(gòu)。
什么是堆
堆是一顆具有特定性質(zhì)的二叉樹(shù),堆的基本要求就是堆中所有結(jié)點(diǎn)的值必須大于或等于(或小于或等于)其孩子結(jié)點(diǎn)的值,這也稱為堆的性質(zhì),我們也叫堆序性;堆還有另一個(gè)性質(zhì),就是當(dāng) h > 0 時(shí),所有葉子結(jié)點(diǎn)都處于第 h 或 h - 1 層(其中 h 為樹(shù)的高度,完全二叉樹(shù)),也就是說(shuō),堆應(yīng)該是一顆完全二叉樹(shù);
如下:
根據(jù)兩種堆序性,我們將堆分為兩類,即根節(jié)點(diǎn)權(quán)值≥子節(jié)點(diǎn)權(quán)值的我們叫大根堆,根節(jié)點(diǎn)權(quán)值≤子節(jié)點(diǎn)權(quán)值的我們叫小根堆。道理簡(jiǎn)單,就不做圖演示了。
上文所述,優(yōu)先隊(duì)列是由一個(gè)堆維護(hù)的,堆序性正對(duì)應(yīng)了優(yōu)先隊(duì)列的優(yōu)先級(jí)。由此,優(yōu)先隊(duì)列就并不是一個(gè)線性的數(shù)據(jù)結(jié)構(gòu),其所有操作都是logn的時(shí)間復(fù)雜度。
了解完堆與優(yōu)先隊(duì)列的關(guān)系,我們就可以開(kāi)始討論如何實(shí)現(xiàn)優(yōu)先對(duì)列了。
堆的存儲(chǔ)方式
我們將一個(gè)堆從上到下從左到右(實(shí)際上這個(gè)順序也是堆一般的討論模式),從0開(kāi)始給每個(gè)節(jié)點(diǎn)編號(hào)。如下圖:
然后按照順序存儲(chǔ)進(jìn)一個(gè)線性的數(shù)組之中,那么這就算存儲(chǔ)好了~
簡(jiǎn)不簡(jiǎn)單?意不意外?是不是最開(kāi)始想到的是遞歸生成樹(shù)?但實(shí)際上因?yàn)槎研蛐缘拇嬖?,我們并不需要那么?fù)雜的存儲(chǔ)方式~
同樣的道理,我們反過(guò)來(lái)用一個(gè)數(shù)組建堆,也就是如上操作的逆操作而已。
問(wèn)題就來(lái)了,如何用一個(gè)無(wú)序的數(shù)組來(lái)建堆呢?這就要談到維護(hù)堆序性的兩種操作——上浮,下沉。
維護(hù)堆的方法
1、上浮操作
首先將一個(gè)無(wú)序的數(shù)組按下標(biāo)標(biāo)號(hào),然后開(kāi)始進(jìn)行前方所說(shuō)的建堆操作,我們建堆的過(guò)程便是主要用到上浮操作,每操作一步就要與父節(jié)點(diǎn)比較,如果大于(此處以大根堆為例子)父節(jié)點(diǎn),則與父節(jié)點(diǎn)進(jìn)行交換,然后跳轉(zhuǎn)到交換后的位置,繼續(xù)與父節(jié)點(diǎn)進(jìn)行比較,直到不大于父節(jié)點(diǎn)后,就算完成了一次調(diào)整。光說(shuō)肯定有些童鞋無(wú)法想象得那么明白,下面放圖!
這里用數(shù)組a[6] = {3,5,8,9,1,2}做模板,別多想,很隨機(jī)的數(shù)字罷了。
第一步,將下標(biāo)為0的節(jié)點(diǎn)做根節(jié)點(diǎn),就是3啦~
第二步,將下標(biāo)為1的節(jié)點(diǎn)也就是5作為3的左孩子~
很明顯啊,5要比它的父節(jié)點(diǎn)3要大,那么,交換位置~
再看5并沒(méi)有比它小的根節(jié)點(diǎn)了,那么繼續(xù)下一步~
第三步,將下標(biāo)為2的節(jié)點(diǎn)也就是8,放在5的下邊作為右孩子~
很明顯哦,8比它的父節(jié)點(diǎn)大,那么~,交換位置~
很明顯,8并沒(méi)有比它更小的父節(jié)點(diǎn)了,那么繼續(xù)下一步~
再接下去我就不講了,很簡(jiǎn)單,序號(hào)從上到下從左到右。
那么任一的一個(gè)節(jié)點(diǎn)如果它足夠大(?。鸵欢〞?huì)最底下一層爬到最大的根節(jié)點(diǎn),是不是上浮呢,生動(dòng)而形象,在建堆的時(shí)候每插入一個(gè)元素,就要對(duì)該元素進(jìn)行一次上浮調(diào)整,將其放在正確的地方。
相信聰明的童鞋已經(jīng)發(fā)現(xiàn)了,同層的節(jié)點(diǎn)不存在任何的關(guān)系!?。∩踔敛煌?jié)點(diǎn)的同層節(jié)點(diǎn)也不存在任何關(guān)系,每一個(gè)節(jié)點(diǎn)僅僅只是在其子堆中的最大值,即局部最大值。
2、下沉操作
該操作在隊(duì)列的基本操作,也就是彈出隊(duì)頂操作時(shí)所用,即刪除最大(?。└?jié)點(diǎn)的操作。
原理也很簡(jiǎn)單,將編號(hào)為0的節(jié)點(diǎn)與編號(hào)最大的節(jié)點(diǎn)權(quán)值互換,然后彈出編號(hào)最大的節(jié)點(diǎn)(此時(shí)即前一步的隊(duì)頂元素),此時(shí)再對(duì)隊(duì)頂節(jié)點(diǎn)進(jìn)行下沉操作:
先與左子樹(shù)進(jìn)行比較,按照堆序性交換,直到換回它應(yīng)在的位置,此時(shí)所有局部均為優(yōu)先隊(duì)列,其也維護(hù)完成。
上圖:
這里還是前面那個(gè)數(shù)組,順便也給大家看看建堆后的亞子~
a[6] = {3,5,8,9,1,2}
第一步, 將編號(hào)為0的節(jié)點(diǎn)與編號(hào)最大的節(jié)點(diǎn)權(quán)值互換
即將9與2進(jìn)行互換。
第二步, 彈出編號(hào)最大的節(jié)點(diǎn)(此時(shí)即前一步的隊(duì)頂元素)
即9
第三步, 對(duì)隊(duì)頂節(jié)點(diǎn)進(jìn)行下沉操作
即先和8,5進(jìn)行順序比較,按照優(yōu)先級(jí),明顯與8互換,換完后如下
再與3先比較,無(wú)法交換再與1比較~最后應(yīng)該是這個(gè)樣子的。
兩種操作方式也已經(jīng)說(shuō)完,這里就會(huì)有童鞋問(wèn)道,那么如何在數(shù)組中進(jìn)行所謂的上浮下沉,操作呢?
這里就有一個(gè)很重要的知識(shí)了,就是父節(jié)點(diǎn)和子節(jié)點(diǎn)在數(shù)組編號(hào)中的關(guān)系!
其實(shí)也并不難發(fā)現(xiàn),根據(jù)堆的性質(zhì),有如下的關(guān)系:
設(shè)某個(gè)節(jié)點(diǎn)編號(hào)為i:
其父節(jié)點(diǎn):dad = (i - 1) / 2
左/右子節(jié)點(diǎn):left = 2 * i + 1
right = 2 * i + 2
這樣大家就可以將上浮、下沉操作的每一步在數(shù)組中實(shí)現(xiàn)了!
附上代碼
#include<iostream> #include<cmath> #include<stdlib.h> #define bug cout<<"nug is here"<<endl; #include<vector> using namespace std; typedef size_t ull; //堆 template<typename P> class Heap{ private: vector<int> heap_elem;//堆容器 ull heap_depth;//深度 bool Priority; //優(yōu)先級(jí) ull heap_size; //容量 void Up_adjust(int now);//上浮調(diào)整 void Down_adjust(int now);//下沉調(diào)整 //now指代下標(biāo) public: //構(gòu)造堆 enum{max_heap = true, min_heap = false}; Heap(vector<P> &l, bool priority = max_heap){ heap_size = l.size(); heap_depth = log2(heap_size); Priority = priority;//設(shè)置優(yōu)先級(jí) for(int i = 0;i < heap_size;i++){ heap_elem.push_back(l[i]); Up_adjust(i);//上浮調(diào)整 } } Heap(int a[], ull n, bool priority = max_heap){ heap_size = n; heap_depth = log2(heap_size); Priority = priority;//設(shè)置優(yōu)先級(jí) for(int i = 0;i < n;i++){ heap_elem.push_back(a[i]); Up_adjust(i);//上浮調(diào)整 } } Heap(){ heap_size = 0; heap_depth = 0; Priority = max_heap; }; //堆的成員函數(shù) ull Depth(){ return heap_depth; } ull Size(){ return heap_size; } void Push(P x){ heap_elem.push_back(x); ++heap_size; heap_depth = log2(heap_size); swap(heap_elem[heap_elem.size() - 1], heap_elem[heap_size - 1]);//將加入的元素放入有效位 Up_adjust(heap_size - 1); } void Pop(){ heap_depth = log2(heap_size); swap(heap_elem[--heap_size], heap_elem[0]);//將第一個(gè)元素與最后一個(gè)元素交換,并且縮短有效位數(shù) //其實(shí)這里可以用vector的函數(shù)pop_back(),相應(yīng)的上面的Push函數(shù)也不用換位置,但是這樣更快 Down_adjust(0); } P &Top(){ return heap_elem[0]; } void show_as_tree(){//以樹(shù)的形式輸出 int _size = max(log10(heap_elem[0]),log10(heap_elem[heap_size - 1])) + 1; ull max_size = (pow(2, heap_depth) * 2) * _size; ull _max_size = _size * pow(2, heap_depth + 1); int start = -1; for(int i = 0;i <= heap_depth;i++){ max_size >>= 1; max_size++; if(i == heap_depth) cout<<heap_elem[++start]; else printf("%*d",max_size,heap_elem[++start]); int w = pow(2, i); for(int j = 1;j < w && start < heap_size - 1;j++) printf("%*d",_max_size,heap_elem[++start]); _max_size >>= 1; _max_size++; printf("\n"); } } void show_as_array(){//數(shù)組方式輸出 for(int i = 0;i < heap_size;i++) cout<<heap_elem[i]<<" "; cout<<endl; } }; //上浮調(diào)整 template<typename P> void Heap<P>::Up_adjust(int now){ if(Priority) while(now > 0 && heap_elem[now] > heap_elem[(now - 1) / 2]){//如果當(dāng)前節(jié)點(diǎn)的權(quán)值比父親大 swap(heap_elem[now], heap_elem[(now - 1) / 2]);//交換 now = (now - 1) / 2; } else while(now > 0 && heap_elem[now] < heap_elem[(now - 1) / 2]){ swap(heap_elem[now], heap_elem[(now - 1) / 2]); now = (now - 1) / 2; } } //下沉調(diào)整 template<typename P> void Heap<P>::Down_adjust(int now){ ull left = now * 2 + 1; ull right; while(left < heap_size){//能換的時(shí)候 left = now * 2 + 1; right = now * 2 + 2; if(Priority){ if(heap_elem[now] < heap_elem[left]){//比左孩子小,下沉 swap(heap_elem[now], heap_elem[left]); now = left; } else if(right < heap_size){//比右孩子小,下沉 if(heap_elem[now] > heap_elem[right]){ swap(heap_elem[now], heap_elem[right]); now = right; } } } else{ if(heap_elem[now] > heap_elem[left]){//比左孩子大,下沉 swap(heap_elem[now], heap_elem[left]); now = left; } else if(right > heap_size){//比右孩子大,下沉 if(heap_elem[now] < heap_elem[right]){ swap(heap_elem[now], heap_elem[right]); now = right; } } } } } int main(){ int a[6] = {3,5,8,9,1,2}; Heap<int> h(a, 6, true); //輸出堆 h.show_as_tree(); // h.Push(12); // h.show_as_tree(); // // h.Pop(); // h.show_as_tree(); // // cout<<h.Top()<<endl; // vector<int> a; // Heap<int> h(a, Heap<int>::max_heap); // for(int i=0;i < 10;++i) // h.Push(rand()%100); // // h.show_as_tree(); return 0; }
按照前面那個(gè)數(shù)組運(yùn)行,結(jié)果如下:
是不是很神奇呢?
以上就是C++實(shí)現(xiàn)優(yōu)先隊(duì)列的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C++優(yōu)先隊(duì)列的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- C++ 實(shí)現(xiàn)優(yōu)先隊(duì)列的簡(jiǎn)單實(shí)例
- c++優(yōu)先隊(duì)列(priority_queue)用法詳解
- c++優(yōu)先隊(duì)列用法知識(shí)點(diǎn)總結(jié)
- C++優(yōu)先隊(duì)列用法案例詳解
- 詳解c++優(yōu)先隊(duì)列priority_queue的用法
- C++高級(jí)數(shù)據(jù)結(jié)構(gòu)之優(yōu)先隊(duì)列
- C++示例詳解Prim算法與優(yōu)先隊(duì)列
- 深入了解C++優(yōu)先隊(duì)列(priority_queue)的使用方法
- C++中STL的優(yōu)先隊(duì)列priority_queue詳解
- C++優(yōu)先隊(duì)列的使用小結(jié)
- C++的實(shí)現(xiàn)優(yōu)先隊(duì)列(Priority?Queue)的實(shí)現(xiàn)
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)的PNPoly算法代碼例子
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)的PNPoly算法代碼例子,PNPoly算法j是判斷一個(gè)坐標(biāo)點(diǎn)是否在不規(guī)則多邊形內(nèi)部的算法,需要的朋友可以參考下2014-07-07C語(yǔ)言實(shí)現(xiàn)搶紅包程序代碼精簡(jiǎn)版
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)搶紅包程序代碼的精簡(jiǎn)版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07C語(yǔ)言操作符進(jìn)階教程(表達(dá)式求值隱式類型轉(zhuǎn)換方法)
這篇文章主要為大家介紹了C語(yǔ)言操作符進(jìn)階教程(表達(dá)式求值隱式類型轉(zhuǎn)換方法)2022-02-02C++中declspec(dllexport)和declspec(dllimport)?的用法介紹
這篇文章介紹了C++中declspec(dllexport)和declspec(dllimport)?的用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04