若是一個無向連通圖中不存在迴路,則這種圖稱爲樹。html
無向連通圖G的一個子圖若是是一顆包含G的全部頂點的樹,則該子圖稱爲G的生成樹。ios
生成樹是連通圖的極小連通子圖。這裏所謂極小是指:若在樹中任意增長一條邊,則將出現一條迴路;若去掉一條邊,將會使之變成非連通圖。算法
一個帶權值的連通圖。用$n-1$條邊把$n$個頂點鏈接起來,且鏈接起來的權值最小。數組
設想有9個村莊,這些村莊構成以下圖所示的地理位置,每一個村莊的直線距離都不同。若要在每一個村莊間架設網絡線纜,若要保證成本最小,則須要選擇一條可以聯通9個村莊,且長度最小的路線。網絡
知識點:數據結構——並查集數據結構
始終選擇當前可用、不會(和已經選取的邊)構成迴路的最小權植邊。post
具體步驟:測試
1. 將全部邊按權值進行降序排序優化
2. 依次選擇權值最小的邊spa
3. 若該邊的兩個頂點落在不一樣的連通份量上,選擇這條邊,並把這兩個頂點標記爲同一連通份量;若這條邊的兩個頂點落到同一連通份量上,捨棄這條邊。反覆執行2,3,直到全部的都在同一連通份量上。【這一步須要用到上面的並查集】
模板題:https://www.luogu.org/problem/P3366
#include <iostream> #include <algorithm> using namespace std; int pre[5005]; int n, m; //n個定點,m條邊 struct ENode { int from, to, dis; bool operator<(ENode p) { return dis < p.dis; } }M[200005]; int Find(int x) { return x == pre[x] ? pre[x] : pre[x] = Find(pre[x]); } int kurskal() { sort(M, M + m); int N = n, res = 0; for (int i = 0; i < m && N > 1; i++) { int fx = Find(M[i].from), fy = Find(M[i].to); if (fx != fy) { pre[fx] = fy; N--;//找到了一條邊,當N減到1的時候代表已經找到N-1條邊了,就完成了 res += M[i].dis; } } if (N == 1)//循環作完,N不等於1 代表沒有找到合適的N-1條邊來構成最小生成樹 return res; return -1; } int main() { cin >> n >> m; for (int i = 0; i <= n; i++) { pre[i] = i; } for (int i = 0; i < m; i++) { scanf("%d%d%d", &M[i].from, &M[i].to, &M[i].dis);; } int ans = kurskal(); if (ans != -1) cout << ans << endl; else cout << "orz" << endl; return 0; }
首先將圖的點分爲兩部分,一種是訪問過的$u$(第一條邊任選),一種是沒有訪問過的$v$
1: 每次找$u$到$v$的權值最小的邊。
2: 而後將這條邊中的$v$中的頂點添加到$u$中,直到$v$中邊的個數$=$頂點數$-1$
維護一個$dis$數組,記錄只使用已訪問節點可以到達各未訪問節點最短的權值。
初始值爲節點1(任意一個均可以)到各點的值,規定到本身是0,到不了的是$inf$(定義一個特別大的數)。
找當前能到達的權值最短的點。1-->4,節點4
將dis[4]賦值爲0,標記爲已訪問過,同時藉助4節點更新dis數組。
後面依次
最後整個dis數組都是0了,最小生成樹也就出來了,若是$dis$數組中還有 $inf$ 的話,說明這不是一個連通圖。
仍是上面那道模板題:https://www.luogu.org/problem/P3366
#include <iostream> #include <fstream> using namespace std; struct ENode { int dis, to;//權重、指向 ENode* next = NULL; void push(int to, int dis) { ENode* p = new ENode; p->to = to; p->dis = dis; p->next = next; next = p; } }*head; const int inf = 1 << 30; int N, M; int dis[5005]; int prim() { int res = 0; for (int i = 2; i <= N; i++) { dis[i] = inf; } for (int i = 0; i < N; i++) {//與kurskal區分,找邊是N-1條邊,找點是N個點 int v = 1 , MIN = inf; for (int j = 1; j <= N; j++) { //到不了的,訪問過的不進行比較 if (dis[j] != 0 && dis[j] < MIN) { v = j; MIN = dis[j]; } } if (MIN == inf && v != 1)//這裏v!=1是爲了把dis的初始化放在循環裏面作,也能夠放在循環外面作,可是外層循環就只須要作N-1次了 return -1;//還沒找夠n個點,沒路了 res += dis[v]; dis[v] = 0; ENode *p = head[v].next; while (p) { if (dis[p->to] > p->dis) { dis[p->to] = p->dis; } p = p->next; } } return res; } int main() { #ifdef LOCAL fstream cin("data.in"); #endif // LOCAL cin >> N >> M; head = new ENode[N + 1]; for (int i = 0; i < M; i++) { int from, to, dis; scanf("%d%d%d", &from, &to, &dis); //cin >> from >> to >> dis; head[from].push(to, dis); head[to].push(from, dis); } int ans = prim(); if (ans != -1) cout << ans << endl; else cout << "orz" << endl; return 0; }
時間複雜度爲$O(n^2)$,$n$爲頂點的數量,其時間複雜度與邊得數目無關,適合稠密圖。
時間複雜度爲$O(e\cdot loge)$,$e$爲邊的數目,與頂點數量無關,適合稀疏圖。
其實就是排序的時間,由於並查集的查詢、合併操做都是$O(1)$。
通俗點說就是,點多邊少用Kruskal,由於Kruskal算法每次查找最短的邊。 點少邊多用Prim,由於它是每次找一個頂點。
具體選擇用那個,能夠用電腦算一下,題目給的數據級別,$n^2$和$e\cdot loge$看看那個小,好比上面的模板題,題目給的數據級別是$(n<=5000,e<=200000)$,粗略估算一下,kurskal算法必定是會快很多的,結果也確實如粗。
明眼人都能看出來,kurskal算法要簡單太多了。kurskal算法不須要把圖表示出來,而Prim算法必須建表或者鄰接矩陣,因此從上面的數據也能看出來當邊的數目較大時,Prim算法所佔用的空間比kurskal算法多了不少。
用堆存儲當前全部可到達的點和距離,就是把dis數組裏的內容一式兩份,存在堆裏,而後每次取堆頂元素,每次操做爲$O(logn)$,因此使用堆優化後的Prim算法理論上時間複雜度爲$O(nlogn)$,可是好像沒有達到想要的效果
看了測試數據發現,有不少重邊,那就合理了,作了不少次的無用循環,因此時間上也和kurskal比較相近。因此在數據可靠、無重邊的狀況下,這個算法必定是上述幾種中最快的一個。
#include <iostream> #include <fstream> #include <cstdio> #include <queue> using namespace std; struct P { int dis, v; P(int d, int v) :dis(d), v(v) {}; bool operator<(P p)const { return p.dis < dis; } }; struct ENode { int dis, to;//權重、指向 ENode* next = NULL; void push(int to, int dis) { ENode* p = new ENode; p->to = to; p->dis = dis; p->next = next; next = p; } }*head; const int inf = 1 << 30; int N, M; int dis[5005]; bool fuck[5005]; int prim() { priority_queue<P>pq; pq.push(P(0, 1)); int res = 0, cnt = N; dis[1] = 0; fill(dis + 1, dis + N + 1, inf); while (!pq.empty() && cnt > 0) {//與kurskal區分,找邊是N-1條邊,找點是N個點 int v = pq.top().v, d = pq.top().dis; pq.pop(); if (fuck[v])continue; fuck[v] = true; res += d; cnt--; ENode* p = head[v].next; while (p) { if (dis[p->to] > p->dis) { dis[p->to] = p->dis; pq.push(P(p->dis, p->to)); } p = p->next; } } if (cnt > 1) return -1; return res; } int main() { #ifdef LOCAL fstream cin("data.in"); #endif // LOCAL cin >> N >> M; head = new ENode[N + 1]; for (int i = 0; i < M; i++) { int from, to, dis; scanf("%d%d%d", &from, &to, &dis); //cin >> from >> to >> dis; head[from].push(to, dis); head[to].push(from, dis); } int ans = prim(); if (ans != -1) cout << ans << endl; else cout << "orz" << endl; return 0; }