并查集(Disjoint Set)也叫不相交集合

概念描述: 将编号分别为1.......N的N个对象划分为不相交集合, 在每个集合中,选择其中某一个元素代表所在集合。

常见的两种操作:

1. 合并两个集合

2. 查找某元素属于哪个集合

 实现方法(1):

用编号最小的元素标记所在集合;   

定义一个数组Set[ 1....N ], 其中Set[i]表示元素i所在的集合, 用下标1...N表示N个元素

构建数组:

 当想知道下标3属于哪个集合, 返回 Set[3]=1  说明下标3这个元素属于1集合。

合并两个集合, a, b分别为两个集合标签, 如{1,3,7}集合的标签就是1, {2,5,9,10}集合的标签是2

需要遍历Set数组中的所有元素,更新需要修改的值(标签)

Mergel(a, b)
{
    i = min(a, b)
    j = max(a, b)
    for(k=1; k<=N; k++)
    {
        if(Set[k] == j)
        {
            Set[k] = i;
        }
    }
}

查找x所属的集合
findl(Set[],x)
{
    return Set[x];
}

合并操作会消耗大量资源,因为必须搜索全部元素, 可以改进算法,线性结构更改成树结构。

实现方法(2)  Set[i]数组中保存的值是 索引 i 对应的父节点, 如4的父节点为2,  因为Set[4]=2。

每个集合用一棵" 有根数 "表示, 定义数组 Set[ 1........n]

Set[i] = i  则 i 表示本集合, 并不是集合对应的根  (如Set[2] = 2 , 说明2是集合对应的根)

Set[i] = j , 若 j 不等于 i, 则 j 是 i 的父节点(如节点7, Set[7] = 4, 说明4是7的父节点, Set[4]=2, 说明2是4的父节点, Set[2]=2, 说明是根节点)

当每个人都单独为一个集合,最坏情况需要查N次, 复杂度为O(N)

树形结构的查找和合并操作:

find2(Set[], x)
{
    r = x;
    while(Set[r] != r)
    {
        r = Set[r];
    }
    return r;
}

两集合的合并:(也就是将两个子树合并成一个树,a,b分别为两个树的根)
merge2(a, b)
{
    Set[a] = b;
}

为了避免O(N)这种情况, 应该将深度小的树合并到深度大的树(也就是深度大的树做根节点)

应该判断树的高度再合并。

merge3(a, b)
{
    if(height(a) == height(b))
    {
        height(a) = height(a) + 1;
        Set[b] = a;
    }
   if(height(a) < height(b))
    {
        Set[a] = b;
    }
    else
    {
        Set[b] = a;
    }
}
当有多组数据输入时候:
while(scanf("%d", &n), n)
{
    scanf("%d", &m);                
    for(; m>0; m--)                
        scanf("%d %d", &x, &y)
}

n表示n个城市, m表示m个街道,  接下来m行输入x->y有一条街道,
当所有街道输入完毕,就会退出内层循环,重新输入n, 当输入n=0时候,退出外层循环

问题描述:

现在有n个城市, m个街道, 城市x和y之间街道,判断为了所有城市都连通,至少还需要新建几条街道。 

输入数据:

5,3         ( 5表示有5个城市, 3表示一共有三条街道)

1,2         (表示x=1的城市 和 y=2的城市有一条街道)

2,4

2,5

解题思路:

首先初始化一个数组, 数组中每个元素都是一个单独集合。

开始输入x和y城市, 每次输入x和y城市,都判断x的根节点, y的根节点,如果是同一个节点,说明x和y在同一个集合,不用合并集合, 最终,把有通道连接的城市划分为一个集合, 集合中,每个城市之间是连通的,  会有多个合并成多个集合, 问题是为了连通者多个集合,还要建设多少条街道。

现在已经合并成多个集合,最后遍历数组,找有几个根节点,当 Set[i] == i,  count++。当查到一个根节点, 那就计数器加1, 最终输出,T个集合会有T-1条通道

#include<stdio.h>
int bin[1002];

int find(int x)
{
	int r = x;
	while(bin[r] != r)
	{
		r = bin[r];
	}
	return r;
}

void merge(int x, int y)
{
	int fx,fy;
	fx = find(x);
	fy = find(y);
	if(fx != fy)
	{
		bin[fx] = fy; 
	}
}

int main()
{
	int n,m,i,x,y,count;
	while(scanf("%d",&n),n)
	{
		for(i=1; i<=n; i++)
		{
			bin[i] = i;
		}
		
		for(scanf("%d", &m); m>0; m--)
		{
			scanf("%d %d", &x, &y);
			merge(x, y);
		}
		
		for(count=-1,i=1; i<=n; i++)
		{
			if(bin[i] == i )
			{
				count++;
			}
		}
		
		printf("%d\n",count);
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值