经典算法之并查集

一、什么是并查集

       并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

       其特点是看似并不复杂,但数据量极大。若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高。

       并查集是一种树型的数据结构,但不是二叉树。用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。

       当然,简单理解也可以认为并查集是一种用来管理元素分组情况的数据结构。

       两种常见的并查集使用场景:

  • 查询元素a和元素b是否属于同一组
  • 合并元素a和元素b所在的组

二、如何使用并查集

       使用并查集主要步骤是:查找合并。当然,在查找过程中,我们通常使用路径压缩来对算法进行优化。

1. 查找

       通常我们用一个find函数来表示查找的过程。查找的目的是判断两组元素是否属于同一个集合。而判断的方法就是去依次查询它们的父结点,然后再去查找父结点的父结点,直到查找到根结点,再判断是否属于同一个根结点即可。

        当然这样的查找过程有一个问题,就是效率比较低下,因为我们每次都需要依次查找父结点到根。如果对于每个节点,一旦向上查找到了根节点,就把这个结点作为根结点的直接子结点。这就是路径压缩

        


int pre[1010]; //存放第i个元素的父结点

int unionsearch(int root)
{
	int son, tmp;
	son = root;
	while(root != pre[root]) //查找根结点
		root = pre[root];
	while(son != root) //路径压缩
	{
		tmp = pre[son];
		pre[son] = root;
		son = tmp;
	}
	return root; //返回根结点
}

2. 合并

       通常我们用一个join函数来表示查找的过程。合并的目的是将判断属于同一个集合的两组元素合并成一组元素。也就是从一个组的根向另一个组的根连边,这样两棵树就变成了一棵树。

void join(int root1, int root2) 
{
	int x, y;
	x = unionsearch(root1);
	y = unionsearch(root2);
	if(x != y)   //如果不连通,就把它们所在的连通分支合并
		pre[x] = y;
}

三、并查集的经典问题

图的连通性问题

       某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路? 

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int pre[1010]; 
 
int unionsearch(int root)
{
	int son, tmp;
	son = root;
	while(root != pre[root]) 
		root = pre[root];
	while(son != root)
	{
		tmp = pre[son];
		pre[son] = root;
		son = tmp;
	}
	return root;
}
 
int main()
{
	int num, road, total, i, start, end, root1, root2;
	while(scanf("%d%d", &num, &road) && num)
	{
		total = num - 1; 
		for(i = 1; i <= num; ++i)
			pre[i] = i;
		while(road--)
		{
			scanf("%d%d", &start, &end); 
			root1 = unionsearch(start);
			root2 = unionsearch(end);
			if(root1 != root2) 
			{
				pre[root1] = root2;
				total--;
			}
		}
		printf("%d\n", total);
	}
	return 0;
}
发布了35 篇原创文章 · 获赞 37 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/103998614