【数据结构】 最小生成树四——利用kruskal算法搞定例题×3+变形+一道大水题

  在这一专辑(最小生成树)中的上一期讲到了prim算法,但是prim算法比较难懂,为了避免看不懂,就先用kruskal算法写题吧,下面将会将三道例题,加一道变形,以及一道大水题,水到不用高级数据结构,建树,画图,最短路径什么的,统统不需要。废话不多说,直接看题:

例题精讲

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,a[1000],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,ans);
64     return 0;
65 }

  有没有发现什么?和T2一模一样的代码竟然在T3就过了,我猜出这道题的是为了让我们再手敲一遍代码加深理解,既然这道题是披着T3皮的T2,那就不解释了,直接看一道变形。

牛刀小试

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,ans;
 8 struct tree{
 9     int start;
10     int end;
11     int cost;
12 };
13 tree T[1000];
14 bool operator < (const 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",&e);
52     for(int i=1;i<=e;i++)
53     {
54         scanf("%d%d%d",&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 }

  这道题轻松一变就过了,可以说这四道题是捆绑在一起的,一道过了,四道稍变就全过。其实例题一共有四道,但是为什么没有把第四到放进来讲呢?这是因为这道题是到大水题。

大水题

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],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了,当然,也欢迎用最小生成树写出的大佬评论感受,下面来讲一讲小编找出的规律:首先,题目告诉要把父辈排在子辈前面,小编想到了用并查集,但是貌似写不出来,于是小编就猜想是不是儿子越多,辈分就越高,那么如果儿子一样多又该怎么处理呢?接着,小编想了各种方法处理,最后突然想到可以让他们减少儿子,那又怎么减呢?把已经定位好辈分的人从剩下人的儿子中抹掉,假装其他人都失去了这个儿子。那么是应该先挑小辈呢?还是先挑长辈呢?当然是先挑小辈,因为比较好选,优先选长辈会出现很多奇怪的状况,小编亲手试过,有了这个大思路,小编就以试试的心态写出了这个代码,没想到竟然过了,这道题不需要用树,不需要图,不需要最小生成树竟然可以过,纯找规律,真是道大水题。

  原本小编还是一知半解的,写了几道题以后终于弄明白了,建议大家也可以写一写。

附测评网站:

  1.【例4-9】城市公交网建设问题

  2.【例4-10】最优布线问题

  3.【例4-11】最短网络(agrinet)

  4.【例4-12】家谱树

  5.局域网(net)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


【啊哈!算法】算法3:最常用的排序——快速排序       上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正则表达式就可以组成第一个构造。正如稍后要介绍的一样,既然也可以命名组,大家就有考虑把这个构造作为匿名组。作为一个实例,请看看下列字符串: “08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13“ 这个字符串就是由生日和年龄组成的。如果需要匹配年两而不要生日,就可以把正则
选择排序:从数组的起始位置处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素方式在第0个位置上,接着再从第1个位置开始再次进行排序操作。这种操作一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。 public void SelectionSort() { int min, temp;
public struct Pqitem { public int priority; public string name; } class CQueue { private ArrayList pqueue; public CQueue() { pqueue
在编写正则表达式的时候,经常会向要向正则表达式添加数量型数据,诸如”精确匹配两次”或者”匹配一次或多次”。利用数量词就可以把这些数据添加到正则表达式里面了。 数量词(+):这个数量词说明正则表达式应该匹配一个或多个紧紧接其前的字符。 string[] words = new string[] { "bad", "boy", "baad", "baaad" ,"bear", "b
来自:http://blog.csdn.net/morewindows/article/details/6678165/归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列
插入排序算法有两层循环。外层循环会啄个遍历数组元素,而内存循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内存循环选择的元素,那么瘦元素都想右移动以便为内存循环元素留出位置。 public void InsertionSort() { int inner, temp;
public int binSearch(int value) { int upperBround, lowerBound, mid; upperBround = arr.Length - 1; lowerBound = 0; while (lowerBound <= upper
虽然从表内第一个节点到最后一个节点的遍历操作是非常简单的,但是反向遍历链表却不是一件容易的事情。如果为Node类添加一个字段来存储指向前一个节点的连接,那么久会使得这个反向操作过程变得容易许多。当向链表插入节点的时候,为了吧数据复制给新的字段会需要执行更多的操作,但是当腰吧节点从表移除的时候就能看到他的改进效果了。 首先需要修改Node类来为累增加一个额外的链接。为了区别两个连接,这个把指
八、树(Tree)树,顾名思义,长得像一棵树,不过通常我们画成一棵倒过来的树,根在上,叶在下。不说那么多了,图一看就懂:当然了,引入了树之后,就不得不引入树的一些概念,这些概念我照样尽量用图,谁会记那么多文字?树这种结构还可以表示成下面这种方式,可见树用来描述包含关系是很不错的,但这种包含关系不得出现交叉重叠区域,否则就不能用树描述了,看图:面试的时候我们经常被考到的是一种叫“二叉树”的结构,二叉
Queue的实现: 就像Stack类的实现所做的一样,Queue类的实现用ArrayList简直是毋庸置疑的。对于这些数据结构类型而言,由于他们都是动态内置的结构,所以ArrayList是极好的实现选择。当需要往队列中插入数据项时,ArrayList会在表中把每一个保留的数据项向前移动一个元素。 class CQueue { private ArrayLis
来自:http://yingyingol.iteye.com/blog/13348911 快速排序介绍:快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地
Stack的实现必须采用一种基本结构来保存数据。因为再新数据项进栈的时候不需要担心调整表的大小,所以选择用arrayList.using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Collecti
数组类测试环境与排序算法using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Data_structure_and_algorithm{ class CArray { pr
一、构造二叉树 二叉树查找树由节点组成,所以需要有个Node类,这个类类似于链表实现中用到的Node类。首先一起来看看Node类的代码。 public class Node { public int Data; public Node Left; public Node Right; public v
二叉树是一种特殊的树。二叉树的特点是每个结点最多有两个儿子,左边的叫做左儿子,右边的叫做右儿子,或者说每个结点最多有两棵子树。更加严格的递归定义是:二叉树要么为空,要么由根结点、左子树和右子树组成,而左子树和右子树分别是一棵二叉树。 下面这棵树就是一棵二叉树。         二叉树的使用范围最广,一棵多叉树也可以转化为二叉树,因此我们将着重讲解二叉树。二叉树中还有连两种特殊的二叉树叫做满二叉树和
上一节中我们学习了队列,它是一种先进先出的数据结构。还有一种是后进先出的数据结构它叫做栈。栈限定只能在一端进行插入和删除操作。比如说有一个小桶,小桶的直径只能放一个小球,我们现在向小桶内依次放入2号、1号、3号小球。假如你现在需要拿出2号小球,那就必须先将3号小球拿出,再拿出1号小球,最后才能将2号小球拿出来。在刚才取小球的过程中,我们最先放进去的小球最后才能拿出来,而最后放进去的小球却可以最先拿
msdn中的描述如下:(?= 子表达式)(零宽度正预测先行断言。) 仅当子表达式在此位置的右侧匹配时才继续匹配。例如,w+(?=d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。(?(零宽度正回顾后发断言。) 仅当子表达式在此位置的左侧匹配时才继续匹配。例如,(?此构造不会回溯。msdn描述的比较清楚,如:w+(?=ing) 可以匹配以ing结尾的单词(匹配结果不包括ing),(
1.引入线索二叉树 二叉树的遍历实质上是对一个非线性结构实现线性化的过程,使每一个节点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。但在二叉链表存储结构中,只能找到一个节点的左、右孩子信息,而不能直接得到节点在任一遍历序列中的前驱和后继信息。这些信息只有在遍历的动态过程中才能得到,因此,引入线索二叉树来保存这些从动态过程中得到的信息。 2.建立线索二叉树 为了保
排序与我们日常生活中息息相关,比如,我们要从电话簿中找到某个联系人首先会按照姓氏排序、买火车票会按照出发时间或者时长排序、买东西会按照销量或者好评度排序、查找文件会按照修改时间排序等等。在计算机程序设计中,排序和查找也是最基本的算法,很多其他的算法都是以排序算法为基础,在一般的数据处理或分析中,通常第一步就是进行排序,比如说二分查找,首先要对数据进行排序。在Donald Knuth 的计算机程序设