標籤:去掉 順序 時間複雜度 span alt col 輕量級 切割 name
對於一個連通圖來說,我們可以去掉其中一些邊依然保持其連通的性質,在這些圖中存在一個或多個圖,他們的路徑總和是最小的,這樣的圖必然是樹。因為,如果說圖中存在環,則去掉環的一條邊依然可以保證連通性,這與總路徑和最小是矛盾的。這樣的圖被稱為最下產生樹。城市間鋪設電路就可以利用最小產生樹來進行規劃。
的黑色路徑構成了最小產生樹,去掉bc並加入ah也是一棵最小產生樹,可見一個圖的最小產生樹並不一定是唯一的。
最小產生樹可以使用安全邊的策略進行產生:假設集合A是最小產生樹的子集,我們可以找到一條邊加入到A中,依然保持A是最小產生樹的子集,這樣的邊就被稱為安全邊。為了尋找安全邊,我們定義下方黑色部分ab與de邊構成了集合A,(A,V-A)稱為G的一個切割。如果一條邊(v,u)一個端點屬於A而另一個端點屬於V-A,則稱它為橫跨切割(A,V-A)。在橫跨切割的邊中,可以找到一條或多條權重最小的邊,該邊稱為輕量級邊,輕量級邊即是安全邊。因為A一定要與V-A部分產生串連,這就必須要通過橫跨切割的邊,而輕量級邊是其中最短的,所以必定屬於最小產生樹。所示ah bj bc cd df ef為橫跨切割的邊,其中cd為輕量級邊。為了尋找輕量級邊,有兩種基於貪心策略的演算法。
Kruskal演算法
Kruskal演算法的思想是,尋找串連森林中兩棵不同樹的邊裡面的最短邊作為安全邊加入集合A。可以使用不相交集合來維護這樣的結構,對每個結點建立一棵樹。通過FIND-SET來返回結點屬於哪棵樹,有邊加入集合A時合并u和v所在的樹。時間複雜度可表示為O(ElgV)。
圖中所示為各邊按照長度順序不斷遍曆檢查是否要加入到集合A中,直到遍曆完所有的邊結束。
#include<stdio.h>#include<vector>#include <algorithm>using namespace std;#define SIZE 10class Node{public: int rank; Node *p;};class Road{public: int u; int v; int weight;};int G[SIZE][SIZE];//鄰接矩陣,參數初始化略Node nodes[SIZE];void makeSet(Node x){ x.p = &x; x.rank = 0;}void Union(Node x,Node y){ if(x.rank > y.rank) y.p = &x; else{ x.p = &y; if(x.rank == y.rank) y.rank++; }}Node* findSet(Node x){ if(x.p != &x) x.p = findSet(*x.p); return x.p;}bool com (Road a,Road b) { return (a.weight<b.weight); //升序排列}vector<Road> MSTKruskal(){ vector<Road> A; int i,j; vector<Road> roads; for(i = 0; i < SIZE; i++){ nodes[i] = *new Node(); makeSet(nodes[i]);//每棵樹包含一個結點 } for(i = 0; i < SIZE; i++){ for(j = i + 1; j < SIZE; j++){ if(G[i][j] != 0){ Road road = *new Road(); road.u = i; road.v = j; road.weight = G[i][j]; roads.push_back(road); } } } sort(roads.begin(),roads.end(),com);//對所有路徑進行降序排列 for(i = 0; i < roads.size(); i++){ Road road = roads[i]; if(findSet(nodes[road.u]) != findSet(nodes[road.v])){ A.push_back(road); Union(nodes[road.u],nodes[road.v]); } } return A;}Prim演算法
Prim演算法的思路是從根結點開始加入集合A,不斷尋找A與V-A相連邊中最短的,即橫跨(A,V-A)的輕量級邊。可以將V-A到A距離的最小值儲存到優先隊列Q中來減少每次遍曆尋找最短邊的時間。優先隊列可以通過最小二元堆積或者斐波那契堆來實現,前者的漸進時間為O(ElgV)後者改進為O(E+VlgV)
#include<stdio.h>#include<vector>using namespace std;#define SIZE 10#define INFI 10000class Road{public: int u; int v; int weight;};int G[SIZE][SIZE];//鄰接矩陣,參數初始化略vector<Road> MSTPrim(int root){ vector<Road> A; int i; Road roads[SIZE];//記錄到達A的最短路徑 for(i = 0; i < SIZE; i++){ roads[i] = *new Road(); if(G[root][i] != 0){ roads[i].u = root; roads[i].v = i; roads[i].weight = G[root][i]; } else roads[i].weight = INFI; } while(A.size() != SIZE - 1){ int min = 0; for(i = 1; i < SIZE; i++){ if(roads[i].weight < roads[min].weight && roads[i].weight > 0) min = i;//尋找最短的路徑 } A.push_back(roads[min]); roads[min].weight = -1;//表示該點已在A中 for(i = 0; i < SIZE; i++){ if(G[min][i] < roads[i].weight) roads[i].weight = G[min][i];//更新到達A的最短長度 } } return A;}
演算法導論——最小產生樹