STL——heap(heap並不屬於STL容器組件)C++實現

heap並不歸屬於STL容器組件,它是個幕後英雄,扮演priority queue的助手,priority queue允許用戶以任何次序將任何元素放入容器內,但是取出時一定是從優先級最高的元素開始取,heap正是具有這樣的特性,適合作爲priority queue的底層機制

heap的四種算法:push_heappop_heapsort_heapmake_heap,對應插入、刪除、排序、建堆, 下述算法理解都以大頂堆爲例

由於堆是一棵完全二叉樹,所以可以很輕易地用一個數組存儲堆中的每一個元素,並且由子結點訪問到其父親結點和由父親結點訪問到其子結點。下面給出圖來說明該表示方法:
在這裏插入圖片描述

數據結構上heap的實現👇

STL的魅力之一在於能夠對特定類型的數據結構提供泛型化,並且提供高效的函數接口。STL不僅實現了heap算法,而且還彌補上述算法的不足。
平時的heap算法都是針對一個靜態數組,而STL以動態數組vector爲底層實現,但提供的依舊是建堆操作、插入操作、刪除操作、堆排序操作

//底層用靜態數組實現的heap

#include<iostream>
#include<algorithm>
#define maxn 1001   //heap's size

using namespace std;

struct Heap {
	int size;	// number of elements in array
	int *array;
	Heap() {	//初始化
		size = 0;
		array = new int[maxn];
	}
	Heap(int n) {	//init
		size = 0;
		array = new int[n];
	}
	~Heap() {	//free memory
		delete array;
	}
	bool empty() {
		if(size != 0) return false;
		return true;
	}
	
	int max() {
		if(empty()) return -1;
		return array[1];
	}
};

而STL底層用vector實現

#include<vector>
#include<iostream>
#include<algorithm>//有heap算法
using namespace std;
int main()
{
	int ia[9] = {0,1,2,3,4,5,6,7,8,9};
	vector<int> ivec(ia, ia+9);//底層用vector
	make_heap(ivec.begin(),ivec.end());
	...
	
}

①數組新加入一個元素:先插入在數組的最後一個,在堆上看就是這棵完全二叉樹的底層最左邊葉子節點,要符合大頂堆,則不斷往上比較,如果當前index值大於父節點index/2則交換,

	void insert(int value) {
		array[++size] = value;
		int index = size;
		while(index > 1) {
			if(array[index] > array[index/2]) swap(array[index],array[index/2]);
			index /= 2;
		}
	}

而STL用push_heap算法實現

inline void push_heap(b,e,cmp=greater<T>() )

向堆中插入元素分爲兩個步驟:
(1)先通過push_back將待插入的元素插入到底層容器的末端
(2)再調用push_heap(b,e,cmp)函數堆新插入的元素做向上調整。

②刪除堆頂元素:將最後一個元素放到第一個元素處,再從上到下調整使符合大頂堆要求,

	void del() 
	{
		if(empty()) return;
		swap(array[1],array[size--]);
		int index = 1;
		while(2*index <= size) 
		{
			int next = 2*index;
			if(next < size && array[next+1] > array[next]) next++;//和左右子節點中較大的比較交換
			if(array[index] < array[next]) 
			{
				swap(array[index],array[next]);
				index = next;//交換並往下比較,直到到葉子節點
			} else break;
		}
	}

STL中的堆頂元素刪除操作:
刪除算法:inline void pop_heap(b,e,cmp=greater() )
要實現堆的真正刪除操作,分兩步進行:
(1)先調用pop_heap函數將首部的元素與尾部元素交換,再將原尾部的元素做向下調整操作。此時,原堆頂元素被放置在最後一個位置,並未從底層容器中刪除。
(2)若要實現真正的元素刪除,可以調用底層容器的pop_back函數。
所以,在調用pop_heap函數後,若要實現元素真正從堆中刪除,還需要調用底層容器的pop_back函數。

③建堆:對非葉子節點進行進行調整從而得到一個大頂堆

void buildHeap(int array[],int size) {
	int i,tmp,index;
	for(i = size/2; i >= 1; i--) {//對每一個非葉子節點從上到下調整
		tmp = array[i];
		index = 2*i;
		while(index <= size) {//堆化
			if(index < size && array[index+1] > array[index]) index++;
			if(array[index] < tmp) break;
			array[index/2]  = array[index];
			index *= 2;
		}
		array[index/2] = tmp;
	}
}

STL建堆算法:inline void make_heap( b, e , cmp=greater() )
該函數對[b,e)範圍中的元素建立一個堆,所建的堆的類型由cmp決定,默認爲大頂堆。

④堆排序:獲取堆頂元素, 與沒排序元素的最後一個交換, 當整個程序執行完畢就會得到一個遞增序列

void heapsort(int array[],int size)
{
	buildheap(array, size);//先建堆得到一個合格的堆
	int i = size;
	while(true)
	{
		if(i<=1)
			break;
		swap(array, 1, i);//和最後一個交換
		i--;//始終是未排序的最後一個
		//heapify();再對前面的元素進行堆化
	}
}

STL的堆排序算法:

inline void sort_heap(b,e,cmp=greater<T>() )

堆排序實際上是對堆中元素不斷地假刪除操作,只不過在刪除過程中,[b,e)中的e每刪除一次,就要做–e的更新。

小頂堆的測試程序

小頂堆取決於cmp這個仿函數的定義

#include<iostream>
#include<vector>
#include<list>
#include<algorithm>//後面用到copy函數和heap相關函數
#include<iterator>//後面用到迭代器ostream_iterator
#include<functional>//後面用到了一個比較的仿函數greater<T>
using namespace std;

typedef vector<int> Vint;//vector底層
void print(const Vint& vec)//輸出當前vector容器中的元素
{
	cout<<"容器內的元素爲:";
	copy(vec.begin(),vec.end(),ostream_iterator<int>(cout," "));//將容器內的元素輸出到標準輸出設備上
	cout<<endl;
	cout<<"容器內元素的個數爲:"<<vec.size()<<endl<<endl;;
}
 
bool cmp(const int &a,const int &b)
{
	return a>b;//大頂堆,則cmp相等於greater<int>(),注意不是greater<int>,前者是一個對象,後者是一個類
}
int main()
{
	int arr[]={3,2,1,9,4,12,15,7};
	vector<int>vec(arr,arr+sizeof(arr)/sizeof(int));//創建一個vector容器對象,將數組的副本壓入到該容器中
	cout<<"-----------初始狀態---------------"<<endl;
	print(vec);//將最初的vector容器的內容輸出
 
	cout<<"-------------建堆----------------"<<endl;
	make_heap(vec.begin(),vec.end(),cmp);//新建一個小頂堆
	//⭐⭐上行代碼等價於make_heap(vec.begin(),vec.end(),greater<int>()⭐⭐
	print(vec);
	
	cout<<"----------彈出堆頂元素-----------"<<endl;
	pop_heap(vec.begin(),vec.end(),cmp);//這裏也要加cmp,因爲彈出之後要給出向下調整的規則,否則系統會調用默認的最大堆調整方法
	print(vec);
 
	cout<<"--------向堆中插入值6的方法--------"<<endl;
	vec.push_back(6);//先將待插入的值放在容器的末尾
	push_heap(vec.begin(),vec.end(),cmp);//再最堆進行向下調整
	print(vec);
 
	cout<<"----------執行堆排序--------------"<<endl;
	sort_heap(vec.begin(),vec.end(),cmp);
	print(vec);
	return 0;
}

優先隊列
需要調動到#include<queue>,以下以a爲例:
大根堆的調用:priority_queue<int>a;
小根堆的調用:priority_queue<int,vector<int>,greater<int> > a;

a.size():返回堆內元素個數。
a.empty():如果堆爲空,返回真,否則返回假。
a.top():返回堆頂元素。
a.pop():刪除堆頂元素,自動整理。
a.push(x):插入一個元素x,自動整理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章