数据结构与算法——编程练习——第七章 图

1.The Unique MST

题目

总时间限制: 
1000ms
 
内存限制: 
65536kB
描述
Given a connected undirected graph, tell if its minimum spanning tree is unique.
Definition 1 (Spanning Tree): Consider a connected, undirected graph G = (V, E). A spanning tree of G is a subgraph of G, say T = (V', E'), with the following properties:
1. V' = V.
2. T is connected and acyclic.
Definition 2 (Minimum Spanning Tree): Consider an edge-weighted, connected, undirected graph G = (V, E). The minimum spanning tree T = (V, E') of G is the spanning tree that has the smallest total cost. The total cost of T means the sum of the weights on all the edges in E'.
输入
The first line contains a single integer t (1 <= t <= 20), the number of test cases. Each case represents a graph. It begins with a line containing two integers n and m (1 <= n <= 100), the number of nodes and edges. Each of the following m lines contains a triple (xi, yi, wi), indicating that xi and yi are connected by an edge with weight = wi. For any two nodes, there is at most one edge connecting them.
输出
For each input, if the MST is unique, print the total cost of it, or otherwise print the string 'Not Unique!'.
样例输入
2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2
样例输出
3
Not Unique!
来源
    POJ Monthly--2004.06.27 srbga@POJ

我的代码

 1 # include <iostream>
 2 # include <algorithm>
 3 using namespace std;
 4 
 5 typedef struct{
 6     int x, y, w;
 7 } EDGE;
 8 
 9 bool cmp(EDGE e1, EDGE e2){
10     return e1.w < e2.w;
11 }
12 
13 EDGE * e;   //按权重增序存储所有边的信息
14 int * f;    //父节点表示法
15 int T, N, M,//输入参数
16     sum;    //最小生成树的边权和
17 
18 //带路径压缩的根节点搜索算法
19 int FindRoot(int i){
20     if(f[i] != i)
21         f[i] = FindRoot(f[i]);
22     return f[i];
23 }
24 
25 //判断最小生成树是否唯一
26 bool IsUnique(){
27     int cnt1 = 0;   //可选的加入最小生成树的边的数量
28     int cnt2 = 0;   //实际上加入最小生成树的边的数量
29     int i = 0, j = 0;
30     int fx, fy;
31     while(i < M){
32         j = i;
33         while(e[i].w == e[j].w && j < M){
34             fx = FindRoot(e[j].x);
35             fy = FindRoot(e[j].y);
36             if(fx != fy)
37                 cnt1++; //计数:边j可以加入最小生成树
38             j++;
39         }
40         j = i;
41         while(e[i].w == e[j].w && j < M){
42             fx = FindRoot(e[j].x);
43             fy = FindRoot(e[j].y);
44             if(fx != fy){
45                 cnt2++;         //计数:边j实际加入最小生成树
46                 f[fx] = fy;     //合并以fx和fy为根的树(等价类)
47                 sum += e[j].w;  //计算边权和
48             }
49             j++;
50         }
51         i = j;
52     }
53     return cnt1 == cnt2;
54 }
55 
56 //等价类森林初始化
57 void ForestIni(){
58     cin >> N >> M;
59     sum = 0;
60     e = new EDGE[M+1];
61     f = new int[N+1];
62     for(int i=0; i<M; ++i)
63         cin >> e[i].x >> e[i].y >> e[i].w;
64     sort(e, e+M, cmp);
65     for(int i=1; i<=N; ++i)
66         f[i] = i;
67 }
68 
69 //等价类森林删除
70 void ForestDel(){
71     delete [] e;
72     delete [] f;
73 }
74 
75 int main(){
76     cin >> T;
77     while(T--){
78         ForestIni();
79         if(IsUnique())
80             cout << sum << endl;
81         else
82             cout << "Not Unique!\n";
83         ForestDel();
84     }
85     return 0;
86 }
View Code

实现思路

思路1:MST的可选边多于实际MST的边数 <=> MST不唯一(我的代码采用这个思路)

思路2:构造MST,删除其中一条边,重新构造MST,构造成功 <=> MST不唯一(参见https://blog.csdn.net/libin56842/article/details/17339525)

MST的构造方法

1.Prim算法

任选起始顶点加入树,寻找一顶点在树中&&另一顶点不在树中的权重最小的边,扩展树;重复上述过程,直至所有顶点加入树。

2.Kruskal算法

为顶点划分等价类,初始状态下每个顶点一个等价类;寻找两顶点不在同一等价类中的权重最小的边,合并等价类;重复上述过程,直至所有节点合并为一个等价类。

2.兔子与星空

题目

总时间限制: 
1000ms
 
内存限制: 
10000kB
描述

很久很久以前,森林里住着一群兔子。兔子们无聊的时候就喜欢研究星座。如图所示,天空中已经有了n颗星星,其中有些星星有边相连。兔子们希望删除掉一些边,然后使得保留下的边仍能是n颗星星连通。他们希望计算,保留的边的权值之和最小是多少?

输入
第一行只包含一个表示星星个数的数n,n不大于26,并且这n个星星是由大写字母表里的前n个字母表示。接下来的n-1行是由字母表的前n-1个字母开头。最后一个星星表示的字母不用输入。对于每一行,以每个星星表示的字母开头,然后后面跟着一个数字,表示有多少条边可以从这个星星到后面字母表中的星星。如果k是大于0,表示该行后面会表示k条边的k个数据。每条边的数据是由表示连接到另一端星星的字母和该边的权值组成。权值是正整数的并且小于100。该行的所有数据字段分隔单一空白。该星星网络将始终连接所有的星星。该星星网络将永远不会超过75条边。没有任何一个星星会有超过15条的边连接到其他星星(之前或之后的字母)。在下面的示例输入,数据是与上面的图相一致的。
输出
输出是一个整数,表示最小的权值和
样例输入
9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
样例输出
216
提示
考虑看成最小生成树问题,注意输入表示。

我的代码

 1 # include <iostream>
 2 # include <algorithm>
 3 using namespace std;
 4 
 5 //边类
 6 typedef struct {
 7     int x, y, w;
 8 } EDGE;
 9 //比较边权值
10 bool cmp(EDGE e1, EDGE e2){
11     return e1.w < e2.w;
12 }
13 EDGE e[80]; //e[i]存储边i的信息
14 int f[30];  //f[i]存储节点i的父节点下标
15 int n, m;   //结点数量n,边的数量m
16 int sum = 0;//最小生成树的边权和
17 //带路径压缩的根节点搜索算法
18 int FindRoot(int i){
19     if(f[i] != i)
20         f[i] = FindRoot(f[i]);
21     return f[i];
22 }
23 //Kruskal方法找最小生成树
24 void Kruskal(){
25     int i = 0, fx, fy, cnt = 1;
26     //边i<m未检测 或 合并等价类操作只进行了cnt<n次
27     while(i < m && cnt < n){
28         fx = FindRoot(e[i].x);
29         fy = FindRoot(e[i].y);
30         //合并以fx和fy为根的等价类等价类
31         if(fx != fy){
32             f[fx] = fy;
33             sum += e[i].w;
34             ++cnt;  //记录合并等价类操作的次数
35         }
36         ++i;
37     }
38 }
39 //图的输入和初始化
40 void BuildGraph(){
41     int x, edge, w;
42     char xc, yc;
43     cin >> n;   //共有n个节点
44     //初始化f[],每个等价类中只有一个元素,元素的父节点为自身
45     for(x = 0; x < n; ++x)
46         f[x] = x;
47     //初始化e[],记录边的数量m
48     for(x = 0, m = 0; x < n-1; ++x){
49         //输入第x个节点的信息
50         cin >> xc >> edge;
51         for(int j = 0; j < edge; ++j, ++m){
52             //输入边的信息
53             cin >> yc >> w;
54             e[m].x = x;
55             e[m].y = yc-'A';
56             e[m].w = w;
57         }
58     }
59     sort(e, e+m, cmp);  //按边权值增序排列
60 }
61 
62 int main(){
63     BuildGraph();
64     Kruskal();
65     cout << sum << endl;
66     return 0;
67 }
View Code

 

3.舰队、海域出击!

题目

总时间限制: 

5000ms
 
单个测试点时间限制: 
2000ms
 
内存限制: 
262144kB
描述

作为一名海军提督,Pachi将指挥一支舰队向既定海域出击!
Pachi已经得到了海域的地图,地图上标识了一些既定目标和它们之间的一些单向航线。如果我们把既定目标看作点、航线看作边,那么海域就是一张有向图。不幸的是,Pachi是一个会迷路的提督QAQ,所以他在包含环(圈)的海域中必须小心谨慎,而在无环的海域中则可以大展身手。
受限于战时的消息传递方式,海域的地图只能以若干整数构成的数据的形式给出。作为舰队的通讯员,在出击之前,请你告诉提督海域中是否包含环。

例如下面这个海域就是无环的:

扫描二维码关注公众号,回复: 7943979 查看本文章

而下面这个海域则是有环的(C-E-G-D-C):

输入
每个测试点包含多组数据,每组数据代表一片海域,各组数据之间无关。
第一行是数据组数T。
每组数据的第一行两个整数N,M,表示海域中既定目标数、航线数。
接下来M行每行2个不相等的整数x,y,表示从既定目标x到y有一条单向航线(所有既定目标使用1~N的整数表示)。
描述中的图片仅供参考,其顶点标记方式与本题数据无关。
1<=N<=100000,1<=M<=500000,1<=T<=5
注意:输入的有向图不一定是连通的。
输出
输出包含T行。
对于每组数据,输出Yes表示海域有环,输出No表示无环。
样例输入
2
7 6
1 2
1 3
2 4
2 5
3 6
3 7
12 13
1 2
2 3
2 4
3 5
5 6
4 6
6 7
7 8
8 4
7 9
9 10
10 11
10 12
样例输出
No
Yes
提示
输入中的两张图就是描述中给出的示例图片。

我的代码

 1 # include <iostream>
 2 using namespace std;
 3 
 4 //有向边类
 5 typedef struct{
 6     int x, y;   //始点x,终点y
 7     bool v;     //是否被添加到DFS-tree中
 8 } EDGE;
 9 //节点类
10 typedef struct{
11     int f;      //记录DFS-tree中的父节点下标
12     bool v;     //是否被添加到DFS-tree中
13 } NODE;
14 EDGE * e;   //e[]记录有向边信息
15 NODE * n;   //f[]记录节点信息
16 int T, N, M;//数据组数T,节点数N,有向边数M
17 
18 //输入并建立图
19 void BiuldGraph(){
20     cin >> N >> M;
21     e = new EDGE[M + 1];
22     n = new NODE[N + 1];
23     //边信息e[]初始化
24     for(int i = 0; i < M; ++i){
25         cin >> e[i].x >> e[i].y;
26         e[i].v = 0;
27     }
28     //节点信息n[]初始化
29     for(int i = 1; i <= N; ++i){
30         n[i].f = i;
31         n[i].v = 0;
32     }
33 }
34 //清除图
35 void DeleGraph(){
36     delete [] e;
37     delete [] n;
38 }
39 //深度优先遍历图中所有节点
40 //s是当前搜索的起始节点
41 void DFS(int s){
42     if(n[s].v) return;  //已经访问过
43     n[s].v = 1;         //标记节点s为已访问过
44     //找节点i的未访问过的邻边j
45     for(int j = 0; j < M; ++j)
46         if(!e[j].v && e[j].x == s && !n[e[j].y].v){
47             e[j].v = 1;     //边j加入DFS-tree
48             n[e[j].y].f = s;//边j的终点y的父节点设置为节点s
49             DFS(e[j].y);    //递归地深度优先搜索节点y
50         }
51 }
52 //判断在DFS-tree中,节点x是否是节点y的子孙
53 bool IsDescend(int x, int y){
54     //节点x是根节点
55     if(n[x].f == x)
56         return false;
57     //节点x的父节点是节点y
58     if(n[x].f == y)
59         return true;
60     //判断节点x的父节点是否是节点y的子孙
61     return IsDescend(n[x].f, y);
62 }
63 
64 //寻找backward-edge,找到则说明有环
65 bool FindBackwardEdge(){
66     for(int j = 0; j < M; ++j)
67         //寻找图中未加入DFS-tree的边
68         if(!e[j].v)
69             //判断backward
70             if(IsDescend(e[j].x, e[j].y))
71                 return true;
72     //未找到backward-edge
73     return false;
74 }
75 
76 int main(){
77     cin >> T;
78     while(T--){
79         //建立图
80         BiuldGraph();
81         //深度优先遍历图,建立DFE-tree
82         for(int i = 1; i <= N; ++i)
83             DFS(i);
84         //寻找backward-edge,找到则说明有环
85         if(FindBackwardEdge())
86             cout << "Yes\n";
87         else
88             cout << "No\n";
89         //清除图
90         DeleGraph();
91     }
92     return 0;
93 }
View Code

实现思路

 背景知识:基于DFS的边的分类

  

  Forward Edges: 从DFS树的祖先到子孙。

  Backward Edges: 从DFS树的子孙到祖先。

  Cross Edges: DFS树上两个不是“祖先-子孙”关系的两个点之间的边。

 存在Backward Edges <=> 图中有环

猜你喜欢

转载自www.cnblogs.com/tanshiyin-20001111/p/11913655.html