1. 程式人生 > >C++標準庫中的堆-heap

C++標準庫中的堆-heap

前言

本文介紹如何使用STL裡的heap(堆)演算法。第一次接觸heap這種資料結構是在大學的資料結構教材上,它是一棵完全二叉樹。在STL中,heap是演算法的形式提供給我們使用的。包括下面幾個函式:

  • make_heap: 根據指定的迭代器區間以及一個可選的比較函式,來建立一個heap. O(N)

  • push_heap: 把指定區間的最後一個元素插入到heap中. O(logN)

  • pop_heap: 彈出heap頂元素, 將其放置於區間末尾. O(logN)

  • sort_heap:堆排序演算法,通常通過反覆呼叫pop_heap來實現. N*O(logN)

C++11加入了兩個新成員:

  • is_heap: 判斷給定區間是否是一個heap. O(N)

  • is_heap_until: 找出區間中第一個不滿足heap條件的位置. O(N)

因為heap以演算法的形式提供,所以要使用這幾個api需要包含 #include <algorithm>

heap相關演算法的使用

make_heap

STL中的通過make_heap建立的堆,預設是大頂堆(max heap),即每個非葉子節點元素的值均不”小於”(預設使用<作為比較準則)其左右孩子節點。要改變堆的建立準則,可以自己制定一個比較函式,如下第二個版本的make_heap宣告:

// 1
template<
class RandomIt > void make_heap( RandomIt first, RandomIt last ); // 2 template< class RandomIt, class Compare > void make_heap( RandomIt first, RandomIt last, Compare comp );

示例程式碼:

預設的make_heap

vector<int>vi{6, 1, 2, 5, 3, 4};
printContainer(vi, "vi: ");             // vi: 6 1 2 5 3 4
make_heap(vi.begin(), vi.end()); printContainer(vi, "vi: "); // vi: 6 5 4 1 3 2

需要注意的是,make_heap需使用隨機迭代器來建立heap。

自己指定比較函式的make_heap

vector<int>v2{6, 1, 2, 5, 3, 4};
printContainer(v2, "v2 before make_heap: ");
make_heap(v2.begin(), v2.end(), greater<int>());
printContainer(v2, "v2 after make_heap: ");

輸出:

v2before make_heap: 6 1 2 5 3 4
v2 after make_heap: 1 3 2 5 6 4

這裡使用了greater<int>()來代替預設的less<int>()來建立int型別的heap。可以按層次遍歷的順序把這個heap畫出來,可以看到它跟預設情況剛好相反,會是一個小頂堆。

push_heap

// 1
template<class RandomIt >
void push_heap( RandomIt first, RandomIt last );

// 2
template< class RandomIt, class Compare >
void push_heap( RandomIt first, RandomIt last, Compare comp );

make_heap類似,push_heap也有兩個版本,其中有一個版本可以指定堆的比較函式,並且也是以一對迭代器指定的區間來進行操作。

示例程式碼:

vector<int>v1{6, 1, 2, 5, 3, 4};
make_heap(v1.begin(), v1.end());

v1.push_back(200);
printContainer(v1, "before push_heap: ");        // before push_heap: 6 5 4 1 3 2 200
push_heap(v1.begin(), v1.end());
printContainer(v1, "after push_heap: ");         // after push_heap: 200 5 6 1 3 2 4

先用make_heap來構造一個堆,然後在容器末尾追加元素之後,把新的迭代器區間傳給push_heap,這樣新尾部元素也被新增到堆中。

注意:使用push_heap(f, l)的話,呼叫者需要確保[f, l-1)已經是一個堆. push_heap(f, l)僅僅會把*(l-1)插入到[f, l-1)這個區間形成的堆中,時間複雜度是O(logN).

即, STL書中所述:the caller has to ensure, on entry, the elements in the range [begin, end) are a heap(according to the same sorting criterion).

如果一開始不用make_heap處理,直接push_heap會怎樣?

vector<int>v2{6, 1, 2, 5, 3, 4};
v2.push_back(200);
printContainer(v2, "v2 before push_heap: ");
push_heap(v2.begin(), v2.end());
printContainer(v2, "v2 after push_heap: ");

輸出:

v2before push_heap: 6 1 2 5 3 4 200
v2 after push_heap: 200 1 6 5 3 4 2

可以看出直接呼叫push_heap的結果並不是一個heap. 下面要提到的pop_heap也有同樣的要求。

pop_heap

// 1	
template<class RandomIt >
void pop_heap( RandomIt first, RandomIt last );

// 2
template< class RandomIt, class Compare >
void pop_heap( RandomIt first, RandomIt last, Compare comp );

Swaps the value in the position first and the value in the position last-1 and makes the subrange [first, last-1) into a max heap. This has the effect of removing the first (largest) element from the heap defined by the range [first, last).

它的作用是:交換*first*(last-1), 然後把[first, last-1)建成一個max heap. 也就是說把堆頂的最大元素交換到區間尾,然後把除了尾部的元素的剩餘區間重新調整成堆。

需要注意的是,呼叫者要保證,在呼叫pop_heap[first, last)已經是一個堆(使用相同的排序準則)。

vector<int>v1{6, 1, 2, 5, 3, 4};
make_heap(v1.begin(), v1.end());
printContainer(v1, "after make_heap: ");

pop_heap(v1.begin(), v1.end());
printContainer(v1, "after pop_heap: ");
auto largest = v1.back();
psln(largest);
v1.pop_back();
printContainer(v1, "delete largest: ");

輸出:

aftermake_heap: 6 5 4 1 3 2
after pop_heap: 5 3 4 1 2 6
largest = 6
delete largest: 5 3 4 1 2

sort_heap

// 1
template<class RandomIt >
void sort_heap( RandomIt first, RandomIt last );

// 2
template< class RandomIt, class Compare >
void sort_heap( RandomIt first, RandomIt last, Compare comp );

sort_heap即經典的堆排序演算法,通過每次彈出堆頂直到堆為空,依次被彈出的元素就組成了有序的序列了。STL中的priority_queue即使用heap的這個特性來實現。

使用sort_heap(f, l)處理過的區間因為已經有序,就不再是一個heap了。

vector<int>v1{6, 1, 2, 5, 3, 4};
printContainer(v1, "before sort_heap: ");       

make_heap(v1.begin(), v1.end());

sort_heap(v1.begin(), v1.end());
printContainer(v1, "after sort_heap: ");

輸出:

beforesort_heap: 6 1 2 5 3 4
after sort_heap: 1 2 3 4 5 6

注意:呼叫者仍需確保區間已經是一個堆。

is_heap

// (1)	(since C++11)
template<class RandomIt >
bool is_heap( RandomIt first, RandomIt last );

// (2)	(since C++11)
template< class RandomIt, class Compare >
bool is_heap( RandomIt first, RandomIt last, Compare comp );

示例:

vector<int>v1{6, 1, 2, 5, 3, 4};
psln(is_heap(v1.begin(), v1.end()));

pln("after make_heap");

make_heap(v1.begin(), v1.end());
psln(is_heap(v1.begin(), v1.end()));

輸出:

is_heap(v1.begin(),v1.end()) = 0
after make_heap
is_heap(v1.begin(), v1.end()) = 1

is_heap_until

原型:

// (1)	(since C++11)
template<class RandomIt >
RandomIt is_heap_until( RandomIt first, RandomIt last );

// (2)	(since C++11)
template< class RandomIt, class Compare >
RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );

示例:

vector<int>v1{6, 1, 2, 5, 3, 4};
auto iter = is_heap_until(v1.begin(), v1.end());
psln(*iter);        // *iter = 5    5 是第一個不滿足heap條件的位置。

make_heap(v1.begin(), v1.end());
iter = is_heap_until(v1.begin(), v1.end());
ASSERT_TRUE(iter == v1.end());

總結

  1. 建堆,make_heap

  2. 堆操作:增加元素(push_heap),刪除元素(pop_heap), 排序(sort_heap), 均要求區間已經是一個heap,並且是與當前操作使用相同的排序準則

  3. is_heapis_heap_until當做輔助判斷函式來用

  4. 所有的heap演算法操作的區間都需要是隨機迭代器組成