并查集及 编程练习

并查集概念:

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。能够实现较快的合并和判断元素所在集合的操作,

应用很多,如其判定一个无向图是否有环,求无向图的连通分量个数等。比如典型应用:实现Kruskar算法求最小生成树。

并查集的主要操作 :

下面举例说明并查集的常用的三种操作:

1、make_set() 把每一个元素初始化为一个集合

初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。

2、find_set(x) 查找一个元素所在的集合

查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。

判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

合并两个集合,也是使一个集合的祖先成为另一个集合的祖先.(祖先节点即下标与其值相等 即 father[i]=i)
3、merge_set(x,y) 合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用find_set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

其中2中写并查集时涉及到的路径压缩,最好用递归,一方面代码的可读性非常好,另一方面,可以更直观的理解路径压缩时在回溯时完成的巧妙。

编程练习

下面以HDOJ上的题目为例编程实现。

hdoj  1232

http://acm.hdu.edu.cn/showproblem.php?pid=1232

畅通工程,实际上就是求这个图的连通分量的问题,如果有 n 个,那么最少还需要 n-1 根线将它们连接起来。求连通分量的话,

可以使用深度优先或是广度优先遍历的方法,遍历一遍,有多少个起点,就是有多少个连通分量;

也可以用并查集的思路,连通的节点放在在一起(即有同一个祖先节点),最后数一数有多少“集合”就 o好了。并查集 不用占很多

内存空间,理论上高效些。最后  连通量个数-1就是要修的最少的路了。

直接看代码吧:

#include <stdio.h>
#include <string.h>

#define N 1001

int father[N];//* father[x]表示x的父节点*/
int mark[N];//* mark[x]表示x是否在集合中*/

void make_set()//1初始化
{
    int i;
    for(i=1; i<N; ++i)
        father[i]=i;
    memset(mark, 0, sizeof(mark));
}
/* 查找x元素所在的集合,回溯时压缩路径*/
int find_set(int x)//2查找
{
    if(x != father[x])
        father[x] = find_set(father[x]);//回溯时的压缩路径
    return father[x];
}
void merge_set(int x, int y)//3合并
{
    int fx = find_set(x), fy=find_set(y);
    if(fx<fy)
        father[fy]=fx;
    else if(fy<fx)
        father[fx] = fy;
}
int main()
{
    freopen("in.txt", "r", stdin);
    int n,m;
    int x,y,i,cnt;//cnt 统计连通分量个数
    while(scanf("%d%d",&n, &m)!= EOF && n)
    {
        make_set();
        for(i=0; i<m; ++i)
        {
            scanf("%d%d",&x, &y);
            merge_set(x,y);
            mark[x]=mark[y]=1;
        }
        for(i=1; i<=n; ++i)//集合中所有的点
            mark[i]=1;
        cnt=0;
        for(i=1; i<N; ++i)//求图连通分量的个数
        {
            if(mark[i] && father[i] == i)
                ++cnt;
        }
        printf("%d\n", cnt-1);//最少边为 连通分量个数-1
    }
    return 0;
}



HDOJ 1272

http://acm.hdu.edu.cn/showproblem.php?pid=1272

根据题意,只要判断下面两个条件即可。
1. 判断是否成环(回路);
2. 判断是否只有一棵树(只有一个连通图);

这个题目 利用并查集可以容易解决,要是利用图论的方法,那就麻烦很多了。


下面是实现代码:

#include<stdio.h>
#include <string.h>
#define M 100001

int father[M];/* father[x]表示x的父节点*/
int vis[M]; // vis[x] 表示节点x是否在集合中
void make_set()//1初始化
{
    memset(vis, 0, sizeof(vis));
    int i;
    for(i=1; i<M; ++i)//初始化
        father[i] = i;
}

int find_set(int x)//2查找
{
    if(x != father[x])
        father[x]=find_set(father[x]);//回溯时的压缩路径
    return father[x];
}

int merge_set(int x, int y)//3合并
{
    int fx = find_set(x), fy = find_set(y);
    if(fx<fy)
        father[fy] = fx;//更新根点值
    else if(fy<fx)
        father[fx] = fy;
    else//出现回路
        return 1;
    return 0;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    int x, y;
    int i,flag,cnt;//flag 标志是否出现环,cnt统计连通分支数
    while(scanf("%d%d", &x, &y)!=EOF)
    {
        if( x==-1 && y==-1)
            break;
        make_set();
        flag = 1;
        while(x!= 0 && y !=0)
        {
            if(merge_set(x, y))//判断是否出现环
                flag = 0;
            vis[x]=vis[y]=1;
            scanf("%d%d", &x, &y);
        }
        cnt = 0;
        for(i=1; flag && i<M; ++i)
        {
            if(vis[i] && father[i] == i)
            {
                if(++cnt >1)//多个连通分支
                {
                    flag = 0;
                    break;
                }
            }
        }
        if(flag)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}




上面的int find_set(int x) 查找父节点采用的是一采用递归的方式压缩路径, 但是,递归压缩路径可能会造成溢出栈,或者对于求带环路的(如欧拉回路易出问题),下面我们说一下非递归方式进行的路径压缩:

int find_s(int x)
{
    int k, j, r;
    r = x;
    while(r != father[r])     //查找跟节点
        r = father[r];      //找到跟节点,用r记录下
    k = x;
    while(k != r)             //非递归路径压缩操作
    {
        j = father[k];         //用j暂存parent[k]的父节点
        father[k] = r;        //parent[x]指向跟节点
        k = j;                    //k移到父节点
    }
    return r;         //返回根节点的值
}

此外并查集的题目 经典的还有并查集,已有牛人分析,参见代码参见2

参考资料:

http://zh.wikipedia.org/zh-cn/%E5%B9%B6%E6%9F%A5%E9%9B%86

《算法导论》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值