文章目录
- 题目描述
- 输入
- 输出
- 样例输入
- 样例输出
- 提示
- 算法思想
- 代码实现
- 寻找根节点
- 汇总连接情况
- 完整代码
- 关于flag的初值
题目描述
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
输出
对每个测试用例,在1行里输出最少还需要建设的道路数目。
样例输入
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
样例输出
1
0
2
998
提示
Huge input scanf is recommended.
算法思想
数组下标作为一个点,数组中存储的元素作为其连接的另一个点,全联通即:将所有下标对应的点归拢到一个点上(也就是除了根节点,大家对应的都不是自己啦),相当于大家都跟一个点连接的话,当然是全联通状态啦。
代码实现
寻找根节点
定义一个函数用来搜索给出结点的根节点,最终返回根节点的值(康康大家是不是来自同一地方嘛)
int find(int x){while(data[x]!=x){x=data[x];}return x;//返回根节点的值
}
当联通关系不多,树的深度不深的时候,用上一种方法无疑是很快的,但是如果联通关系错综复杂,树的深度很深,例如每层只有一个节点这种奇葩情况,遍历搜索根节点就会很费时间,这时候就要用到“路径压缩“这一方法,即将所查询节点的数组内容由”父节点“改为”祖宗节点“。
int find(int x){//寻找根节点int ans,tmp;ans = x;while(data[x]!=x){//x是根节点x=data[x];}while (ans != x) {//路径压缩tmp = data[ans];data[ans] = x;//数组内容直接储存根节点,不再存储父节点ans = tmp;//接下来压缩父节点}return x;//返回根节点的值
}
汇总连接情况
将所给的两个点的连接情况进行记录,根节点不一样的话说明是第一次告知两个节点联通,所以要把根节点改成一样的, 一样的话当然就是告知过了,不需要改变(大型认祖归宗活动,哦兄弟我们是一家的,来来来族谱上记一下/哦兄弟我们是一家的,来来来……桥豆麻袋!族谱上记过了……不用记了诶)
⬆(淦!你忘了我们很久以前见过面的!!!)
void add(int x, int y){if(find(x)!=find(y)){data[find(x)]=find(y);}
}
完整代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;int data[1003];int find(int x){//寻找根节点while(data[x]!=x){x=data[x];}return x;//返回根节点的值
}void add(int x, int y){if(find(x)!=find(y)){data[find(x)]=find(y);}
}int main(int argc, char const *argv[]) {int n,m;//n元素个数,m元素连接条数int x,y;//x,y是两个相连的元素int flag;//未联通的结点数while (cin >> n,n) {memset(data,0,sizeof (data));for (int i = 1; i <= n; i++) {data[i] = i;}for (cin >> m; m > 0; m--) {cin >> x;cin >> y;add(x,y);}flag = -1;for (int i = 1; i <= n; i++) {if(data[i]==i){flag++;//数组内容等于数组下标表示和自己联通}}cout << flag << endl;}return 0;
}
关于flag的初值
其实难以理解的地方可能是flag的初值为什么要设置成-1,flag存储的是数组内容等于数组下标的情况(自己连接自己的情况)有多少,每有一个就说明有一种连接种类,全联通状态只有一个这样的情况——即根节点,也就是除了根节点和自己连接,其他的节点都不和自己连接,所以要把根节点这个情况排除在外,即flag初值为-1。
这样一来,当全联通时,只有根节点的data[i]=i,flag++,flag的值就为0,最后输出flag的值也就是还需要修多少条路。
举个栗子可能更方便理解
当输入为
5 2
1 2
4 5
时
此时data数组的情况是(第四行):
连接种类有三种:根节点为2的1、2连接、根节点为3的自己和自己连接、以及根节点为5的4、5连接。
此时data[i]=i的情况会出现3次,flag的终值也就是-1自加三次,也就是2,表示还需要修两条路,举个例子,3 2,3 4这样修,那会出现什么情况呢?(为了便于理解拆分一下这个过程)
3 2的时候,3将2的根节点(也就是2本身)认作祖宗,3 4的时候,经过find函数查看族谱,诶大家祖宗不一样诶但我们是一家人,那就一个祖宗认另一个祖宗为爹好了,这样大家就在一起了,于是经过add函数修改族谱,3他爹2就认4他爹5当爹,1和3就成了5的乖孙孙。大家就是一家人了mua。(谁认谁当祖宗就看你怎么写了哦吼,data[find(x)]=find(y)是后面的数当前面的数的祖宗,data[find(y)]=find(x)就是前面的当祖宗咯)