拓撲排序 詳解 + 並查集 詳解 + 最小生成樹(MST)詳解 【普利姆算法 + 優先隊列優化 & 克魯斯卡爾算法】


               本人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) 選擇一個入度為0的頂點並輸出之;
  (2) 從網中刪除此頂點及所有出邊。

  循環結束后,若輸出的頂點數小於網中的頂點數,則輸出“有回路”信息,否則輸出的頂點序列就是一種拓撲序列。 (摘自 : 百度百科)

 我們繼續 以題來進行講解和理解的加深。

 1 Description
 2 有N個比賽隊(1<=N<=500),編號依次為1,23,。。。。,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 號

  主函數 對數據的獲取 和存圖。

 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點就要開始新學期第一堂課了啊。

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM