一、prim演算法
最小生成樹prim版,是由prim提出的
在prim的實現中,需要使用輔助資料結構——最小堆,將所有的橫切邊放入最小堆中,經過一系列操作後取堆頂元素,就可得到最小生成樹。
其思想是基於切分定理,其過程如下:
如下圖,初始時,全為藍色,當某個節點被訪問到時,就將其標記(實現中用marked陣列標記,標記後即為紅色):
從0開始:
將對應的橫切邊放入堆中
此時從最小堆中取堆頂元素0.16出來,0.16就屬於最小生成樹中
然後將最小生成樹中的邊0.16相連的未被訪問節點加入紅色
再將新產生的橫切邊放入最小堆中:
此時再次將堆頂元素0.19取出,加入最小生成樹中:
然後再將最小生成樹中的邊0.19相連的未被訪問節點加入紅色:
有一次將新產生的橫切邊加入最小堆中:
再一次取出堆頂元素0.26:
然後將最小生成樹中的邊0。26相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:
再一次取出堆頂元素0.17,加入最小生成樹中:
然後將最小生成樹中的邊0.17相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:
再一次取出堆頂元素0.28,加入最小生成樹中:
然後將最小生成樹中的邊0.28相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:
再一次取出堆頂元素0.29,(注意:此時邊0.29它已經不滿足橫切邊了(0.29的兩端都為紅色陣營),但是邊0.29仍然在最小堆中且為堆頂元素,此時在程式碼中需要加入判斷語句:if( marked[1] != marked[3] )
,取出的邊判斷是不是橫切邊,不是則扔掉,是則加入最小生成樹中。)0.29直接扔掉。進入下一輪:
取出0.32,直接扔掉:
取出0.34,直接扔掉:
取出0.35,加入最小生成樹中,並將最小生成樹中的邊0.35相連的未被訪問節點加入紅色,再將新產生的橫切邊放入最小堆中:
然後依次取出0.36 、0.37,0.38直接扔掉;再取出最小堆堆頂元素0.40,加入最小生成樹中;並將最小生成樹中的邊0.40相連的未被訪問節點加入紅色,再將新產生的橫切邊放入最小堆中:
然後在依次取出堆頂元素0.52、0.58、0.93,直到最小堆為空。
二、prim最小生成樹的實現
需要一個輔助資料結構——最小堆
#include <algorithm>
#include <cassert>
using namespace std;
//最小堆
template <typename Item>
class MinHeap{
private:
Item *data; //原始資料對應的陣列
int count; //資料對應的索引
int capacity; //堆中容量
void shiftUp(int k){
while(k > 1 && data[k] < data[k/2]){
swap(data[k]), data[k/2];
k /= 2;
}
}
void shiftDown(int k){
while(2*k <= count){
int j = 2*k;
//右孩子存在
if(j+1 < count && data[j] > data[j+1]) {
j++;
}
//右孩子不存在
if(data[k] <= data[j]){
break;
}
swap(data[k], data[j]);
k = j;
}
}
public:
//建構函式,構造一個空堆可容納capacity個元素
MinHeap(int capacity ){
data = new Item[capacity + 1]; //開capacity+1的空間,對應第一個元素從索引值1開始
count = 0;
this->capacity = capacity;
}
//建構函式,透過給定一個定陣列建立一個最小堆
MinHeap(Item arr[], int n){
data = new Item[n+1];
capacity = n;
for(int i = 0; i < n; i++){
data[i+1] = arr[i];
}
count = n;
}
//解構函式
~MinHeap(){
delete[] data;
}
//返回堆中的元素個數
int size(){
return count;
}
//返回一個布林值,表示堆是否為空
bool isEmpty(){
return count == 0;
}
//向最小堆中,插入一個新元素item
Item insert(Item item){
assert(count + 1 <= capacity);
data[count+1] = item;
shiftUp(count+1);
count ++;
}
// 從最小堆中取出堆頂元素, 即堆中所儲存的最小資料
Item extracitMin(){
assert(count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count --;
shiftDown(1);
return ret;
}
//檢視堆頂元素
Item getMin(){
assert(count > 0);
return data[1];
}
};
prim最小生成樹
引入標頭檔案:
#include <iostream>
#include <cassert>
#include <vector>
#include "Edge.h"
#include "MinHeap.h"
//使用LazePrim演算法求最小生成樹
using namespace std;
//使用prim演算法求圖的最小生成數
template <typename Graph, typename Weight>
class LazePrimMST {
private:
Graph &G; //圖G的引用
MinHeap<Edge<Weight>> pq; //最小堆,將橫切邊放入最小堆中
bool *marked; //標記陣列,在演算法執行過程中標記節點i是否被訪問
vector<Edge<Weight>> mst; // 最小生成樹所包含的所有邊
Weight mstWeight; // 最小生成樹的權值
在private中寫visit()方法用來將橫切邊加入最小堆中:
//visit
void visit(int v) {
assert(!marked[v]);
marked[v] = true;
// 使用圖的迭代器,將和節點v相連線的所有未訪問的邊放入最小堆中
typename Graph::adjIterator adj(G, v);
for (Edge<Weight> *e = adj.begin(); !adj.end(); e = adj.next()) {
//判斷v節點的相鄰節點是否被訪問
if ( !marked[e->other(v)]) {
pq.insert(*e);
}
}
}
public:
LazePrimMST(Graph &graph) : G(graph), pq(MinHeap<Edge<Weight>>(graph.E())) {
//初始化演算法
marked = new bool[G.V()];
for (int i = 0; i < G.v(); i++) {
marked[i] = false;
}
//將mst清空
mst.clrar;
// Prim
visit(0);
while (!pq.isEmpty()) {
// 使用最小堆找出已經訪問的邊中權值最小的邊
Edge<Weight> e = pq.extracitMin();
// 如果這條邊的兩端都已經訪問過了, 則扔掉這條邊
if (marked[e.v()] == marked[e.w]) {
continue;
}
//否則e就為最小生成樹的邊
mst.push_back(e);
// 訪問和這條邊連線的還沒有被訪問過的節點
if (!marked[e.v()]) {
visit(e.v());
}
else {
visit(e.w);
}
}
//計算最小生成樹的權值
mstWeight = mst[0].wt;
for (int i = 1; i < mst.size(); i++) {
mstWeight += mst[i].wt;
}
}
//解構函式
~LazePrimMST(){
delete[] marked;
}
// 返回最小生成樹的所有邊
vector<Edge<Weight>> mstEdge(){
return mst;
}
// 返回最小生成樹的權值
Weight result(){
return mstWeight;
}
};
本作品採用《CC 協議》,轉載必須註明作者和本文連結