圖相關(三)圖的鄰接矩陣表示(C++)及最最小生成樹演算法(prim和kruskal)
阿新 • • 發佈:2018-12-31
一.測試用圖
鄰接矩陣表示:
//prim注意是無向圖 vector<vector<int>> p(6, vector<int>(6, INF));//類似dijikstra,沒有邊的點設為INF p[0][1] = 10; p[0][3] = 30; p[0][4] = 45; p[1][0] = 10; p[3][0] = 30; p[4][0] = 45; p[1][2] = 50; p[1][4] = 40; p[1][5] = 25; p[2][1] = 50; p[4][1] = 40; p[5][1] = 25; p[2][4] = 35; p[2][5] = 15; p[4][2] = 35; p[5][2] = 15; p[3][5] = 20; p[5][3] = 20; p[4][5] = 55; p[5][4] = 55;
注意:prim演算法的圖為無向圖,和dijikstra一樣,沒有邊的地方要用INF填充
其中INF表示無窮:
const int INF = 0x3f3f3f;//不能取得太大
二.prim演算法:
//prim演算法,返回最小生成樹的權值 int Prim(vector<vector<int>> cost)//下標從0開始 { int n = cost.size(); vector<bool> visited(n, false); vector<int> lowc(n, 0); int ans = 0; visited[0] = true;//第一個點標記為已訪問 for (int i = 1;i < n;i++)lowc[i] = cost[0][i]; for (int i = 1;i < n;i++) { int minc = INF; int p = -1; for (int j = 0;j < n;j++) if (!visited[j] && minc > lowc[j]) { minc = lowc[j]; p = j; } if (minc == INF)return -1;//原圖不連通 ans += minc; visited[p] = true; for (int j = 0;j < n;j++) if (!visited[j] && lowc[j] > cost[p][j]) lowc[j] = cost[p][j]; } return ans; }
測試:
int minWeight =Prim(p);
cout << "minWeight= " << minWeight << endl;
測試結果:
三.kruskal演算法:
圖改為邊陣列表示,利用並查集很容易理解。此處直接用一個數組來表示並查集,陣列的下標表示節點的編號,對應的值為其父指標的值,初始時,將並查集中的所有元素的父指標指向-1.
3.1圖的邊陣列表示法:
//kruskal:使用並查集 struct Edge { int from;//起點 int to;//終點 int weight; bool operator<(const Edge&rhs){//過載operator <之後可以直接呼叫sort return weight < rhs.weight;//此處也可以單獨寫一個仿函式,傳給sort作比較器 } };
此時上面的測試用圖表示為:
vector<Edge> kru =
{ { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
{ 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
{ 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
3.2並查集的find函式
int Find(vector<int>& UnionFindSet,int x){//索引表示節點標號,對應的值為其父節點的標號
if (UnionFindSet[x] == -1)//代表元素是-1,說明只有一個元素
return x;
return UnionFindSet[x] = Find(UnionFindSet,UnionFindSet[x]);//遞迴修改,扁平化
}
3.3 kruskal演算法
int kruskal(vector<Edge> graph,int vertexNum) {
size_t N = graph.size();
int ans = 0;
int cnt = 0;//已經選取的邊數
vector<int> UnionFindSet(N, -1);//初始化並查集,每個集合的代表元素置為-1;
sort(graph.begin(), graph.end());//給邊排序,拍好序後邊的權值由小到大排列
for (size_t i = 0;i < N;++i) {
int from = graph[i].from;
int to = graph[i].to;
int weight = graph[i].weight;
//找出這條邊的兩個頂點所在的集合
int t1 = Find(UnionFindSet, from);
int t2 = Find(UnionFindSet, to);
if (t1 != t2) {//t1和t2不在一個集合時,即沒有形成迴路時,選取該邊
ans += weight;
UnionFindSet[t1] = t2;//合併兩個集合
++cnt;
}
if(cnt==vertexNum-1)//選夠頂點數減1條邊時,退出,最小生成樹的邊數為頂點數減一
break;
}
if (cnt < vertexNum - 1) return -1;//不連通
else return ans;
}
3.4 kruskal測試
cout << "kruskal: " << endl;
vector<Edge> kru =
{ { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
{ 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
{ 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
int ret = kruskal(kru, 6);
cout << "kruskal= " << ret << endl;
3.5 kruskal測試結果