本章開始介紹了堆的基本概念,而後引入最大堆和最小堆的概念。全章採用最大堆來介紹堆的操做,兩個重要的操做是調整最大堆和建立最大堆,接着着兩個操做引進了堆排序,最後介紹了採用堆實現優先級隊列。node
一、堆ios
堆給人的感受是一個二叉樹,可是其本質是一種數組對象,由於對堆進行操做的時候將堆視爲一顆徹底二叉樹,樹種每一個節點與數組中的存放該節點值的那個元素對應。因此堆又稱爲二叉堆,堆與徹底二叉樹的對應關係以下圖所示:算法
一般給定節點i,能夠根據其在數組中的位置求出該節點的父親節點、左右孩子節點,這三個過程通常採用宏或者內聯函數實現。書上介紹的時候,數組的下標是從1開始的,全部可到:PARENT(i)=i/2 LEFT(i) = 2*i RIGHT(i) = 2*i+1。數組
根據節點數值知足的條件,能夠將分爲最大堆和最小堆。最大堆的特性是:除了根節點之外的每一個節點i,有A[PARENT(i)] >= A[i],最小堆的特性是:除了根節點之外的每一個節點i,有A[PARENT(i)] >=A[i]。數據結構
把堆當作一個棵樹,有以下的特性:函數
(1)含有n個元素的堆的高度是lgn。測試
(2)當用數組表示存儲了n個元素的堆時,葉子節點的下標是n/2+1,n/2+2,……,n。ui
(3)在最大堆中,最大元素該子樹的根上;在最小堆中,最小元素在該子樹的根上。spa
二、保持堆的性質操作系統
堆個關鍵操做過程是如何保持堆的特有性質,給定一個節點i,要保證以i爲根的子樹知足堆性質。書中以最大堆做爲例子進行講解,並給出了遞歸形式的保持最大堆性的操做過程MAX-HEAPIFY。先從看一個例子,操做過程以下圖所示:
從圖中能夠看出,在節點i=2時,不知足最大堆的要求,須要進行調整,選擇節點2的左右孩子中最大一個進行交換,而後檢查交換後的節點i=4是否知足最大堆的要求,從圖看出不知足,接着進行調整,直到沒有交換爲止。書中給出了遞歸形式的爲代碼,我用C語言實現以下所示:
void adjust_max_heap_recursive(int *datas,int length,int i) { int left,right,largest; int temp; left = LEFT(i); //left child right = RIGHT(i); //right child //find the largest value among left and rihgt and i. if(left<=length && datas[left] > datas[i]) largest = left; else largest = i; if(right <= length && datas[right] > datas[largest]) largest = right; //exchange i and largest if(largest != i) { temp = datas[i]; datas[i] = datas[largest]; datas[largest] = temp; //recursive call the function,adjust from largest adjust_max_heap(datas,length,largest); } }
課後習題要求給出其非遞歸的形式,我想了半天,才搞出來,領悟能力有限啊。非遞歸就要考慮要循環進行實現,須要考慮的是循環結束條件是什麼。對一個給定的節點i,要對其進行調整使其知足最大堆的性質。總的思想是先找出節點i的左右孩子節點,而後從三者中找到最大的節點,若是找到的最大節點就是i,說明i節點知足堆的性質,此時循環就結束了。若是找到的最大節點不是節點i,那麼這個時候就要將最大的節點(設爲largest)與節點i進行交換,而後從largest節點開始循環進行調整,直到知足條件爲止。給出非遞歸的調整堆程序以下:
void adjust_max_heap(int *datas,int length,int i) { int left,right,largest; int temp; while(1) { left = LEFT(i); //left child right = RIGHT(i); //right child //find the largest value among left and rihgt and i. if(left <= length && datas[left] > datas[i]) largest = left; else largest = i; if(right <= length && datas[right] > datas[largest]) largest = right; //exchange i and largest if(largest != i) { temp = datas[i]; datas[i] = datas[largest]; datas[largest] = temp; i = largest; continue; } else break; } }
三、建堆
創建最大堆的過程是自底向上地調用最大堆調整程序將一個數組A[1.....N]變成一個最大堆。將數組視爲一顆徹底二叉樹,從其最後一個非葉子節點(n/2)開始調整。調整過程以下圖所示:
書中給出了建立堆的爲代碼,我用C語言實現以下:
void build_max_heap(int *datas,int length) { int i; //build max heap from the last parent node for(i=length/2;i>0;i--) adjust_max_heap(datas,length,i); }
四、堆排序算法
堆排序算法過程爲:先調用建立堆函數將輸入數組A[1...n]形成一個最大堆,使得最大的值存放在數組第一個位置A[1],而後用數組最後一個位置元素與第一個位置進行交換,並將堆的大小減小1,並調用最大堆調整函數從第一個位置調整最大堆。給出堆數組A={4,1,3,16,9,10,14,8,7}進行堆排序簡單的過程以下:
(1)建立最大堆,數組第一個元素最大,執行後結果下圖:
(2)進行循環,從length(a)到2,並不斷的調整最大堆,給出一個簡單過程以下:
書中給出了對排序爲代碼,我用C語言實現以下所示
void heap_sort(int *datas,int length) { int i,temp; //bulid max heap build_max_heap(datas,length); i=length; //exchange the first value to the last unitl i=1 while(i>1) { temp = datas[i]; datas[i] = datas[1]; datas[1] =temp; i--; //adjust max heap,make sure the fisrt value is the largest adjust_max_heap(datas,i,1); } }
結合上面的調整堆和建立堆 的過程,寫個簡單測試程序連續堆排序,程序以下所示:
#include <stdio.h> #include <stdlib.h> //array's index begins 1,not 0 #define PARENT(i) (i/2) #define LEFT(i) (i*2) #define RIGHT(i) (i*2+1) #define NOTNUSEDATA -65536 void adjust_max_heap(int *datas,int length,int i); void adjust_max_heap_recursive(int *datas,int length,int i); void build_max_heap(int *datas,int length); void heap_sort(int *datas,int length); int main() { int i; //array's index begin 1 int datas[11] = {NOTNUSEDATA,5,3,17,10,84,19,6,22,9,35}; heap_sort(datas,10); for(i=1;i<11;++i) printf("%d ",datas[i]); printf("\n"); exit(0); } void adjust_max_heap_recursive(int *datas,int length,int i) { int left,right,largest; int temp; left = LEFT(i); //left child right = RIGHT(i); //right child //find the largest value among left and rihgt and i. if(left<=length && datas[left] > datas[i]) largest = left; else largest = i; if(right <= length && datas[right] > datas[largest]) largest = right; //exchange i and largest if(largest != i) { temp = datas[i]; datas[i] = datas[largest]; datas[largest] = temp; //recursive call the function,adjust from largest adjust_max_heap(datas,length,largest); } } void adjust_max_heap(int *datas,int length,int i) { int left,right,largest; int temp; while(1) { left = LEFT(i); //left child right = RIGHT(i); //right child //find the largest value among left and rihgt and i. if(left <= length && datas[left] > datas[i]) largest = left; else largest = i; if(right <= length && datas[right] > datas[largest]) largest = right; //exchange i and largest if(largest != i) { temp = datas[i]; datas[i] = datas[largest]; datas[largest] = temp; i = largest; continue; } else break; } } void build_max_heap(int *datas,int length) { int i; //build max heap from the last parent node for(i=length/2;i>0;i--) adjust_max_heap(datas,length,i); } void heap_sort(int *datas,int length) { int i,temp; //bulid max heap build_max_heap(datas,length); i=length; //exchange the first value to the last unitl i=1 while(i>1) { temp = datas[i]; datas[i] = datas[1]; datas[1] =temp; i--; //adjust max heap,make sure the fisrt value is the largest adjust_max_heap(datas,i,1); } }
程序測試結果以下所示:
從結果能夠看出按照最大堆進行堆排序最終使得結果是從小到大排序(非遞減的)。
堆排序算法時間複雜度:調整堆過程知足遞歸式T(n)<=T(2n/3)+θ(1),有master定義能夠知道T(n) = O(lgn),堆排序過程當中執行一個循環,調用最大堆調整函數,總的時間複雜度爲O(nlgn)。
五、問題
(1)在建立最大堆的過程當中,爲何從最後一個非葉子節點(n/2)開始到第一個非葉子(1)結束,而不是從第一個非葉子節點(1)到最後一個非葉子節點(n/2)結束呢?
個人想法是:若是是從第一個非葉子節點開始建立堆,有可能致使建立的堆不知足堆的性質,使得第一個元素不是最大的。這樣作只是使得該節點的和其左右孩子節點知足堆性質,不能確保整個樹知足堆的性質。若是最大的節點在葉子節點,那麼將可能不會出如今根節點中。例以下面的例子:
從圖中能夠看出,從第一個非葉子節點開始建立最大堆,最後獲得的結果並非最大堆。而從最後一個非葉子節點開始建立堆時候,可以保證該節點的子樹都知足堆的性質,從而自底向上進行調整堆,最終使得知足最大堆的性質。
第六章:優先級隊列
一、概述
隊列是一種知足先進先出(FIFO)的數據結構,數據從隊列頭部取出,新的數據從隊列尾部插入,數據之間是平等的,不存在優先級的。這個就相似於普通老百姓到火車站排隊買票,先來的先買票,每一個人之間是平等的,不存在優先的權利,整個過程是固定不變的。而優先級隊列能夠理解爲在隊列的基礎上給每一個數據賦一個權值,表明數據的優先級。與隊列相似,優先級隊列也是從頭部取出數據,從尾部插入數據,可是這個過程根據數據的優先級而變化的,老是優先級高的先出來,因此不必定是先進先出的。這個過就相似於買火車票時候軍人比普通人優先買,雖然軍人來的晚,可是軍人的優先級比普通人高,老是可以先買到票。一般優先級隊列用在操做系統中的多任務調度,任務優先級越高,任務優先執行(相似於出隊列),後來的任務若是優先級比之前的高,則須要調整該任務到合適的位置,以便於優先執行,整個過程老是使得隊列中的任務的第一任務的優先級最高。
優先級隊列有兩種:最大優先級隊列和最小優先級隊列,這兩種類別分別能夠用最大堆和最小堆實現。書中介紹了基於最大堆實現的最大優先級隊列。一個最大優先級隊列支持的操做以下操做:
INSERT(S,x):把元素x插入到集合S
MAXIMUM(S):返回S中具備最大關鍵字的元素
EXTRACT_MAX(S):去掉並返回S中的具備最大關鍵字的元素
INCREASE_KEY(S,x,k):將元素x的關鍵字的值增長到k,這裏k值不能小於x的原關鍵字的值。
二、最大優先級隊列操做實現
採用最大堆實現最大優先級隊列,關於最大堆能夠參見上文。
(1)HEAP_MAXIMUM用O(1)時間實現MAXIMUM(S)操做,即返回最大堆第一個元素的值便可(return A[1])。
(2)HEAP_EXTRACT_MAX實現EXTRACT_MAX操做,刪除最大堆中第一個元素,而後調整堆。操做過程以下:將最堆中最後一個元素複製到第一個位置,刪除最後一個節點(將堆的大小減小1),而後從第一個節點位置開始調整堆,使得稱爲新的最大堆。操做過程以下圖所示:
僞代碼描述以下:
HEAD_EXTRACT_MAX(A) if heap_size[A]<1 ther error max = A[1] A[1] = A[heap_size[A]]; heap_size[A] = heap_size[A]-1 adjust_max_heap(A,1) return MAX
(3)HEAP_INCREASE_KEY實現INCREASE_KEY,經過下標來標識要增長的元素的優先級key,增長元素後須要調整堆,從該節點的父節點開始自頂向上調整。操做過程以下圖所示:
僞代碼描述以下:
HEAP_INCREASE_KEY(A,i,key) if key < A[i] then error A[i] = key while i>1 && A[PARENT(i)] <A[i] do exchange A[i] <-> A[PARENT(i)] i = PARENT(i)
(4)MAX_HEAP_INSERT實現INSERT操做,向最大堆中插入新的關鍵字。新的關鍵字插入在優先級的隊尾部,而後從尾部的父節點開始自頂向上調整堆僞代碼描述以下:
MAX_HEAP_INSERT(A,key) heap_size[A] = heap_size[A]+1 A[heap_size[A]] = -0; HEAP_INCREASE_KEY(A,heap_size[A],key)
三、實例
問題描述以下:優先級隊列中有多個事件發生,每一個事件有本身獨立的優先級,優先級是非負數,數值越大優先級越高。採用最大優先級隊列模擬事件執行的優先順序。具體操做包括:
(1)向優先級隊列中添加一個新事件
(2)獲取優先級隊列中優先級最高的事件
(3)刪除優先級隊列中指定位置的事件
(4)增長優先級隊列中指定位置事件的優先級
(5)下降優先級隊列中指定位置事件的優先級
採用C++語言實現,完整程序以下所示:
#include <iostream> #include <string> #include <cstdlib> using namespace std; const static int QUEUELEN = 100; class Event { public: Event():eventname(""),priority(-1){}; Event(const string &en,const int p):eventname(en),priority(p){}; Event(const Event& en) { eventname = en.eventname; priority = en.priority; } ~Event(){}; int get_event_priority()const { return priority; } string get_event_name()const { return eventname; } void increase_event_priority(const int k) { priority = priority + k; } void decrease_event_priority(const int k) { priority = priority - k; } void show_event() const { cout<<"Eventname is: ("<<eventname<<") and the priority is: "<<priority<<endl; } private: string eventname; int priority; }; class PriorityQueue { public: PriorityQueue(); void adjust_event(int index); Event get_event()const; void insert_event(const Event& en); void increase_event_priority(int pos,int k); Event delete_event(int pos); void show_events() const; ~PriorityQueue(); private: Event *events; int length; }; PriorityQueue::PriorityQueue() { events = new Event[QUEUELEN]; length = 0; } PriorityQueue::~PriorityQueue() { if(!events) delete [] events; length = 0; } //adjust max heap void PriorityQueue::adjust_event(int index) { int left,right,largest; Event temp; while(1) { left = index*2; right = index*2+1; if(left <= length && events[left].get_event_priority() > events[index].get_event_priority()) largest = left; else largest = index; if(right <= length && events[right].get_event_priority() > events[largest].get_event_priority()) largest = right; if(largest != index) { temp = events[index]; events[index] = events[largest]; events[largest] = temp; index = largest; } else break; } } Event PriorityQueue::get_event()const { if(length != 0) return events[1]; else return Event(); } void PriorityQueue::insert_event(const Event& en) { length = length + 1; events[length] = en; increase_event_priority(length,0); } void PriorityQueue::increase_event_priority(int pos,int k) { int i,parent; Event temp; if(pos > length) { cout<<"error: the pos index is larger than queue length"<<endl; return; } events[pos].increase_event_priority(k); i = pos; parent = i/2; while(i>1 && events[parent].get_event_priority() < events[i].get_event_priority()) { temp = events[i]; events[i] = events[parent]; events[parent] = temp; i = parent; parent = i/2; } } Event PriorityQueue::delete_event(int pos) { Event reten; if(pos > length) { cout<<"Error:pos index is larger than queue length"<<endl; return reten; } reten = events[pos]; events[pos] = events[length]; length--; adjust_event(pos); return reten; } void PriorityQueue::show_events() const { if(length == 0) { cout<<"There is no any event in the priority queue"<<endl; } else { cout<<"There are "<<length<<" events in the priority queue."<<endl; for(int i=1;i<=length;i++) { events[i].show_event(); } } } int main() { PriorityQueue pqueue; Event en; Event en1("fork",2); Event en2("exec",3); Event en3("wait",1); Event en4("signal",6); Event en5("pthread_create",5); pqueue.insert_event(en1); pqueue.insert_event(en2); pqueue.insert_event(en3); pqueue.insert_event(en4); pqueue.insert_event(en5); pqueue.show_events(); cout<<"\nThe max priority event is: "<<endl; en = pqueue.get_event(); en.show_event(); cout<<"\nIncrese event3 by 7"<<endl; pqueue.increase_event_priority(3,7); en = pqueue.get_event(); en.show_event(); pqueue.show_events(); cout<<"\nDelete the first event:"<<endl; pqueue.delete_event(1); pqueue.show_events(); exit(0); }
程序測試結果以下所示:
四、問題
(1)如何使用優先級隊列實現一個先進先出的隊列和先進後出的棧?
個人想法是:隊列中的元素是先進先出(FIFO)的,所以能夠藉助最小優先級隊列實現隊列。具體思想是,給隊列中的每一個元素賦予一個權值,權值從第一個元素到最後一個依次遞增(若是採用數組實現的話,能夠用元素所在的下標做爲優先級,優先級小的先出隊列),元素出隊列操做每次取優先級隊列第一個元素,取完以後須要堆最小優先級隊列進行調整,使得第一個元素的優先級最小。棧中的元素與隊列恰好相反,元素是先進後出(FILO),所以能夠採用最大優先級隊列進行實現,與用最小優先級隊列實現隊列思想相似,按照元素出現的順序進行標記元素的優先級,數據越是靠後,優先級越高。
舉例說明採用最小優先級隊列實現先進先出隊列,如今有一組數A={24,15,27,5,43,87,34}共六個數,假設數組下標從1開始,以元素所在數組中的下標爲優先級建立優先級隊列,隊列中元素出入時候調整最小優先級隊列。操做過程以下圖所示:
練習6.5.8:最小堆K路合併
《算法導論》第六章主要內容是關於堆和優先級隊列,書中給出了一個練習題,很是有有意思,今天好好研究練習一下。題目以下:請給出一個時間爲O(nlgk)、用來將k個已排序鏈表合併爲一個排序鏈表的算法。此處n爲全部輸入鏈表中元素的總數。(提示:用一個最小堆來作k路合併)。
看到題目第個想到的是歸併排序過程當中的歸併操做子過程,從頭開始兩兩比較,找出最小的,而後接着日後比較,經常使用的是2路歸併。而題目給的是k個已排好序的鏈表(k>=2)。若是沒有提示,我半天不知道如何去實現,幸虧提示說用最小堆來作k路合併,因而我想到能夠這樣作:建立一個大小爲k的數組,將k個鏈表中的第一個元素依次存放到數組中,而後將數組調整爲最小堆,這樣保證數組的第一個元素是最小的,假設爲min,將min從最小堆取出並存放到最終結果的鏈表中,此時將min所在鏈表的下一個元素到插入的最小堆中,繼續上面的操做,直到堆中沒有元素爲止。舉個例子以下圖所示(只給出不部分操做):
最終結果以下圖所示:
如今採用C++語言,藉助STL實現此過程,鏈表採用list,最小堆中存放的是list的迭代器,表示list中元素的位置。完整程序以下:
#include <iostream> #include <vector> #include <list> #include <iterator> #include <cstdlib> using namespace std; template<class T> class MinHeap { public: MinHeap(); MinHeap(const size_t size); ~MinHeap(); T get_min() const; void delete_min(); void insert_element(const T& e); void adjust_min_heap(const size_t i); size_t get_heap_size() const; int compare(const T& t1,const T& t2); private: T *heap; size_t heap_size; }; template<class T> MinHeap<T>::MinHeap():heap(NULL),heap_size(0){} template<class T> MinHeap<T>::MinHeap(const size_t size) { if(!heap) delete [] heap; heap = new T[size+1]; heap_size = 0; } template<class T> MinHeap<T>::~MinHeap() { if(!heap) delete [] heap; heap_size = 0; } template<class T> T MinHeap<T>::get_min() const { if(heap_size > 0) return heap[1]; else return T(); } template<class T> void MinHeap<T>::delete_min() { if(heap_size > 0) { heap[1] = heap[heap_size]; heap_size = heap_size - 1; adjust_min_heap(1); } else { cout<<"Error: the min heap is empty"<<endl; } } template<class T> void MinHeap<T>::insert_element(const T& e) { size_t i,parent; T temp; heap_size = heap_size + 1; heap[heap_size] = e; i = heap_size; parent = i/2; while(i>1 && compare(heap[parent],heap[i]) > 0) { temp = heap[parent]; heap[parent] = heap[i]; heap[i] = temp; i = parent; parent = i/2; } } template<class T> void MinHeap<T>::adjust_min_heap(const size_t i) { size_t left,right,least; T temp; left = i*2; right = i*2+1; if(left <= heap_size && compare(heap[left],heap[i]) < 0) least = left; else least = i; if(right <= heap_size && compare(heap[right],heap[least]) < 0) least = right; if(least != i) { temp = heap[least]; heap[least] = heap[i]; heap[i] = temp; adjust_min_heap(least); } } template<class T> size_t MinHeap<T>::get_heap_size() const { return heap_size; } template<class T> int MinHeap<T>::compare(const T& t1,const T& t2) { return (*t1-*t2); } const static int k = 3; int main() { list<int> lists[k]; list<int>::iterator iters[k]; list<int> retlist; list<int>::iterator retiter; list<int>::iterator iter; MinHeap<list<int>::iterator> minheap(k); //first list <12,24,52> lists[0].push_back(12); lists[0].push_back(24); lists[0].push_back(52); cout<<"First list: "; for(iter=lists[0].begin();iter != lists[0].end();++iter) cout<<*iter<<"->"; cout<<"NULL"<<endl; //second list <9,32> lists[1].push_back(9); lists[1].push_back(32); cout<<"Second list: "; for(iter=lists[1].begin();iter != lists[1].end();++iter) cout<<*iter<<"->"; cout<<"NULL"<<endl; //third list <34,42,78> lists[2].push_back(34); lists[2].push_back(42); lists[2].push_back(78); cout<<"Third list: "; for(iter=lists[2].begin();iter != lists[2].end();++iter) cout<<*iter<<"->"; cout<<"NULL"<<endl; iters[0] = lists[0].begin(); iters[1] = lists[1].begin(); iters[2] = lists[2].begin(); minheap.insert_element(iters[0]); minheap.insert_element(iters[1]); minheap.insert_element(iters[2]); while(minheap.get_heap_size()) { iter = minheap.get_min() ; retlist.push_back(*iter); minheap.delete_min(); ++iter; if(iter != lists[0].end() && iter != lists[1].end() &&iter != lists[2].end()) minheap.insert_element(iter); } cout<<"Merge the there list is: "<<endl; for(retiter = retlist.begin();retiter!= retlist.end();retiter++) cout<<*retiter<<"->"; cout<<"NULL"<<endl; exit(0); }
程序測試結果以下所示: