先備知識與注意事項
在Minimum Spanning Tree:Prim's Algorithm中介紹過Prim's Algorithm的概念,也提供了範例程式碼。
本篇文章將補充以稍微優秀一點的資料結構:Min-Priority Queue實現Prim's Algorithm中的「取最小值(ExtractMin)」的方法。
上一篇文章的MinKeyExtract()
需要把整個key[]
走過一次,時間複雜度為\(O(|V|)\),其中\(|V|\)表示vertex的數量。
而以Binary Heap實現的Min-Priority Queue,在「取最小值」的時候只要\(O(\log{|V|})\),即為以Binary Tree解讀Binary Heap的「樹高」,理論上應該會比較有效率。
建議讀者可以先閱讀以下文章,:
- Minimum Spanning Tree:Intro(簡介):MST的基本概念,以及證明用的Theorem1與Corollary2。
- Minimum Spanning Tree:Prim's Algorithm:Prim's Algorithm的基本概念。
- Priority Queue:Binary Heap:以Binary Heap實現Min-Priority Queue的方法。
目錄
Prim's Algorithm
以下不再贅述Prim's Algorithm的概念,直接以圖示觀察Min-Priority Queue的作用。
觀察圖一(a)到圖一(m):
圖一(a)。
圖一(b)。
圖一(c)。
圖一(d)。
圖一(e)。
圖一(f)。
圖一(g)。
圖一(h)。
圖一(i)。
圖一(j)。
圖一(k)。
圖一(l)。
圖一(m)。
程式碼
以下的程式範例包含了class BinaryHeap
、class Graph_MST
與main()
。
class BinaryHeap
的概念與程式碼與Priority Queue:Binary Heap的內容相同。
class Graph_MST
建立Graph,其中Prim_MinQueue()
即為尋找MST的Prim's Algorithm之函式主體。
最後,main()
利用AddEdge()
建立出Graph的AdjList
,並執行Prim_MinQueue()
。
// C++ code
#include <iostream>
#include <vector>
#include <list>
#include <utility> // for std::pair<>
#include <iomanip> // for std::setw()
#include <cmath> // for std::floor
using std::cout;
using std::endl;
/////////////// MinHeap ////////////////
struct HeapNode{
int key, element;
HeapNode():key(0),element(0){};
HeapNode(int key, int element):key(key),element(element){};
};
class BinaryHeap{
private:
std::vector<HeapNode> heap;
void swap(struct HeapNode &p1, struct HeapNode &p2);
int FindPosition(int node);
int GetParentNode(int node){return std::floor(node/2);};
public:
BinaryHeap(){heap.resize(1);};
BinaryHeap(int n){
heap.resize(n + 1); // 主要存放vertex及其distance的vector
}
// Min-Priority Queue
void MinHeapify(int node, int length);
void BuildMinHeap(std::vector<int> array);
void DecreaseKey(int node, int newKey);
void MinHeapInsert(int node, int key);
int ExtractMin(); // 回傳heap[1]並調整Heap
int Minimum(){return heap[1].element;}; // 回傳heap[1]
bool IsHeapEmpty(){return (heap.size()<=1);};
};
int BinaryHeap::FindPosition(int node){
int idx = 0;
for (int i = 1; i < heap.size(); i++) {
if (heap[i].element == node) {
idx = i;
}
}
return idx;
}
void BinaryHeap::MinHeapInsert(int node, int key){
heap.push_back(HeapNode(node,key));
DecreaseKey(node, key);
}
void BinaryHeap::DecreaseKey(int node, int newKey){
int index_node = FindPosition(node); // 找到node所在的位置index
if (newKey >= heap[index_node].key) { // 如果不是把node的Key下修, 便終止此函式
std::cout << "new key is not smaller than current key\n";
return;
}
heap[index_node].key = newKey; // 更新node之Key後,
// 需要檢查是否新的subtree滿足Min Heap
while (index_node > 1 && heap[GetParentNode(index_node)].key > heap[index_node].key) {
swap(heap[index_node], heap[GetParentNode(index_node)]);
index_node = GetParentNode(index_node);
}
}
void BinaryHeap::swap(struct HeapNode &p1, struct HeapNode &p2){
struct HeapNode temp = p1;
p1 = p2;
p2 = temp;
}
int BinaryHeap::ExtractMin(){
if (IsHeapEmpty()) {
std::cout << "error: heap is empty\n";
exit(-1);
}
int min = heap[1].element; // 此時heap的第一個node具有最小key值
// 便以min記錄其element, 最後回傳min
// delete the first element/vertex
heap[1] = heap[heap.size()-1]; // 把最後一個element放到第一個位置,
heap.erase(heap.begin()+heap.size()-1); // 再刪除最後一個element
MinHeapify(1, (int)heap.size()); // 目前, heap[1]具有最大Key, 需要進行調整
return min; // 回傳heap中具有最小key的element
}
void BinaryHeap::BuildMinHeap(std::vector<int> array){
// 將array[]的資料放進 heap之矩陣中, 並預留 heap[0] 不做使用
for (int i = 0; i < array.size(); i++) {
heap[i + 1].element = i; // 把array[]的idx視為element
heap[i + 1].key = array[i]; // 把array[]的數值視為key
}
for (int i = (int)heap.size()/2; i >= 1 ; i--) {
MinHeapify(i, (int)heap.size()-1); // length要減一, 因為heap從從1開始存放資料
}
}
void BinaryHeap::MinHeapify(int node, int length){
int left = 2*node, // 取得left child
right = 2*node + 1, // 取得right child
smallest; // smallest用來記錄包含root與child, 三者之中Key最小的node
if (left <= length && heap[left].key < heap[node].key)
smallest = left;
else
smallest = node;
if (right <= length && heap[right].key < heap[smallest].key)
smallest = right;
if (smallest != node) { // 如果目前node的Key不是三者中的最小
swap(heap[smallest], heap[node]); // 就調換node與三者中Key最小的node之位置
MinHeapify(smallest, length); // 調整新的subtree成Min Heap
}
}
/////////////// Prim's Algorithm /////////////////
static const int maxDistance = 100;
class Graph_MST{
private:
int num_vertex;
std::vector<std::list<std::pair<int,int> > > AdjList;
std::vector<int> predecessor, distance;
std::vector<bool> visited;
void InitializeSingleSource(int Start); // 以Start作為起點
void PrintDataArray(std::vector<int> array);
public:
Graph_MST():num_vertex(0){};
Graph_MST(int n):num_vertex(n){
AdjList.resize(num_vertex);
}
void AddEdge(int from, int to, int weight);
void Prim_MinQueue(int Start);
friend class BinaryHeap;
};
void Graph_MST::InitializeSingleSource(int Start){
distance.resize(num_vertex);
predecessor.resize(num_vertex);
for (int i = 0; i < num_vertex; i++) {
distance[i] = maxDistance;
predecessor[i] = -1;
}
distance[Start] = 0; // 起點vertex的distance設為0, ExtractMin就會從起點開始
}
void Graph_MST::Prim_MinQueue(int Start){
InitializeSingleSource(Start);
BinaryHeap minQueue(num_vertex);
minQueue.BuildMinHeap(distance); // use minQueue to handle distance[]
visited.resize(num_vertex, false); // initializa visited[] as {0,0,0,...,0}
while (!minQueue.IsHeapEmpty()) {
int u = minQueue.ExtractMin();
visited[u] = true;
for (std::list<std::pair<int, int> >::iterator itr = AdjList[u].begin();
itr != AdjList[u].end(); itr++) {
if (visited[(*itr).first] == false && (*itr).second < distance[(*itr).first]) {
// for edge(X,Y)
// u: X , (*itr).first: Y, (*itr).second: weight(X,Y)
// (*itr).second < distance[(*itr).first]: weight(X,Y) < distance[Y]
distance[(*itr).first] = (*itr).second;
predecessor[(*itr).first] = u;
minQueue.DecreaseKey((*itr).first, distance[(*itr).first]);
}
}
}
/////// print result /////////
cout << "print predecessor[]:\n";
PrintDataArray(predecessor);
cout << "print distance[]:\n";
PrintDataArray(distance);
cout << std::setw(3) << "v1" << " - " << std::setw(3) << "v2"<< " : weight\n";
int i = (Start+1)%num_vertex; // 若從4開始, i依序為5,6,0,1,2,3
while (i != Start) {
cout << std::setw(3) << predecessor[i] << " - " << std::setw(3) << i
<< " : " << std::setw(3) << distance[i] <<"\n";
i = (++i)%num_vertex; // 到了6之後, 6+1 = 7, error:bad_access
}
}
void Graph_MST::PrintDataArray(std::vector<int> array){
for (int i = 0; i < num_vertex; i++){
cout << std::setw(4) << i;
}
std::cout << endl;
for (int i = 0; i < num_vertex; i++){
cout << std::setw(4) << array[i];
}
cout << endl << endl;
}
void Graph_MST::AddEdge(int from, int to, int weight){
AdjList[from].push_back(std::make_pair(to,weight));
}
int main(){
Graph_MST g6(7);
g6.AddEdge(0, 1, 5);g6.AddEdge(0, 5, 3);
g6.AddEdge(1, 0, 5);g6.AddEdge(1, 2, 10);g6.AddEdge(1, 4, 1);g6.AddEdge(1, 6, 4);
g6.AddEdge(2, 1, 10);g6.AddEdge(2, 3, 5);g6.AddEdge(2, 6, 8);
g6.AddEdge(3, 2, 5);g6.AddEdge(3, 4, 7);g6.AddEdge(3, 6, 9);
g6.AddEdge(4, 1, 1);g6.AddEdge(4, 3, 7);g6.AddEdge(4, 5, 6);g6.AddEdge(4, 6, 2);
g6.AddEdge(5, 0, 3);g6.AddEdge(5, 4, 6);
g6.AddEdge(6, 1, 4);g6.AddEdge(6, 2, 8);g6.AddEdge(6, 3, 9);g6.AddEdge(6, 4, 2);
cout << "MST found by Prim_MinQueue:\n";
g6.Prim_MinQueue(2);
return 0;
}
output:
MST found by Prim_MinQueue:
print predecessor:
0 1 2 3 4 5 6
1 4 -1 2 3 0 4
print distance:
0 1 2 3 4 5 6
5 1 0 5 7 3 2
v1 - v2 : weight
2 - 3 : 5
3 - 4 : 7
0 - 5 : 3
4 - 6 : 2
1 - 0 : 5
4 - 1 : 1
結果如同圖一(m):
圖一(m)。
以上便是利用Min-Priority Queue實作的Prim's Algorithm之介紹。
建議讀者可以稍微比較一下以下兩者:
- 利用Min-Priority Queue實作的Prim's Algorithm,以及
- 利用Min-Priority Queue實作的Dijkstra's Algorithm。
根本一模一樣。
參考資料:
- Introduction to Algorithms, Ch23
- Fundamentals of Data Structures in C++, Ch6
- Rashid Bin Muhammad:Jarnik's (Prim's) Algorithm
- Theory of Programming:Prim's Algorithm
- GeeksforGeeks:Greedy Algorithms | Set 6 (Prim’s MST for Adjacency List Representation)
- Single-Source Shortest Path:Dijkstra's Algorithm
- Priority Queue:Binary Heap
MST系列文章
Minimum Spanning Tree:Intro(簡介)
Minimum Spanning Tree:Kruskal's Algorithm
Minimum Spanning Tree:Prim's Algorithm
Minimum Spanning Tree:Prim's Algorithm using Min-Priority Queue
回到目錄: