標籤:style class blog code http tar
第一節 鏢局運鏢-圖的最小產生樹
所謂最小產生樹,就是在一個具有N個頂點的帶權連通圖G中,如果存在某個子圖G‘,其包含了圖G中的所有頂點和一部分邊,且不形成迴路,並且子圖G‘的各邊權值之和最小,則稱G‘為圖G的最小產生樹。
最小產生樹的三個性質
- 最小產生樹不能有迴路
- 最小產生樹可能是一個,也可能有多個
- 最小生產樹的邊的個數等於頂點的個數減一
Kruskal 演算法(選邊法)
其核心思想是:首先按照邊的權值進行從小到大排序,每次從剩餘的邊中選擇權值較小且邊的兩個頂點不在同一個集合的邊(就是不會產生迴路的邊),加入到生產樹中,直到加入了n-1條邊為止。其實現是 貪心+並查集
#include <iostream>#include <vector>#include <algorithm>using namespace std;struct edge{ int u; int v; int w; edge(int uu =0, int vv= 0, int ww = 0):u(uu),v(vv),w(ww){} bool operator<(const edge& a) const{ return w < a.w; }};vector<int> f;//建立並查集void make_set(int n){ for(int i = 0 ; i <= n; ++ i) f.push_back(i);}//查詢並查集int find_set(int x){ if(f[x] == x) return x; else{ f[x] = find_set(f[x]); return f[x]; }}//合并子集bool union_set(int x, int y){ int t1 = find_set(x), t2 = find_set(y); if(t1!=t2){ f[t2] = t1; return true; } return false;}int main(){ int n,m; cin >> n >> m; vector<edge> e(m+1); for(int i = 1 ; i <= m; ++ i){ cin >> e[i].u >> e[i].v >> e[i].w; } sort(e.begin(),e.end()); int sum = 0, cnt = 0; make_set(n); for(int i = 1; i <= m ; ++ i){ if(union_set(e[i].u,e[i].v)){ cnt++; sum+=e[i].w; } if(cnt == n-1) break; } cout<<sum<<endl;}Kruskal演算法
n代表頂點數,m代表邊數,對邊的快速排序時間複雜度是O(MlogM),在m條邊中找出n-1條邊是O(MlogN),所以Krusal演算法時間複雜度為O(MlogM+MlogN),通常M大於N,因此最終時間複雜度為O(MlogM)。
第二節 再談最小產生樹
Prim演算法(選點法)
其核心思想是:首先選擇任意一個頂點加入產生樹中,接下來找出一條邊添加到產生樹中,這需要枚舉每一樹頂點(已被選入生產樹的頂點)到每一個非樹頂點所有的邊,然後找出最短的邊加入到產生樹中。
#include <iostream>#include <vector>#include <algorithm>#define INF 100000using namespace std;int main(){ int n,m; cin >> n >> m; vector<vector<int> > e(n,vector<int>(n,INF)); for(int i = 0 ; i < n; ++ i) e[i][i] = 0; for(int i = 0 ; i < m; ++ i){ int u,v,w; cin>> u >> v >> w; --u;--v; e[u][v] = w; e[v][u] = w; } vector<int> dist(e[0].begin(),e[0].end()); vector<bool> visit(n,false); visit[0 ] = true; int cnt = 1,sum = 0; while(cnt < n){ int minw = INF,index = 0; for(int i = 0; i < n ; ++ i){ if(!visit[i] && dist[i] < minw){ minw = dist[i]; index = i; } } visit[index] = true; cnt++; sum +=minw; for(int k = 0; k < n; ++ k){ if(!visit[k] && dist[k]> e[index][k]){ dist[k] = e[index][k]; } } } cout<<sum<<endl;}Prim演算法
上述Prim演算法如果使用鄰接矩陣來儲存圖的話,時間複雜度是O(N^2),觀察代碼很容易發現,時間主要浪費在每次都要遍曆所有點找一個最小距離的頂點,對於這個操作,我們很容易想到用堆來最佳化,使得每次可以在log層級的時間找到距離最小的點,然後使用鄰接表格儲存體圖,整個演算法的時間複雜度會降到O(mlogn)
Kruskal演算法更適用於稀疏圖,沒有使用堆最佳化的Prim演算法適用於稠密圖,使用了堆最佳化的Prim演算法更適用於稀疏圖