【數據結構】 最小生成樹(四)——利用kruskal算法搞定例題×3+變形+一道大水題


  在這一專輯(最小生成樹)中的上一期講到了prim算法,但是prim算法比較難懂,為了避免看不懂,就先用kruskal算法寫題吧,下面將會將三道例題,加一道變形,以及一道大水題,水到不用高級數據結構,建樹,畫圖,最短路徑什么的,統統不需要。廢話不多說,直接看題:

1.例題精講

T1:

1348:【例4-9】城市公交網建設問題


時間限制: 1000 ms         內存限制: 65536 KB
提交數: 2094     通過數: 650 

【題目描述】

有一張城市地圖,圖中的頂點為城市,無向邊代表兩個城市間的連通關系,邊上的權為在這兩個城市之間修建高速公路的造價,研究后發現,這個地圖有一個特點,即任一對城市都是連通的。現在的問題是,要修建若干高速公路把所有城市聯系起來,問如何設計可使得工程的總造價最少?

【輸入】

n(城市數,1<≤n≤100)

e(邊數)

以下e行,每行3個數i,j,wiji,j,wij,表示在城市i,j之間修建高速公路的造價。

【輸出】

n-1行,每行為兩個城市的序號,表明這兩個城市間建一條高速公路。

【輸入樣例】

5 8
1 2 2
2 5 9
5 4 7
4 1 10
1 3 12
4 3 6
5 3 3
2 3 8

【輸出樣例】

1  2
2  3
3  4
3  5

  看完之后有思路嗎?這題肯定簡單,這是一道純模板題,只不過輸出有點麻煩。總之,先來回憶一下kruskal算法原理:首先需要找到圖中的一條最短的邊,如果它不與最小生成樹集合中的其他邊產生回路,那么就加入這條邊至集合中,上次小編寫的很草率,只是一個偽代碼(偽代碼如下),這次的題目小編會寫成正式代碼;接着,輸出又是一個麻煩事,這就需要分析樣例了,先好好看一看樣例,你發現了嗎?左邊的數總小於右邊的數,下面的第一個數總比上面的第一個數大,當然,如果第一個數一樣大,那么就按第二個數從小到大排序。依據這個規律,接着,就來看一看AC代碼吧。

 1 //假設MST[]是最小生成樹的集合,cnt表示是存入集合的邊數 
 2 while(cnt<n-1)//共有n-1條邊
 3 {
 4     在圖中找出最短的一條邊;
 5     if(添加這條邊不產生回路) 
 6     {
 7         加入MST集合;
 8         cnt++; 
 9     } 
10 }

//偽代碼

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 int n,e,a[1000],k,p,q;
 8 struct tree{
 9     int start;
10     int end;
11     int cost;
12 };
13 tree T[1000];
14 bool operator < (const tree& a,const tree& b)//按cost的值從小到大排序
15 {
16     return a.cost>b.cost;
17 }
18 bool cmp(tree a,tree b)//這個排序方式就是上面所說的關系
19 {
20     if(a.start==b.start)
21     return a.end<b.end;
22     return a.start<b.start;
23 }
24 priority_queue<tree>t;
25 inline int find(int x)//並查集 26 {
27     if(x==a[x]) return x;
28     else return a[x]=find(a[x]);
29 }
30 void kruskal()
31 {
32     for(int i=1;i<=n;i++)//並查集初始化
33     a[i]=i;
34     tree large;
35     for(int i=1;i<=e;i++)
36     {
37         large=t.top();//獲取最小邊
38         t.pop();
39         if(find(large.start)!=find(large.end))//如果不產生回路
40         {
41             p=find(large.start);q=find(large.end);
42             a[q]=p;
43             T[++k]=large;//加入到集合中
44         }
45     }
46     sort(T+1,T+k+1,cmp);//按規律排序,否則順序不對
47     for(int i=1;i<=k;i++)
48     printf("%d %d \n",T[i].start,T[i].end);
49 }
50 int main()
51 {
52     tree s;
53     scanf("%d%d",&n,&e);
54     for(int i=1;i<=e;i++)
55     {
56         scanf("%d%d%d",&s.start,&s.end,&s.cost);
57         if(s.start>s.end) swap(s.start,s.end);
58         t.push(s);
59     }
60     kruskal();
61     return 0;
62 }

//AC代碼

  這個代碼雖然看起來很長,但是效率很高,如果用數組來存儲,代碼確實精簡了,看起來確實易懂了,但是很浪費時間,每一次的最小邊都要花O(n)的時間去尋找,如果用堆(優先隊列),直接詢問隊頂元素就可以了。如果你並不清楚用着結構體的優先隊列,就一定不會理解以下這段代碼的意思,這段代碼表示按cost的值進行排序,因為結構體中有三個元素,不這么寫就無法按你的心意排序。

14 bool operator < (const tree& a,const tree& b)
15 {
16     return a.cost>b.cost;
17 }

T2:

1349:【例4-10】最優布線問題


時間限制: 1000 ms         內存限制: 65536 KB
提交數: 1228     通過數: 733 

【題目描述】

學校有n台計算機,為了方便數據傳輸,現要將它們用數據線連接起來。兩台計算機被連接是指它們有數據線連接。由於計算機所處的位置不同,因此不同的兩台計算機的連接費用往往是不同的。

當然,如果將任意兩台計算機都用數據線連接,費用將是相當龐大的。為了節省費用,我們采用數據的間接傳輸手段,即一台計算機可以間接的通過若干台計算機(作為中轉)來實現與另一台計算機的連接。

現在由你負責連接這些計算機,任務是使任意兩台計算機都連通(不管是直接的或間接的)。

【輸入】

第一行為整數n(2≤n≤100),表示計算機的數目。此后的n行,每行n個整數。第x+1行y列的整數表示直接連接第x台計算機和第y台計算機的費用。

【輸出】

一個整數,表示最小的連接費用。

【輸入樣例】

3
0 1 2
1 0 1
2 1 0

【輸出樣例】

2

  這道題和剛才的題出自同一個方法,似乎用prim算法更好,不知道你剛才的代碼還留着嗎?其實剛才的代碼稍加改動,這道題就能過了,下面來分析一下這道題與剛才的題的異同點,首先輸入一定是不同的,要輸入一個矩陣,所以這里要改一下;還有這道題只求和,不求每一條邊,這可是一個福利,不用像剛才一樣麻煩了,總之,廢話不多說,AC代碼呈上:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 int n,e,a[1000],k,p,q,ans,map[1000][1000];
 8 struct tree{
 9     int start;
10     int end;
11     int cost;
12 };
13 //tree T[1000];
14 bool operator < (const tree& a,const tree& b)
15 {
16     return a.cost>b.cost;
17 }
18 bool cmp(tree a,tree b)
19 {
20     if(a.start==b.start)
21     return a.end<b.end;
22     return a.start<b.start;
23 }
24 priority_queue<tree>t;
25 inline int find(int x)
26 {
27     if(x==a[x]) return x;
28     else return a[x]=find(a[x]);
29 }
30 void kruskal()
31 {
32     for(int i=1;i<=n;i++)
33     a[i]=i;
34     tree large;
35     for(int i=1;i<=e;i++)
36     {
37         large=t.top();
38         t.pop();
39         if(find(large.start)!=find(large.end))
40         {
41             p=find(large.start);q=find(large.end);
42             a[q]=p;
43             ans+=large.cost;
44         }
45     }
46 }
47 int main()
48 {
49     tree s;
50     scanf("%d",&n);
51     for(int i=1;i<=n;i++)
52     for(int j=1;j<=n;j++)
53     {
54         scanf("%d",&map[i][j]);
55         s.start=i;
56         s.end=j;
57         s.cost=map[i][j];
58         t.push(s);
59         if(i!=j)
60         e++;
61     }
62     kruskal();
63     printf("%d",ans);
64     return 0;
65 }

  具體就不怎么介紹了,這也是一道純模板題,如果你想練手,可以先寫一寫下面這道題。

T3:

1350:【例4-11】最短網絡(agrinet)


時間限制: 1000 ms         內存限制: 65536 KB
提交數: 1054     通過數: 711 

【題目描述】

農民約翰被選為他們鎮的鎮長!他其中一個競選承諾就是在鎮上建立起互聯網,並連接到所有的農場。當然,他需要你的幫助。約翰已經給他的農場安排了一條高速的網絡線路,他想把這條線路共享給其他農場。為了用最小的消費,他想鋪設最短的光纖去連接所有的農場。你將得到一份各農場之間連接費用的列表,你必須找出能連接所有農場並所用光纖最短的方案。每兩個農場間的距離不會超過100000。

【輸入】

第一行:農場的個數,N(3≤N≤100)。

第二行..結尾:后來的行包含了一個N*N的矩陣,表示每個農場之間的距離。理論上,他們是N行,每行由N個用空格分隔的數組成,實際上,他們限制在80個字符,因此,某些行會緊接着另一些行。當然,對角線將會是0,因為不會有線路從第i個農場到它本身。

 

【輸出】

只有一個輸出,其中包含連接到每個農場的光纖的最小長度。

【輸入樣例】

4
0  4  9  21
4  0  8  17
9  8  0  16
21 17 16  0

【輸出樣例】

28

  怎么樣,你寫出來了嗎?下面是AC代碼:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 int n,e,a[1000],k,p,q,ans,map[1000][1000];
 8 struct tree{
 9     int start;
10     int end;
11     int cost;
12 };
13 //tree T[1000];
14 bool operator < (const tree& a,const tree& b)
15 {
16     return a.cost>b.cost;
17 }
18 bool cmp(tree a,tree b)
19 {
20     if(a.start==b.start)
21     return a.end<b.end;
22     return a.start<b.start;
23 }
24 priority_queue<tree>t;
25 inline int find(int x)
26 {
27     if(x==a[x]) return x;
28     else return a[x]=find(a[x]);
29 }
30 void kruskal()
31 {
32     for(int i=1;i<=n;i++)
33     a[i]=i;
34     tree large;
35     for(int i=1;i<=e;i++)
36     {
37         large=t.top();
38         t.pop();
39         if(find(large.start)!=find(large.end))
40         {
41             p=find(large.start);q=find(large.end);
42             a[q]=p;
43             ans+=large.cost;
44         }
45     }
46 }
47 int main()
48 {
49     tree s;
50     scanf("%d",&n);
51     for(int i=1;i<=n;i++)
52     for(int j=1;j<=n;j++)
53     {
54         scanf("%d",&map[i][j]);
55         s.start=i;
56         s.end=j;
57         s.cost=map[i][j];
58         t.push(s);
59         if(i!=j)
60         e++;
61     }
62     kruskal();
63     printf("%d",ans);
64     return 0;
65 }

  有沒有發現什么?和T2一模一樣的代碼竟然在T3就過了,我猜出這道題的是為了讓我們再手敲一遍代碼加深理解,既然這道題是披着T3皮的T2,那就不解釋了,直接看一道變形。

2.牛刀小試

T4:

1391:局域網(net)


時間限制: 1000 ms         內存限制: 65536 KB
提交數: 1072     通過數: 658 

【題目描述】

某個局域網內有n(n≤100)台計算機,由於搭建局域網時工作人員的疏忽,現在局域網內的連接形成了回路,我們知道如果局域網形成回路那么數據將不停的在回路內傳輸,造成網絡卡的現象。因為連接計算機的網線本身不同,所以有一些連線不是很暢通,我們用f(i,j)表示i,j之間連接的暢通程度(f(i,j)≤1000),f(i,j)值越小表示i,j之間連接越通暢,f(i,j)為0表示i,j之間無網線連接。現在我們需要解決回路問題,我們將除去一些連線,使得網絡中沒有回路,並且被除去網線的Σf(i,j)最大,請求出這個最大值。

【輸入】

第一行兩個正整數n k

接下來的k行每行三個正整數i j m表示i,j兩台計算機之間有網線聯通,通暢程度為m。

【輸出】

一個正整數,Σf(i,j)的最大值。

【輸入樣例】

5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2

【輸出樣例】

8

  照之前的套路小編題也不看就猜到了要干什么,瀟灑的把T1和T2結合在一起,稍微一改輸入輸出,差點就提交了,幸虧最后試了一下輸入樣例,什么,竟然之前的模板不靈了!小編看了看題,才恍然大悟,也不過如此,這道題求的是產生回路的邊的和,那么就加產生回路的邊好了。AC代碼如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 int n,e,a[1000],k,p,q,ans;
 8 struct tree{
 9     int start;
10     int end;
11     int cost;
12 };
13 tree T[1000];
14 bool operator < (const tree& a,const tree& b)
15 {
16     return a.cost>b.cost;
17 }
18 bool cmp(tree a,tree b)
19 {
20     if(a.start==b.start)
21     return a.end<b.end;
22     return a.start<b.start;
23 }
24 priority_queue<tree>t;
25 inline int find(int x)
26 {
27     if(x==a[x]) return x;
28     else return a[x]=find(a[x]);
29 }
30 void kruskal()
31 {
32     for(int i=1;i<=n;i++)
33     a[i]=i;
34     tree large;
35     for(int i=1;i<=e;i++)
36     {
37         large=t.top();
38         t.pop();
39         if(find(large.start)!=find(large.end))
40         {
41             p=find(large.start);q=find(large.end);
42             a[q]=p;
43             
44         }
45         else ans+=large.cost;//注意這里有變
46     }
47 }
48 int main()
49 {
50     tree s;
51     scanf("%d%d",&n,&e);
52     for(int i=1;i<=e;i++)
53     {
54         scanf("%d%d%d",&s.start,&s.end,&s.cost);
55         if(s.start>s.end) swap(s.start,s.end);
56         t.push(s);
57     }
58     kruskal();
59     printf("%d",ans);
60     return 0;
61 }

  這道題輕松一變就過了,可以說這四道題是捆綁在一起的,一道過了,四道稍變就全過。其實例題一共有四道,但是為什么沒有把第四到放進來講呢?這是因為這道題是到大水題。

3.大水題

T5:

1351:【例4-12】家譜樹


時間限制: 1000 ms         內存限制: 65536 KB
提交數: 826     通過數: 568 

【題目描述】

有個人的家族很大,輩分關系很混亂,請你幫整理一下這種關系。

給出每個人的孩子的信息。

輸出一個序列,使得每個人的后輩都比那個人后列出。

【輸入】

第1行一個整數N(1≤N≤100),表示家族的人數;

接下來N行,第I行描述第I個人的兒子;

每行最后是0表示描述完畢。

 

【輸出】

輸出一個序列,使得每個人的后輩都比那個人后列出;

如果有多解輸出任意一解。

 

【輸入樣例】

5
0
4 5 1 0
1 0
5 3 0
3 0

【輸出樣例】

2 4 5 3 1

  這道題怎么辦?似乎找不到和最小生成樹的關系,和圖似乎有點關系,可是怎么寫呢?這又是一個迷,先看代碼再解釋:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 int n,a[1000][1000],ans[1000],cnt,w[1000],k,minn;bool vis[1000]={false};
 5 int main()
 6 {
 7     scanf("%d",&n);
 8     for(int i=1;i<=n;i++)
 9     for(int j=1;;j++)
10     {
11         scanf("%d",&a[i][j]);
12         if(a[i][j]==0) break;w[i]++;
13     }
14     for(int i=1;i<=n;i++)
15     {
16         minn=999999,k=0;
17         for(int j=1;j<=n;j++)
18         {
19             if(vis[j]==true) continue;
20             if(w[j]<minn)
21             {
22                 k=j;
23                 minn=w[j];
24             }
25         }
26         vis[k]=true;ans[++cnt]=k;
27         for(int j=1;j<=n;j++)
28         {
29             if(vis[j]==true) continue;
30             for(int l=1;l<=n;l++)
31             if(a[j][l]==k) w[j]--;
32         }
33     }
34     for(int i=cnt;i>=1;i--)
35     printf("%d ",ans[i]);
36     return 0;
37 }

                                          

  對,你沒有看錯,它雖然身處最小生成樹中,但是卻不需要高級數據結構,說白了就是個找規律題,小編也是瞎貓碰見死耗子,隨便一腦洞大開就AC了,當然,也歡迎用最小生成樹寫出的大佬評論感受,下面來講一講小編找出的規律:首先,題目告訴要把父輩排在子輩前面,小編想到了用並查集,但是貌似寫不出來,於是小編就猜想是不是兒子越多,輩分就越高,那么如果兒子一樣多又該怎么處理呢?接着,小編想了各種方法處理,最后突然想到可以讓他們減少兒子,那又怎么減呢?把已經定位好輩分的人從剩下人的兒子中抹掉,假裝其他人都失去了這個兒子。那么是應該先挑小輩呢?還是先挑長輩呢?當然是先挑小輩,因為比較好選,優先選長輩會出現很多奇怪的狀況,小編親手試過,有了這個大思路,小編就以試試的心態寫出了這個代碼,沒想到竟然過了,這道題不需要用樹,不需要圖,不需要最小生成樹竟然可以過,純找規律,真是道大水題。

  原本小編還是一知半解的,寫了幾道題以后終於弄明白了,建議大家也可以寫一寫。

4.附測評網站:

  1.【例4-9】城市公交網建設問題

  2.【例4-10】最優布線問題

  3.【例4-11】最短網絡(agrinet)

  4.【例4-12】家譜樹

  5.局域網(net)

專欄:

 

【數據結構】 最小生成樹(一)——什么是最小生成樹?

 

【數據結構】 最小生成樹(二)——kruskal算法

 

【數據結構】 最小生成樹(三)——prim算法

 

【數據結構】 最小生成樹(四)——利用kruskal算法搞定例題×3+變形+一道大水題

 


免責聲明!

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



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