上题目
Problem Description
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?Input 测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000
)和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说 3 3 1 2 1 2 2 1 这种输入也是合法的 当N为0时,输入结束,该用例不被处理。Output 对每个测试用例,在1行里输出最少还需要建设的道路数目。
Sample Input
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
Sample Output
1
0
2
998
分析
基础的联通问题
看两个城市是否联通在一起
这里我们运用一种并查集的思想
我们设立一个标志节点 如果两个节点都可以上诉到同一个标志节点 那么这两个节点是联通的
这里用函数说明
全局变量部分
int father[1050];//father[i]所储存的数是father[i]的父节点的坐标
int rank[1050]; // 表示这个数的在树中的深度
接下来是初始化的函数
我们先将每个数的父节点设置为他自己也就是每个数是独立的
将他的rank设置为0 代表深度为0; 函数
void init (int n)
{
for(int i = 0; i < n; i++)
{
father[i] = i;
rank[i] = 0;
}
}
接下来是建立并查集的过程
这里本质上是合并两个集合的过程
比如 1和2联通 我们实际上就是合并1和2的最终父节点
注意这里在合并的过程中实际考虑了高度的问题
优先合并到较高的那一列
这样不会增加并查集的深度
void unite (int x, int y)
{
x = find(x);
y = find(y);
if(x == y)
return;
if(rank[x] < rank[y])
{
father[x] = y;;
}
else
{
father[y] = x;
if(rank[x] == rank[y])
rank[x]++;
}
}
接下来就是查找的查找的过程
可以这样理解
因为father[i]存储的是i的父节点 所以我们可以一直向上遍历
直到出现 father[i] = i 的情况
也就是他的父节点是他自己
这时他就是最终的父节点
这里先上递推版的
int find(int x)
{
int far = x;
while (far != father[far])// 寻找最终父节点
far = father[far];
return far;
}
路径压缩问题
这里我们的做法是将全部的子节点合并到最终父节点下
int minn(int x)
{
int far = find(x);
int i = x;
while (father[i] != far)// 状态压缩 将各级子节点压缩到最终父节点下
{
int temp = father[i];
father[i] = far;
i = temp;
}
return far;
}
上代码+ 注释
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int father[1050];
int t[1050];
int find(int x)
{
int far = x;
while (far != father[far])// 寻找最终父节点
far = father[far];
int i = x;
while (father[i] != far)// 状态压缩 将各级子节点压缩到最终父节点下
{
int temp = father[i];
father[i] = far;
i = temp;
}
return far;
}
void mix(int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
{
father[fy] = fx;
}
}
int main(void)
{
int N, M, a, b, i, j, ans;
while (scanf("%d%d", &N, &M) && N)
{
for (i = 1; i <= N; i++) //初始化
father[i] = i;
for (i = 1; i <= M; i++) //吸收并整理数据
{
scanf("%d%d", &a, &b);
mix(a, b);
}
memset(t, 0, sizeof(t));
for (i = 1; i <= N; i++) //标记根结点
{
t[find(i)] = 1;
}
for (ans = 0, i = 1; i <= N; i++)
if (t[i])
ans++;
printf("%d\n", ans - 1);
}
return 0;
}