本人QQ :2319411771 郵箱 : cyb19950118@163.com
若您發現本文有什么錯誤,請聯系我,我會及時改正的,謝謝您的合作!
本文為原創文章,轉載請注明出處
本文鏈接 :http://www.cnblogs.com/Yan-C/p/3943940.html 。
哎呀,好久了啊,想寫這篇博文好久了,但是因為懶的原因 一直遲遲沒動手啊。
今天,終於在長久的懶惰下,突然來了那么一點熱度。把這篇博文寫一下。
本文分為以下幾個部分 :
1、 拓撲排序
2、 並查集
3、 普利姆算法 & 優先隊列優化
4、 克魯斯卡爾算法
前情提要 : 本文的存圖方式 只有兩種 : 鄰接矩陣 or 前向星。
1、 拓撲排序
我們起床穿褲子和鞋子時,相信大部分人的順序是這樣的,先穿上內褲,然后再穿上褲子,再穿上襪子,然后才是鞋子。 那么,我們把這些步驟分解:
(1)穿內褲
(2)穿褲子
(3)穿襪子
(4)穿鞋子
我們把這四個步驟,按照上述的順序 給排一下,這就是所謂的拓撲排序。
當然這個排序的順序是唯一的,如果你先進行(2)然后(1)(3)(4),哦,不,你不是超人,請不要這樣做, 又假如你按照(1)(2)(4)(3), 那顯然也是不行的。
拓撲排序 也可以描述一個暑假寫作業的過程 : 語文作業,數學作業,英語作業,生物作業,化學作業,物理作業。
(1) 語文
(2) 數學
(3) 英語
(4) 生物
(5) 化學
(6) 物理
你可以是(1)(2)(3)(4)(5)(6),也可以是(6)(5)(4)(3)(2)(1),再者英語老師比較凶,那么可以是(3)(1)(2)(4)(5)(6)。等等其他的排序方式。
那么這個排序又是不唯一的。
因此 拓撲排序可能是唯一的又有可能是不唯一的。
就像 3個籃球隊進行比賽。 編號分別為 1 , 2 , 3。
1打贏了2
2打贏了3
3打贏了1。 問誰是最后的冠軍。 各一勝一負你問我誰是冠軍 ,這不是扯蛋嘛。 So,這是不能判斷誰是冠軍的, 因為這個事件存在一個 環,互相牽制,進行排序是不行產生結果的。
如果這樣 :
1打贏了2
3打贏了2
那么最后的冠軍可能是不確定的,因為你不知道1和3 誰強。 所以只能是 1,3並列了,你如果喜歡大數在前 那就是3 1 2,反之,就是1 3 2了。
拓撲排序其實就是這個樣子。
前面大篇幅的扯犢子,主要是介紹什么是拓撲排序。 那么我們要討論一下,怎么樣進行拓撲排序呢? 哎,這個問題好!
插播 :
我們再次的從 1 2 3 這三支隊伍的冠軍爭奪賽說起。
1打贏了2 因為2輸了一場比賽,所以要給2做一標記。因此2號的菊花上就出現了一桿長槍。 我們稱這個標記為 入度 那么2的入度就是 1了。
3打贏了2 因為2又輸了一場比賽,又是一桿長槍啊。為什么受傷的總是2。 那么2的入度 就++了 變成了2。
好了 這就是 什么是 入度 了。 如果你還不是很懂入度是什么。那我告訴你,入度 在這里就是2號被打敗了幾次。
那我們 就要 進入正題了。
拓撲排序 :
由AOV網構造拓撲序列的拓撲排序算法主要是循環執行以下兩步,直到不存在入度為0的頂點為止。
循環結束后,若輸出的頂點數小於網中的頂點數,則輸出“有回路”信息,否則輸出的頂點序列就是一種拓撲序列。 (摘自 : 百度百科)
我們繼續 以題來進行講解和理解的加深。
1 Description 2 有N個比賽隊(1<=N<=500),編號依次為1,2,3,。。。。,N進行比賽,比賽結束后,裁判委員會要將所有參賽隊伍從前往后依次排名,但現在裁判委員會不能直接獲得每個隊的比賽成績,只知道每場比賽的結果,即P1贏P2,用P1,P2表示,排名時P1在P2之前。現在請你編程序確定排名。 3 4 5 Input 6 輸入有若干組,每組中的第一行為二個數N(1<=N<=500),M;其中N表示隊伍的個數,M表示接着有M行的輸入數據。接下來的M行數據中,每行也有兩個整數P1,P2表示即P1隊贏了P2隊。 7 8 9 Output 10 給出一個符合要求的排名。輸出時隊伍號之間有空格,最后一名后面沒有空格。 11 12 其他說明:符合條件的排名可能不是唯一的,此時要求輸出時編號小的隊伍在前;輸入數據保證是正確的,即輸入數據確保一定能有一個符合要求的排名。 13 14 15 Sample Input 16 17 4 3 18 1 2 19 2 3 20 4 3 21 22 23 24 Sample Output 25 26 1 2 4 3
題目鏈接:在這
因為數據較小,我們可以使用鄰接矩陣進行存儲。 這是第一種方法。
題解在這 :
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 const int INF = 1e9+7; 9 const int VM = 503;// 點的個數 10 11 bool G[VM][VM];//圖 12 int deg[VM];//各個頂點的入度 計數 13 14 void toposort(int n) {//拓撲排序 15 int k = 0; 16 17 for (int i = 1; i <= n; i++) {//共進行|G.V|次操作 18 for (int j = 1; j <= n; j++) {//遍歷所有的頂點 找入度為0的 19 if (deg[j] == 0) {//找到 20 printf("%d%c", j, i == n ? '\n' : ' ');//輸出 21 deg[j]--;//去掉這個點 讓deg[j] = -1; 22 k = j;//記錄這個點 23 break;//跳出循環 24 } 25 } 26 for (int j = 1; j <= n; j++)//遍歷所有的點 27 if (G[k][j] == true) {//找被此點打敗過的點 28 G[k][j] = false;//標記為找到過 29 deg[j]--;//讓這個點的入度-1 30 } 31 } 32 } 33 34 int main() { 35 int n, m; 36 37 while (scanf("%d %d", &n, &m) == 2) {//多組輸入, 獲取n, m 38 memset(G, 0, sizeof(G));//初始化 39 memset(deg, 0, sizeof(deg));//初始化 40 while (m--) { 41 int u, v; 42 scanf("%d %d", &u, &v);//獲取 u,v u打敗過v 43 if (G[u][v] == false) {//防止重邊 如果被同一個對手打敗多次,也太傷v的心了 44 G[u][v] = true;//標記為真 45 deg[v]++;//v的入度++ 一桿長槍入洞了。 46 } 47 } 48 toposort(n);//調用函數 49 } 50 return 0; 51 }
主函數 對數據的獲取 和存圖。
1 int main() { 2 int n, m; 3 4 while (scanf("%d %d", &n, &m) == 2) {//多組輸入, 獲取n, m 5 memset(G, 0, sizeof(G));//初始化 6 memset(deg, 0, sizeof(deg));//初始化 7 while (m--) { 8 int u, v; 9 scanf("%d %d", &u, &v);//獲取 u,v u打敗過v 10 if (G[u][v] == false) {//防止重邊 如果被同一個對手打敗多次,也太傷v的心了 11 G[u][v] = true;//標記為真 12 deg[v]++;//v的入度++ 一桿長槍入洞了。 13 } 14 } 15 toposort(n);//調用函數 16 } 17 return 0; 18 }
拓撲排序的函數 :
1 void toposort(int n) {//拓撲排序 2 int k = 0; 3 4 for (int i = 1; i <= n; i++) {//共進行|G.V|次操作 5 for (int j = 1; j <= n; j++) {//遍歷所有的頂點 找入度為0的 6 if (deg[j] == 0) {//找到 7 printf("%d%c", j, i == n ? '\n' : ' ');//輸出 8 deg[j]--;//去掉這個點 讓deg[j] = -1; 9 k = j;//記錄這個點 10 break;//跳出循環 11 } 12 } 13 for (int j = 1; j <= n; j++)//遍歷所有的點 14 if (G[k][j] == true) {//找被此點打敗過的點 15 G[k][j] = false;//標記為找到過 16 deg[j]--;//讓這個點的入度-1 17 } 18 } 19 }
此算法的時間復雜度為 O(n * n) 復雜度挺高的呢。
那我們要想辦法優化啊。
來了 , 第二種 時間復雜度為 O(V + E) 在這個算法中 我們用到了 前向星 和 優先隊列。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 503;// 點的個數 12 13 struct node {//前向星的結構體 14 int v;//輸隊編號 15 int next; 16 }; 17 node edge[VM * 4];//結構體數組 18 int head[VM];//頭指針數組 19 int cnt;//下標 20 int deg[VM];//入度數組 21 22 void toposort(int n) { 23 priority_queue<int, vector<int>, greater<int> > que;//優先隊列 24 25 for (int i = 1; i <= n; i++)//找所有點 26 if (deg[i] == 0) {//入度為 0 27 que.push(i);//加入隊列 28 deg[i]--;//入度 變為 -1 29 } 30 int k = 1; 31 while (que.empty() == false) {//隊列不為空 32 int u = que.top();//取出隊首的數 33 que.pop();//刪除 34 printf("%d%c", u, k++ == n ? '\n' : ' ');//輸出 35 for (int i = head[u]; i != -1; i = edge[i].next) {//與該點相連的 36 node e = edge[i];//便於書寫 37 deg[e.v]--;//點的入度 -1 38 if (deg[e.v] == 0)//若此點的 入度為 0 39 que.push(e.v);//放入隊列 40 } 41 } 42 } 43 44 int main() { 45 int n, m; 46 int i; 47 48 while (scanf("%d %d", &n, &m) == 2) {//多組輸入 ,獲取n,m 49 memset(head, -1, sizeof(head));//初始化 50 memset(deg, 0, sizeof(deg));//初始化 51 cnt = 0;//初始化 52 while (m--) { 53 int u, v; 54 scanf("%d %d", &u, &v);//獲取u,v 55 for (i = head[u]; i != -1; i = edge[i].next)//查找重邊 56 if (edge[i].v == v)//輸入重復數據 57 break;//不再儲存 58 if (i == -1) {//若不是重復數據 59 deg[v]++;//加邊 60 edge[cnt].v = v; 61 edge[cnt].next = head[u]; 62 head[u] = cnt++; 63 } 64 } 65 toposort(n);//調用函數 66 } 67 return 0; 68 }
所用到的數據結構 :
1 priority_queue<int, vector<int>, greater<int> > que;//優先隊列 2 struct node {//前向星的結構體 3 int v;//輸隊編號 4 int next; 5 }; 6 node edge[VM * 4];//結構體數組 7 int head[VM];//頭指針數組 8 int cnt;//下標
主函數對數據的獲取和 圖的存儲
1 int main() { 2 int n, m; 3 int i; 4 5 while (scanf("%d %d", &n, &m) == 2) {//多組輸入 ,獲取n,m 6 memset(head, -1, sizeof(head));//初始化 7 memset(deg, 0, sizeof(deg));//初始化 8 cnt = 0;//初始化 9 while (m--) { 10 int u, v; 11 scanf("%d %d", &u, &v);//獲取u,v 12 for (i = head[u]; i != -1; i = edge[i].next)//查找重邊 13 if (edge[i].v == v)//輸入重復數據 14 break;//不再儲存 15 if (i == -1) {//若不是重復數據 16 deg[v]++;//加邊 17 edge[cnt].v = v; 18 edge[cnt].next = head[u]; 19 head[u] = cnt++; 20 } 21 } 22 toposort(n);//調用函數 23 } 24 return 0; 25 }
拓撲排序函數
1 void toposort(int n) { 2 priority_queue<int, vector<int>, greater<int> > que;//優先隊列 3 4 for (int i = 1; i <= n; i++)//找所有點 5 if (deg[i] == 0) {//入度為 0 6 que.push(i);//加入隊列 7 deg[i]--;//入度 變為 -1 8 } 9 int k = 1; 10 while (que.empty() == false) {//隊列不為空 11 int u = que.top();//取出隊首的數 12 que.pop();//刪除 13 printf("%d%c", u, k++ == n ? '\n' : ' ');//輸出 14 for (int i = head[u]; i != -1; i = edge[i].next) {//與該點相連的 15 node e = edge[i];//便於書寫 16 deg[e.v]--;//點的入度 -1 17 if (deg[e.v] == 0)//若此點的 入度為 0 18 que.push(e.v);//放入隊列 19 } 20 } 21 }
拓撲排序 講解 完畢。
2、並查集
並查集從字面上最起碼可以看出是一個集合,而且是能並(合並嗎?) 能查的集合。集合也就是分組,一組一組的數據,這一組就是一個集合嘛。
並查集是一種用來管理元素分組情況的數據結構。 並查集,並查集,那么他的功能肯定就是 並 和 查。
他可以高效的進行 :
並 合並元素a和元素b所在的組。
查 查詢元素a和元素b是否屬於同一組。
並查集可以進行合並 但是卻不能進行分割。
並查集的結構 是 樹形結構,但是他卻不是二叉樹,因為是樹,所以必定有根節點,根節點就是這個集合,這個分組中最大的統領着。
對於並查集呢,主要是有兩部分函數構成, 一個是union()函數 也就是我們所說的並(合並),另一個是find()函數 也就是所說的查函數。
對於並查集不會畫圖真的是好糾結。
對於並查集,大家看這個大牛的博客的講解吧, 如果大家不想看的話,可以直接看下面的代碼講解,注釋還是很清晰的。
http://www.cnblogs.com/cyjb/p/UnionFindSets.html
看完講解 大家可以看一下這些題目加深一下。(腦子有點亂亂的,原諒我的“亂來”)。
我們對並查集的初始化
1 for (int i = 1; i <= n; i++) 2 par[i] = i;//這是初始化
這是find() 函數
1 int find(int x) {//查找函數 2 if (par[x] == x)//若本身就是根節點 ,那么return 3 return x; 4 return find(par[x]);//不是的話,繼續查找 5 }
這是合並函數
1 void unite(int x, int y) {//合並函數 2 x = find(x);//查找x的根節點 3 y = find(y);//查找y的根節點 4 if (x == y)//若這兩個數的結點是一樣的,那么他們本來就是一個分組里的了,所以沒有操作 5 return ; 6 par[x] = y;//不然的話,就讓其中的一個點成為另一個點的根節點。 7 }
還有一個判斷的 same函數
1 bool same(int x, int y) { 2 return find(x) == find(y); 3 }
same函數 下面的題解中都是 直接判斷的,所以就把same這個函數直接放在了里面,就這樣 same 函數 被我隱藏了。
這上面的 查找函數和合並函數 都是未經優化的,是比較原始的,下面我們用它做一道題。
題目鏈接在這 我就是題目鏈接
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 100003; 12 13 int par[VM]; 14 15 int find(int x) {//查找函數 16 if (par[x] == x)//若本身就是根節點 ,那么return 17 return x; 18 return find(par[x]);//不是的話,繼續查找 19 } 20 21 void unite(int x, int y) {//合並函數 22 x = find(x);//查找x的根節點 23 y = find(y);//查找y的根節點 24 if (x == y)//若這兩個數的結點是一樣的,那么他們本來就是一個分組里的了,所以沒有操作 25 return ; 26 par[x] = y;//不然的話,就讓其中的一個點成為另一個點的根節點。 27 } 28 29 int main() { 30 int n, m; 31 int t = 0; 32 33 while (scanf("%d %d", &n, &m), n + m) { 34 for (int i = 1; i <= n; i++) 35 par[i] = i;//這是初始化 36 while (m--) { 37 int u, v; 38 scanf("%d %d", &u, &v); 39 unite(u, v); 40 } 41 int ans = 0; 42 for (int i = 1; i <= n; i++) 43 if (i == par[i]) 44 ans++;//計數 45 printf("Case %d: %d\n", ++t, ans);//輸出 46 } 47 return 0; 48 }
既然說了上面是未優化的,那這兒就要說一下優化的嘍。
我們的代碼需要用到路徑壓縮 和 這課樹(也就是分組)的高度。
若不知道這兩個東東的 話 ,還是這位大牛的 http://www.cnblogs.com/cyjb/p/UnionFindSets.html
find函數的優化
int find(int x) {//查找函數 if (par[x] == x)//若本身就是根節點 ,那么return return x; return par[x] = find(par[x]);//不是的話,繼續查找,並且進行路徑壓縮。 //上面為遞歸版本 /* int a = x; while (a != par[a])//一直找到a的 根節點 a = par[a]; while (x != par[x]) {//路徑壓縮 int t = par[x]; par[x] = a; x = t; } return a; */ //上面為非遞歸版本 }
合並函數的優化
1 void unite(int x, int y) {//合並函數 2 x = find(x);//查找x的根節點 3 y = find(y);//查找y的根節點 4 if (x == y)//若這兩個數的結點是一樣的,那么他們本來就是一個分組里的了,所以沒有操作 5 return ; 6 if (rank[x] < rank[y]) //不然的話, 如果x這個分組的高度小於y分組的高度 7 par[x] = y;//將x並到 y這個分組中,並且是x的父節點是y 8 else { 9 par[y] = x;//不然就是y的父節點為x 10 if (rank[x] == rank[y])//若兩個分組的高度相同 11 rank[x]++;//x 的分組高度++ 12 } 13 }
在這給出 這個題目的 優化的代碼。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 100003; 12 13 int par[VM]; 14 int rank[VM]; 15 16 int find(int x) {//查找函數 17 if (par[x] == x)//若本身就是根節點 ,那么return 18 return x; 19 return par[x] = find(par[x]);//不是的話,繼續查找,並且進行路徑壓縮。 20 21 //上面為遞歸版本 22 /* 23 int a = x; 24 while (a != par[a])//一直找到a的 根節點 25 a = par[a]; 26 while (x != par[x]) {//路徑壓縮 27 int t = par[x]; 28 par[x] = a; 29 x = t; 30 } 31 return a; 32 */ 33 //上面為非遞歸版本 34 } 35 36 void unite(int x, int y) {//合並函數 37 x = find(x);//查找x的根節點 38 y = find(y);//查找y的根節點 39 if (x == y)//若這兩個數的結點是一樣的,那么他們本來就是一個分組里的了,所以沒有操作 40 return ; 41 if (rank[x] < rank[y]) //不然的話, 如果x這個分組的高度小於y分組的高度 42 par[x] = y;//將x並到 y這個分組中,並且是x的父節點是y 43 else { 44 par[y] = x;//不然就是y的父節點為x 45 if (rank[x] == rank[y])//若兩個分組的高度相同 46 rank[x]++;//x 的分組高度++ 47 } 48 } 49 50 int main() { 51 int n, m; 52 int t = 0; 53 54 while (scanf("%d %d", &n, &m), n + m) { 55 for (int i = 1; i <= n; i++) 56 par[i] = i;//這是初始化 57 memset(rank, 0, sizeof(rank));//樹的高度 為 0 初始化 58 while (m--) { 59 int u, v; 60 scanf("%d %d", &u, &v); 61 unite(u, v); 62 } 63 int ans = 0; 64 for (int i = 1; i <= n; i++) 65 if (i == par[i]) 66 ans++;//計數 67 printf("Case %d: %d\n", ++t, ans);//輸出 68 } 69 return 0; 70 }
再來一個題 : 題目題目
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 100003; 12 13 int par[VM]; 14 int rank[VM]; 15 16 int find(int x) {//查找函數 17 if (par[x] == x)//若本身就是根節點 ,那么return 18 return x; 19 return par[x] = find(par[x]);//不是的話,繼續查找,並且進行路徑壓縮。 20 21 //上面為遞歸版本 22 /* 23 int a = x; 24 while (a != par[a])//一直找到a的 根節點 25 a = par[a]; 26 while (x != par[x]) {//路徑壓縮 27 int t = par[x]; 28 par[x] = a; 29 x = t; 30 } 31 return a; 32 */ 33 //上面為非遞歸版本 34 } 35 36 void unite(int x, int y) {//合並函數 37 x = find(x);//查找x的根節點 38 y = find(y);//查找y的根節點 39 if (x == y)//若這兩個數的結點是一樣的,那么他們本來就是一個分組里的了,所以沒有操作 40 return ; 41 if (rank[x] < rank[y]) //不然的話, 如果x這個分組的高度小於y分組的高度 42 par[x] = y;//將x並到 y這個分組中,並且是x的父節點是y 43 else { 44 par[y] = x;//不然就是y的父節點為x 45 if (rank[x] == rank[y])//若兩個分組的高度相同 46 rank[x]++;//x 的分組高度++ 47 } 48 } 49 50 int main() { 51 int n, m; 52 53 while (scanf("%d %d", &n, &m) == 2) { 54 for (int i = 0; i < n; i++) 55 par[i] = i;//這是初始化 56 memset(rank, 0, sizeof(rank));//樹的高度 為 0 初始化 57 while (m--) { 58 int u, v; 59 scanf("%d %d", &u, &v); 60 unite(u, v); 61 } 62 int ans = 0; 63 for (int i = 1; i < n; i++) 64 if (find(par[0]) == find(par[i])) 65 ans++;//計數 66 printf("%d\n", ans);//輸出 67 } 68 return 0; 69 }
不會畫圖,就不好講了。
並查集 算是馬馬虎虎的說完了吧。。。。
插播 :
什么是 生成樹?什么 又是 最小生成樹?
給定一個無向圖,如果它的某個子圖中的任意兩個頂點都互相聯通並且是一棵樹,那么這棵樹就是 生成樹 。
也就是說,在一個圖中,有 n 個頂點 ,若有 n - 1 條邊,能使得所有的頂點相連 ,就是 生成樹了。
如果你給這些邊 加上權值 ,那 權值 總和最小的額生成樹 就是最小生成樹
再插 :
最小生成樹 有兩種方法 一種 : 普利姆算法 另一種 : 克魯斯卡爾。
3、 普利姆算法 & 優先隊列優化
prim算法和Dijkstra算法十分相似,都是從某個頂點出發,不斷加邊的算法。
1. 假設有一棵樹只包含一個頂點的v的樹T。
2.貪心的選取T和其他頂點之間相連的最小權值的邊,並將它加入T中。
3.不斷重復1,2 知道所有的點相連生成一棵最小生成樹。(此算法的正確性,不給予證明)
下面開始練題。
題目 : 我是題目 請點擊
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 6 using namespace std; 7 8 const int INF = 1e9+7; 9 const int VM = 103; 10 11 int G[VM][VM];//存圖 12 13 void prim(int n) { 14 int dis[VM];//記錄 邊的權值 15 bool vis[VM];//記錄為否訪問 16 int ans = 0;// 17 18 memset(vis, 0, sizeof(vis));//初始化 19 for (int i = 1; i <= n; i++) 20 dis[i] = G[1][i];//初始化 21 dis[1] = 0;// 22 vis[1] = true;// 1 點標記為已訪問 23 int i; 24 for (i = 2; i <= n; i++) {//進行 n - 1 次操作 25 int u = INF;//初始化 26 int k; 27 for (int j = 1; j <= n; j++) {//遍歷所有頂點 28 if (!vis[j] && u > dis[j]) {//在所有的未加入的點中 找一個最小的權值 29 k = j;//記錄下標 30 u = dis[j];//更新最小值 31 } 32 } 33 if (u == INF)//若圖是不連通的 34 break;//提前退出 35 vis[k] = true;//標記為已加入 36 ans += u;//加權值 37 for (int j = 1; j <= n; j++) {//遍歷所有的點 38 if (!vis[j] && dis[j] > G[k][j])//對未加入的點&&能找到與此點相連且的權值最小的邊 39 dis[j] = G[k][j];//進行更新 40 } 41 } 42 //輸出 43 if (i - 1 == n) 44 printf("%d\n", ans); 45 else 46 printf("?\n"); 47 } 48 49 int main() { 50 int n, m; 51 52 while (scanf("%d %d", &n, &m), n) {//對邊數 和點數的獲取 53 for (int i = 1; i <= m; i++) {//初始化 54 for (int j = 1; j <= m; j++) { 55 G[i][j] = i == j ? 0 : INF; 56 } 57 } 58 while (n--) { 59 int u, v, w; 60 scanf("%d %d %d", &u, &v, &w);//獲取 數據 61 if (G[u][v] > w)//防止重邊&&存兩點之間的最短距離 62 G[u][v] = G[v][u] = w; 63 } 64 prim(m);//調用函數 65 } 66 return 0; 67 }
主函數對數據的獲取及圖的存儲
1 int main() { 2 int n, m; 3 4 while (scanf("%d %d", &n, &m), n) {//對邊數 和點數的獲取 5 for (int i = 1; i <= m; i++) {//初始化 6 for (int j = 1; j <= m; j++) { 7 G[i][j] = i == j ? 0 : INF; 8 } 9 } 10 while (n--) { 11 int u, v, w; 12 scanf("%d %d %d", &u, &v, &w);//獲取 數據 13 if (G[u][v] > w)//防止重邊&&存兩點之間的最短距離 14 G[u][v] = G[v][u] = w; 15 } 16 prim(m);//調用函數 17 } 18 return 0; 19 }
普利姆函數
1 void prim(int n) { 2 int dis[VM];//記錄 邊的權值 3 bool vis[VM];//記錄為否訪問 4 int ans = 0;// 5 6 memset(vis, 0, sizeof(vis));//初始化 7 for (int i = 1; i <= n; i++) 8 dis[i] = G[1][i];//初始化 9 dis[1] = 0;// 10 vis[1] = true;// 1 點標記為已訪問 11 int i; 12 for (i = 2; i <= n; i++) {//進行 n - 1 次操作 13 int u = INF;//初始化 14 int k; 15 for (int j = 1; j <= n; j++) {//遍歷所有頂點 16 if (!vis[j] && u > dis[j]) {//在所有的未加入的點中 找一個最小的權值 17 k = j;//記錄下標 18 u = dis[j];//更新最小值 19 } 20 } 21 if (u == INF)//若圖是不連通的 22 break;//提前退出 23 vis[k] = true;//標記為已加入 24 ans += u;//加權值 25 for (int j = 1; j <= n; j++) {//遍歷所有的點 26 if (!vis[j] && dis[j] > G[k][j])//對未加入的點&&能找到與此點相連且的權值最小的邊 27 dis[j] = G[k][j];//進行更新 28 } 29 } 30 //輸出 31 if (i - 1 == n) 32 printf("%d\n", ans); 33 else 34 printf("?\n"); 35 }
上面的算法的時間復雜度為O(V * V),是不是和Dijkstra很相似呢?那么可不可用優化Dijkstra算法的方法來優化這個呢? 當然可以
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 103; 12 13 typedef pair<int, int>P;//對組 14 struct node {//前向星 結構體 15 int v, w; 16 int next; 17 }; 18 node edge[4 * VM];//前向星數組 19 int head[VM];//頭指針數組 20 int cnt;//計數 21 22 void add(int u, int v, int w) {//加邊函數 23 edge[cnt].v = v;//頂點 24 edge[cnt].w = w;//權值 25 edge[cnt].next = head[u];//下一個 26 head[u] = cnt++;//頭指針 27 } 28 29 void prim(int n) {//普利姆函數 30 bool vis[VM];//標記是否訪問過 31 int dis[VM];//記錄權值 32 int ans = 0;//最小生成樹的總值 33 int count = 0;//計數 34 priority_queue<P, vector<P>, greater<P> >que;//權值從小到大的隊列 35 36 fill(dis, dis + VM, INF);//初始化 37 memset(vis, 0, sizeof(vis));//初始化 38 dis[1] = 0;//初始化 39 que.push(P(0, 1));//將 1點 和 dis[1] = 0 放入隊列 40 while (que.empty() == false) {//隊列不為空時 41 P p = que.top();//取出隊首 42 que.pop();//刪除 43 int u = p.second;// 44 if (vis[u] == true)//若此頂點已經加入生成樹 45 continue;// 46 vis[u] = true;//否則,就標記為加入 47 ans += dis[u];// 48 count++;//加入點個數 49 for (int i = head[u]; i != -1; i = edge[i].next) {//遍歷與該點相鄰的點 50 node e = edge[i]; 51 if (dis[e.v] > e.w) {//更新他們的權值 52 dis[e.v] = e.w;// 53 que.push(P(dis[e.v], e.v));//放入隊列 54 } 55 } 56 } 57 //輸出 58 if (count == n) 59 printf("%d\n", ans); 60 else 61 printf("?\n"); 62 } 63 64 int main() { 65 int n, m; 66 67 while (scanf("%d %d", &n, &m), n) {//邊的個數 頂點個數 68 memset(head, -1, sizeof(head));//初始化 69 cnt = 0;//初始化 70 while (n--) { 71 int u, v, w; 72 scanf("%d %d %d", &u, &v, &w);//獲取數據 73 add(u, v, w);//加邊 74 add(v, u, w);//無向圖 75 } 76 prim(m);//普利姆算法 77 } 78 return 0; 79 }
主函數對數據的獲取 和 圖的存儲
1 int main() { 2 int n, m; 3 4 while (scanf("%d %d", &n, &m), n) {//邊的個數 頂點個數 5 memset(head, -1, sizeof(head));//初始化 6 cnt = 0;//初始化 7 while (n--) { 8 int u, v, w; 9 scanf("%d %d %d", &u, &v, &w);//獲取數據 10 add(u, v, w);//加邊 11 add(v, u, w);//無向圖 12 } 13 prim(m);//普利姆算法 14 } 15 return 0; 16 }
prim函數
1 void prim(int n) {//普利姆函數 2 bool vis[VM];//標記是否訪問過 3 int dis[VM];//記錄權值 4 int ans = 0;//最小生成樹的總值 5 int count = 0;//計數 6 priority_queue<P, vector<P>, greater<P> >que;//權值從小到大的隊列 7 8 fill(dis, dis + VM, INF);//初始化 9 memset(vis, 0, sizeof(vis));//初始化 10 dis[1] = 0;//初始化 11 que.push(P(0, 1));//將 1點 和 dis[1] = 0 放入隊列 12 while (que.empty() == false) {//隊列不為空時 13 P p = que.top();//取出隊首 14 que.pop();//刪除 15 int u = p.second;// 16 if (vis[u] == true)//若此頂點已經加入生成樹 17 continue;// 18 vis[u] = true;//否則,就標記為加入 19 ans += dis[u];// 20 count++;//加入點個數 21 for (int i = head[u]; i != -1; i = edge[i].next) {//遍歷與該點相鄰的點 22 node e = edge[i]; 23 if (dis[e.v] > e.w) {//更新他們的權值 24 dis[e.v] = e.w;// 25 que.push(P(dis[e.v], e.v));//放入隊列 26 } 27 } 28 } 29 //輸出 30 if (count == n) 31 printf("%d\n", ans); 32 else 33 printf("?\n"); 34 }
此算法的時間復雜度為O(E*log(V)); 是不是很棒!
次算法結束。
4、 克魯斯卡爾算法
克魯斯卡爾算法就是利用了並查集這一方法,通過對所有邊從小到大排序后,判斷這兩個頂點是否在一個分組中,若在一個分組中,說明這兩個點已經加入到生成樹之中,若不在一個分組中
就可以直接加上這個邊了。
還是以上一題為例
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 #define LL long long 7 8 using namespace std; 9 10 const int INF = 1e9+7; 11 const int VM = 103; 12 13 struct node {//邊的結構體 14 int u, v, w; 15 }; 16 node edge[VM * 2]; 17 int rank[VM];//分組的高度 18 int par[VM];//父節點 19 20 bool cmp(const node &a, const node &b) { 21 return a.w < b.w;//按w從小到大排序 22 } 23 24 int find(int x) { 25 if (par[x] == x)//若根節點為本身 26 return x; 27 return par[x] = find(par[x]);//路徑壓縮 28 } 29 30 bool same(int x, int y) {//判斷為否在同一分組中 31 return find(x) == find(y); 32 } 33 34 void unite(int x, int y) { 35 x = find(x);//查找根節點 36 y = find(y);//查找根節點 37 if (x == y)//若以在同一分組 38 return ; 39 if (rank[x] < rank[y])//y所在分組的高度 大於x的 40 par[x] = y;//將y作x的父節點。 41 else { 42 par[y] = x;//將x作為y的父節點 43 if (rank[x] == rank[y])//若兩個分組高度相同 44 rank[x]++;//x分組所在高度++ 45 } 46 } 47 48 int main() { 49 int n, m; 50 51 while (scanf("%d %d", &n, &m), n) {//獲取邊的個數 和頂點個數 52 int cnt = 0;// 53 for (int i = 1; i <= m; i++)//初始化 54 par[i] = i; 55 memset(rank, 0, sizeof(rank));//初始化 56 while (n--) { 57 scanf("%d %d %d", &edge[cnt].u, &edge[cnt].v, &edge[cnt].w);//獲取數據 58 cnt++; 59 } 60 sort(edge, edge + cnt, cmp);//按權值從小到大排序 61 int ans = 0;//最小生成樹 權值 62 int count = 0;//計數 63 for (int i = 0; i < cnt; i++) {//對所有的邊 64 node e = edge[i]; 65 if (!same(e.u, e.v)) {//若兩點不屬於一個分組 66 ans += e.w;//權值總和 67 unite(e.u, e.v);//合並兩點 68 count++;//計數 69 } 70 } 71 //輸出 72 if (count == m - 1) 73 printf("%d\n", ans); 74 else 75 printf("?\n"); 76 } 77 return 0; 78 }
自己感覺這個專題寫的好糟糕啊,哎,深夜了,睡覺吧。明天又是新的一天啊。因為明天,不,今天8點就要開始新學期第一堂課了啊。
