1. 程式人生 > >STL原始碼剖析(四)heap--priority_queue

STL原始碼剖析(四)heap--priority_queue

文章目錄

1. heap概述

heap,即我們在資料結構中所說的堆;在STL中我們所應用到priority queue中作為其操作實現的是binary max heap(最大二叉堆),是一種complete binary tree(完全二叉樹)

  • heap可分為max-heap以及min-heap,而在此文中所要介紹的是max-heap,所謂max-heap,即每個節點的鍵值都大於或等於其子節點鍵值,其最大值在根節點,總是位於底層array或vector的首部

2. 為何選擇heap作為priority queue的底層機制?

  • 首先了解priority queue,允許使用者以任何次序將任何元素推入容器內,但取出時一定是從優先權最高的元素開始取
  • 下面我們來分析一下為何採用heap:
  1. 採用list:當元素插入操作達到常數時間,但取值卻需要對整個list進行掃描;將list先排序,使得list由大到小,這樣取值與刪除可達到最高效率,但元素插入卻只有線性表現,不能兩者兼得
  2. 採用binary search tree:元素的插入與取值可達到O(logN),但未免小題大做,況且binary search tree的輸入需要足夠的隨機性,而且binary search tree並不容易實現
  • 結合上述,又能滿足priority queue的條件,又能達到高效率,binary heap就這樣作為了其底層實現

3. binary heap

binary heap就是一種complete binary tree,而complete binary tree又是什麼結構呢?

在這裡插入圖片描述

complete binary tree 整棵樹內沒有任何節點漏洞,這帶來一個極大的好處:我們可以利用array來儲存所有節點。假設動用一個小技巧,將array的#0元素保留(或設為無限大或無限小),那麼當complete binary tree中的某個節點位於array的i處時,其左子節點必位於array的2i處,其右子節點比位於array的2i+1處,其父節點必位於“i/2”處。通過這麼簡單的位置規則,array可以輕易實現出complete binary tree。這種以array表述tree的方式,我們稱為隱式表述法。
這麼一來,我們需要的工具就很簡單了:一個array 和 一組 heap演算法(用來進行元素操作,並將某一整組資料排列成一個heap)。array的缺點是無法動態改變大小,而heap卻需要這項功能,因此,以vector代替array是更好的選擇。

4. heap演算法

為了滿足complete binary tree的條件,新加入的元素一定要放在最下一層作為葉節點,並填補在由左至右的第一個空格,也就是把新元素插入在頂層vector的end()處

  1. push_heap演算法:上溯
    在這裡插入圖片描述
  • percolate up(上溯)程式:將新節點拿來與其父節點比較,如果其鍵值比父節點大,就父子對換位置,如此一直上溯,直到不需要對換或直到根節點為止

  • 當push_heap被呼叫時,新元素應已置於底部容器的最尾端

  • 具體push_heap實現參考原始碼

  1. pop_heap演算法:下溯+上溯
    在這裡插入圖片描述
  • percolate_down(下溯)程式:將空間節點和其較大子節點“對調”並持續下放,直至葉節點為止,然後將前述被割捨的元素值設給空洞節點,然後再執行一次“上溯”程式
  • 在pop_heap時,除了將最大元素取出之外,還需呼叫__adjust_heap進行堆調整
  • 當呼叫完pop_heap時,最大元素只是被放置於底部容器的最尾端,尚未被取走,若要取其值,則需呼叫back(),若要刪除,則需呼叫pop_back()
  • 具體pop_heap實現參考原始碼
  1. sort_heap演算法
    在這裡插入圖片描述
  • sort_heap,就是對pop_heap的重複呼叫:
template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last)
{
	while (last - first > 1 )
		pop_heap(first, last--);
}
  1. make_heap演算法
  • 這個演算法用來將一段現有資料轉換為一個heap
  • make_heap實現參考原始碼

5. priority_queue

priority_queue是一個容器介面卡(與stack、queue一樣),以vector作為其底層結構,以heap演算法作為其操作實現

  • priority_queue實現:
template <class T, class Sequence = vector<T>,
	class Compare = less<typename Sequence::value_type> >
class priority_queue {
public:
	//vector自定義型別
protected:
	Sequence c;        //底層容器
	Compare comp; //元素大小比較標準
public:
	//...
	//用到heap泛型演算法作為其實現
	//定義一個priority_queue實則是一個建堆的過程
	template <class InputIterator>
	priority_queue( InputIterator first,  InputIterator last, const Compare& x)
	  : c (first, last), comp(x) { make_heap(c.begin(), c.end(), comp);}
	priority_queue( InputIterator first,  InputIterator last)
	  : c (first, last) { make_heap(c.begin(), c.end(), comp);}
	//....
	void push(const value_type& x)  {
		__STL_TRY {
		//先利用底層容器的	push_back將新元素推入末端,再重排heap
			c.push_back(x);
			push_heap(c.begin(), c.end(), comp);
		}
		__STL_UNWIND(c.clear());
	}
	void pop()  {
		__STL_TRY {
		//先從heap內取出一個元素,並不是簡單的彈出,而是重排heap,然後在以底層容器的pop_back取得被彈出的元素
			pop_heap(c.begin(), c.end(), comp);
			c.pop_back();
		}
		__STL_UNWIND(c.clear());
 	}
 };