算法导论第六章优先队列(二)

优先队列可以说是堆的一个非常重要的应用,和堆对应,优先队列也分最小优先队列和最大优先队列。

优先队列是一种用来维护由一组元素构成的集合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++编程技术等内容,欢迎大家关注。

posted @ 2015-09-20 17:16  bakari  阅读(1047)  评论(0编辑  收藏  举报