算法导论第六章优先队列(二)
优先队列可以说是堆的一个非常重要的应用,和堆对应,优先队列也分最小优先队列和最大优先队列。
优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中每一个元素都有一个关键字(key),关键字赋予了一个元素的优先级,故名为优先队列。之所以用堆来实现优先队列,我想最大的原因是堆很容易对元素按关键字进行排序。
优先队列的应用:
最大优先队列:其中最为典型的就是“共享计算机系统的作业调度”,通过记录各个作业的优先级,来调度一个作业的执行、删除和插入等操作。
最小优先队列:可以被用于“基于事件驱动的模拟器”,队列中保存要模拟的事件,每个事件都有一个发生时间作为关键字。事件必须按发生的时间顺序进行模拟,因为某一事件的模拟结果可能会触发对其他事件的模拟。
优先队列的实现:
为了实现优先队列,需要实现这样的几个过程:
1)Insert(S, x):插入x到集合S中;
2)Maximum(S):返回S中具有最大关键字的元素;
3)Extract_Max(S):去掉并返回集合S中具有最大关键字的元素;
4)Increase_Key(S, x, key):将元素x的关键字增加到key。
我们暂且不用管这些奇怪的函数为什么要这么定义,因为这是前人的成功经验总结,肯定是在实际应用中这几个函数用得是最多的,总之,知道这样的四个函数就行了,等用到的时候就知道它们的好处了。
首先,基于堆,Maximum可以在O(1)的时间内完成,如:
1 int PriorityQueue::HeapMaximum() //return the maximum key from priority queue; 2 { 3 return GetQueueElement(0); //the first element is max; 4 }
其次,Extract_Max(S)和Maximum()的区别就在于,得到了之后还要删除,在不改变结构的情况下,最好的删除方法就是用最后一个元素来代替第一个元素,但是要注意,删除之后,有可能堆的性质就不能保证了,所以,这个时候调用Max_Heapify()调整一下。如下:
1 int PriorityQueue::HeapExtractMax() //delete and return the maximum key from queue; 2 { 3 if (IsEmptyHeap()) 4 throw "heap underflow"; 5 6 int heap_size = GetHeapSize(); 7 int maxNum = GetQueueElement(0); 8 SetQueueElement(0, GetQueueElement(heap_size-1)); //A[1] = A[heap_size] 9 SetHeapSizeMinus1(); 10 MaxHeapify(0); //Max_Heapify() 11 return maxNum; 12 }
对于Increase_Key(S, x, key),我们直接用Key替换X,但是替换之后堆的性质也可能改变了,可以知道的是,X后面的元素仍然满足堆的性质,因为Key>X,这时就之用维护X前面的元素,一层层往上调用Max_Heapify()即可,如:
1 void PriorityQueue::HeapIncreaseKey(int srcIndex, int dstKey) //increasing the srcKey to dstKey 2 { 3 if (dstKey < GetQueueElement(srcIndex)) 4 throw "new key is smaller than current key!"; 5 SetQueueElement(srcIndex, dstKey); //x = key 6 while(srcIndex > 0 && GetQueueElement(getParent(srcIndex)) < GetQueueElement(srcIndex)) { 7 Swap(srcIndex, getParent(srcIndex)); 8 srcIndex = getParent(srcIndex); //get parent 9 } 10 }
最后,Insert(S, x)操作,我们可以在最后增加一个-∞的元素,然后将其转换成将-∞增加到x,即转换成Increase_Key(S, -∞, x)。如:
1 void PriorityQueue::HeapInsert(int key) //insert key to the priority queue; 2 { 3 AddQueueElement(INT_MIN); //add the INT_MIN to the end of heapQueue; 4 int heap_size = GetHeapSize(); 5 HeapIncreaseKey(heap_size-1, key); 6 }
除此之外,习题6.5-8要求在O(lgn)的时间内实现一个Delete(S, i)操作。很容易想到相当于对A[i]为根的堆进行Extract_Max()操作。如:
1 Heap-Delete(A, i) 2 A[i] = A[A.heap-size] 3 A.heap-size = A.heap-size - 1 4 Heapify(A, i)
以上所有的操作,除了Maximum()之外,其余所有的函数的时间复杂度皆为O(lgn),所以,这也是为什么用堆来实现优先队列一个非常重要的原因。下面给出一个非常简单的优先队列的实现(元素的值就是Key)。
.h 文件
1 #ifndef _PRIORITY_QUEUE_H_ 2 #define _PRIORITY_QUEUE_H_ 3 4 /************************************************************************/ 5 /* priority queue 6 /************************************************************************/ 7 class PriorityQueue { 8 public: 9 PriorityQueue() { m_heapSize = 0; m_length = 0; } 10 ~PriorityQueue() {} 11 12 //inline 13 int getParent(int index) { return (index-1)/2; } 14 int getLeft(int index) { return 2*index + 1; } 15 int getRight(int index) { return 2*index + 2; } 16 17 //heap operation 18 void BuildMaxHeap(); //build the max heap 19 void MaxHeapify(int index); //protect the max heap 20 void HeapSort(); //heap sort 21 22 //priority queue operation 23 void HeapInsert(int key); //insert key to the priority queue; 24 int HeapMaximum(); //return the maximum key from priority queue; 25 int HeapExtractMax(); //delete and return the maximum key from queue; 26 void HeapIncreaseKey(int srcIndex, int dstKey); //increasing the srcKey to dstKey 27 28 void HeapDelete(int key); //delete key Ï°Ìâ6.5-8 29 30 //other assist functions 31 void AddQueueElement(int key); 32 void DisplayQueue(); 33 void DisplayHeapQueue(); 34 35 public: 36 37 38 private: 39 int GetQueueElement(int index) { return m_vecQueue[index]; } 40 void SetQueueElement(int index, int key) { m_vecQueue[index] = key; } 41 void Swap(int i, int j) { 42 int temp = m_vecQueue[i]; 43 m_vecQueue[i] = m_vecQueue[j]; 44 m_vecQueue[j] = temp; 45 } 46 int GetHeapSize() { return m_heapSize; } 47 int GetArrayLength() { return m_length; } 48 void SetHeapSizeMinus1() { m_heapSize = m_heapSize - 1; } 49 void SetHeapSizePlus1() { m_heapSize = m_heapSize + 1; } 50 51 bool IsEmptyHeap() { return (m_heapSize > 1 ? false : true); } 52 53 private: 54 vector<int> m_vecQueue; 55 int m_heapSize; //¶ÑÔªËظöÊý 56 int m_length; //Êý×éÔªËظöÊý 57 58 }; 59 60 61 #endif//_PRIORITY_QUEUE_H_
.cpp 文件
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 #include "PriorityQueue.h" 6 7 //heap operation 8 void PriorityQueue::BuildMaxHeap() //build the max heap 9 { 10 int heap_size = GetHeapSize(); 11 for (int i = (heap_size-1)/2; i >= 0; i --) 12 MaxHeapify(i); 13 } 14 15 void PriorityQueue::MaxHeapify(int index) //protect the max heap 16 { 17 if (index < 0 || index >= GetHeapSize()) 18 return; 19 20 int heap_size = GetHeapSize(); 21 bool isHeapify = true; 22 23 int largest = -1; 24 while (isHeapify && index < heap_size) { 25 int left = getLeft(index); 26 int right = getRight(index); 27 28 if (left < heap_size && GetQueueElement(left) > GetQueueElement(index) ) 29 largest = left; 30 else 31 largest = index; 32 if (right < heap_size && GetQueueElement(right) > GetQueueElement(largest) ) 33 largest = right; 34 35 if (largest != index) { 36 Swap(index, largest); 37 index = largest; 38 } 39 else 40 isHeapify = false; 41 } 42 43 } 44 45 void PriorityQueue::HeapSort() //heap sort 46 { 47 BuildMaxHeap(); 48 int heap_size = GetHeapSize(); 49 for (int i = heap_size-1; i >= 1; i --) { 50 Swap(0, i); 51 SetHeapSizeMinus1(); //heap_size--; 52 MaxHeapify(0); 53 } 54 } 55 56 //priority queue operation 57 void PriorityQueue::HeapInsert(int key) //insert key to the priority queue; 58 { 59 // SetHeapSizePlus1(); 60 // int heap_size = GetHeapSize(); 61 AddQueueElement(INT_MIN); //add the INT_MIN to the end of heapQueue; 62 int heap_size = GetHeapSize(); 63 HeapIncreaseKey(heap_size-1, key); 64 } 65 66 int PriorityQueue::HeapMaximum() //return the maximum key from priority queue; 67 { 68 return GetQueueElement(0); //the first element is max; 69 } 70 71 int PriorityQueue::HeapExtractMax() //delete and return the maximum key from queue; 72 { 73 if (IsEmptyHeap()) 74 throw "heap underflow"; 75 76 int heap_size = GetHeapSize(); 77 int maxNum = GetQueueElement(0); 78 SetQueueElement(0, GetQueueElement(heap_size-1)); //A[1] = A[heap_size] 79 SetHeapSizeMinus1(); 80 MaxHeapify(0); //Max_Heapify() 81 return maxNum; 82 } 83 84 void PriorityQueue::HeapIncreaseKey(int srcIndex, int dstKey) //increasing the srcKey to dstKey 85 { 86 if (dstKey < GetQueueElement(srcIndex)) 87 throw "new key is smaller than current key!"; 88 SetQueueElement(srcIndex, dstKey); //x = key 89 while(srcIndex > 0 && GetQueueElement(getParent(srcIndex)) < GetQueueElement(srcIndex)) { 90 Swap(srcIndex, getParent(srcIndex)); 91 srcIndex = getParent(srcIndex); //get parent 92 } 93 } 94 95 void PriorityQueue::HeapDelete(int key) //delete key Ï°Ìâ6.5-8 96 { 97 int heap_size = GetHeapSize(); 98 int index = 0; 99 for (;index < heap_size; index ++) { 100 if (GetQueueElement(index) == key) 101 break; 102 } 103 SetQueueElement(index, GetQueueElement(heap_size-1)); 104 SetHeapSizeMinus1(); 105 MaxHeapify(index); 106 } 107 108 //other assist functions 109 void PriorityQueue::DisplayQueue() 110 { 111 int nLen = GetArrayLength(); 112 cout << "------------------------" << endl; 113 for (int i = 0; i < nLen; i ++) 114 cout << GetQueueElement(i) << " "; 115 cout << endl; 116 } 117 118 void PriorityQueue::DisplayHeapQueue() 119 { 120 int heap_size = GetHeapSize(); 121 cout << "------------------------" << endl; 122 for (int i = 0; i < heap_size; i ++) 123 cout << GetQueueElement(i) << " "; 124 cout << endl; 125 } 126 127 void PriorityQueue::AddQueueElement(int key) 128 { 129 m_vecQueue.push_back(key); 130 m_heapSize ++; 131 m_length ++; 132 } 133 134 135 // int main() 136 // { 137 // //int key, num; 138 // PriorityQueue PQ; 139 // 140 // // cout << "the number:" << endl; 141 // // cin >> num; 142 // int arr[] = {10,8,7,16,14,9,3,2,4,1}; 143 // cout << "the key:" << endl; 144 // for (int i = 0; i < 10; i ++) { 145 // PQ.AddQueueElement(arr[i]); 146 // } 147 // 148 // PQ.BuildMaxHeap(); 149 // PQ.DisplayQueue(); 150 // 151 // //Max 152 // cout << "Max:" << PQ.HeapMaximum() << endl; 153 // 154 // //IncreaseKey 155 // cout << "IncreaseKey:" << endl; 156 // PQ.HeapIncreaseKey(0, 18); 157 // PQ.DisplayHeapQueue(); 158 // 159 // //InsertKey 160 // cout << "InsertKey:" << endl; 161 // PQ.HeapInsert(20); 162 // PQ.DisplayHeapQueue(); 163 // 164 // //Extract_Max 165 // cout << "Extract_Max:" << PQ.HeapExtractMax() << endl; 166 // PQ.DisplayHeapQueue(); 167 // 168 // //Extract_Max 169 // cout << "Extract_Max:" << PQ.HeapExtractMax() << endl; 170 // PQ.DisplayHeapQueue(); 171 // 172 // return 0; 173 // }
习题精讲:
优先队列的讲述,基本就是这样,其实主要的设计思想还是堆。下面看两道有意思的习题:
1)习题6.5-6:在Heap_Increase_Key的第5行操作中,一般需要通过三次赋值来完成。想一想如何利用Insertion_Sort内循环部分的思想,只用一次赋值就完成这一交换操作?
分析:这是一种非常好的出题思路,能够打开我们的思维,让人有一种眼前一亮的感觉。我们可以先向下移动小于他的祖先,直到没有小于他的祖先后放在空出的位置上。如:
1 Heap-Increase-Key(A, i, key) 2 if A[i] < key 3 error "new key is smaller than original" 4 while i > 1 and A[Parent(i)] < key 5 A[i] = A[Parent(i)] 6 i = Parent(i) 7 A[i] = key
2)习题6.5-9:请设计一个能够在O(nlgk)的算法,它能够将k个有序链表合并成一个有序链表,这里n是所有输入链表包含的总的元素个数。(提示:使用最小堆来完成k路归并)。
这个题首先想到2路归并排序,但此处是k路,乍一看没什么思路,看了提示后,仍然没有什么头绪,看了网友Anker的思路后,发现可以这样来做(太水了):创建一个大小为k的数组,将k个链表中的第一个元素依次存放到数组中,然后将数组调整为最小堆,这样保证数组的第一个元素是最小的,假设为min,将min从最小堆取出并存放到最终结果的链表中,此时将min所在链表的下一个元素到插入的最小堆中,继续上面的操作,直到堆中没有元素为止。举个例子如下图所示(只给出不部分操作):
我们采用C++语言,借助STL实现此过程,链表采用vector,最小堆中存放的是vector的迭代器,表示vector中元素的位置。完整程序如下:
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 7 #include "MinHeap.h" 8 9 //merge the k list to a heap; 10 template<class T> 11 MinHeap<T>::MinHeap(size_t kSize) 12 { 13 if (!m_minHeap) 14 delete []m_minHeap; 15 m_minHeap = new T[kSize+1]; 16 m_heapSize = 0; 17 } 18 19 //adjust the min heap; 20 template<class T> 21 void MinHeap<T>::MinHeapify(const size_t index) 22 { 23 //assert 24 int heap_size = GetHeapSize(); 25 26 while (true) { 27 size_t left = LEFT(index); 28 size_t right = RIGHT(index); 29 30 size_t smallest; 31 if (left < heap_size && HeapCompare(index, left) > 0) 32 smallest = left; 33 else smallest = index; 34 if (right < heap_size && HeapCompare(smallest, right) > 0) 35 smallest = right; 36 37 if (smallest != index) { 38 Swap(index, smallest); 39 index = smallest; 40 } 41 else break; 42 } 43 } 44 45 //insert element 46 template<class T> 47 void MinHeap<T>::HeapInsert(const T &element) 48 { 49 m_minHeap[m_heapSize] = element; 50 m_heapSize += 1; 51 52 size_t index = m_heapSize-1; 53 54 while (index > 0 && HeapCompare(index, PARENT(index)) < 0) { 55 Swap(index, PARENT(index)); 56 index = PARENT(index); 57 } 58 } 59 60 //return and delete the min element 61 template<class T> 62 T MinHeap<T>::HeapExtractMin() const 63 { 64 if (IsEmptyHeap()) 65 throw "Heap is Empty!"; 66 T minElement = HeapMin(); 67 68 int heap_size = GetHeapSize(); 69 m_minHeap[0] = m_minHeap[heap_size-1]; 70 m_heapSize -= 1; 71 MinHeapify(0); 72 return minElement; 73 } 74 75 //return min element; 76 template<class T> 77 T MinHeap<T>::HeapMin() const 78 { 79 return m_minHeap[0]; 80 } 81 82 int main() 83 { 84 size_t k = 3; 85 vector<int> vecList[k]; 86 vector<int>::iterator iterList[k]; 87 vector<int> vecSort; 88 vector<int>::iterator iterS; 89 90 vector<int>::iterator it; 91 92 MinHeap<vector<int>::iterator> minHeap(k); 93 //first list 94 vecList[0].push_back(12); 95 vecList[0].push_back(24); 96 vecList[0].push_back(52); 97 cout << "first list:" << endl; 98 for ( it = vecList[0].begin();it != vecList[0].end(); ++it) 99 cout << *it << "->"; 100 cout << "NULL" << endl; 101 102 vecList[1].push_back(9); 103 vecList[1].push_back(32); 104 105 cout << "second list:" << endl; 106 for ( it = vecList[1].begin();it != vecList[1].end(); ++it) 107 cout << *it << "->"; 108 cout << "NULL" << endl; 109 110 vecList[2].push_back(34); 111 vecList[2].push_back(42); 112 vecList[2].push_back(78); 113 cout << "third list:" << endl; 114 for ( it = vecList[2].begin();it != vecList[2].end(); ++it) 115 cout << *it << "->"; 116 cout << "NULL" << endl; 117 118 iterList[0] = vecList[0].begin(); 119 iterList[1] = vecList[1].begin(); 120 iterList[2] = vecList[2].begin(); 121 122 minHeap.HeapInsert(iterList[0]); 123 minHeap.HeapInsert(iterList[1]); 124 minHeap.HeapInsert(iterList[2]); 125 126 while(minHeap.GetHeapSize()) { 127 it = minHeap.HeapExtractMin(); 128 vecSort.push_back(*it); 129 ++it; 130 if (it != vecList[0].end()&& it != vecList[1].end() && it != vecList[2].end()) { 131 minHeap.HeapInsert(it); 132 } 133 } 134 135 cout << "meger:" << endl; 136 for (iterS = vecSort.begin(); iterS != vecSort.end(); ++ iterS) 137 cout << *iterS << "->"; 138 cout << "NULL" << endl; 139 140 return 0; 141 }
我的公众号 「Linux云计算网络」(id: cloud_dev),号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,分享的内容包括但不限于 Linux、网络、云计算虚拟化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++编程技术等内容,欢迎大家关注。