数据结构——并查集Union Find Sets

1.问题分析

若某个家族人员过于庞大,要判断两个人是否是亲戚,确实很不容易。给出某个亲戚关系图,现在任意给出两个人,判断其是否具有亲戚关系。规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x和y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也是x的亲戚。

那么如何很快判断两个人是否是亲戚? 

2.算法设计 

 并查集是一种树形的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。主要有下面三种操作:

(1)初始化

把每个点所在集合初始化为其自身

(2)查找

查找两个元素所在的集合,即就是找祖宗。注意:查找时,采用递归的方法找其祖宗,祖宗集合号等于自己时即停止。在回归时,把当前节点到祖宗路径上的所有结点统一为祖宗的集合号。

(3)合并

如果两个元素的集合号不同,将两个元素合并为一个集合。注意:合并时只需要把一个元素的祖宗集合号,改成另一个元素的祖宗集合号。

3.详细图解 

假设现在有7个人,通过输入亲戚关系图,判断两个人是否有亲戚关系。

(1)初始化

把每个人集合号初始化为其自身编号。如图所示:

(2)输入亲戚关系2和7。

(3)查找。查找2所在的集合号为2,7所在的集合号为7

(4)合并。两个元素集合号不同,将两个元素合并为一个集合。在此约定将小的集合号赋值给大的集合号,因此修改father[7]=2。

(5)输入亲戚关系4和5。

(6)查找。查找4所在的集合号4,5所在的集合号5。

(7)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[5]=4。

(8)输入亲戚关系3和7。

(9)查找。查找3所在的集合号3,7所在的集合号2。

(10)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[3]=2。

(11)输入亲戚关系4和7。

(12)查找。查找4所在的集合号为4,7所在的集合号是2。

(13)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[4]=2。集合号为4的结点有两个,在此只需要修改这两个节点中的祖宗即可。并不需要把集合号4的所有节点都遍历一遍。

(14)输入亲戚关系3和4。

(15)查找。3所在的集合号是2,4所在的集合好是2.

(16)合并。两个元素集合号一样,什么都不做。

(17)输入亲戚关系5和7。

(18) 查找。查找5的集合号时,因为5的集合号不是5,所以,找父亲的集合号是4,4的父亲集合号是2,2的父亲集合号是2,此时停止。在查找返回时,把当前结点到祖宗路径上的所有结点集合统一为祖宗的集合号。

(19)合并。集合号一样,什么都不做。

(20)输入亲戚关系5和6。

(21)查找。查找5所在的集合号是2,6所在的集合号是6。

(22)合并。两个元素集合号不同,将两个元素合并为一个集合。修改father[6]=2。

(23)输入亲戚关系2和3。

(24)查找。查找2所在的集合号是2,3所在的集合号是2。

(25)合并。集合号一样,什么都不做。

(26)输入亲戚关系1和2。

(27)查找。1所在集合号是1,2所在集合号是2。将两个元素合并为一个集合。father[2]=1;

4.实际问题分析

 快过年了,犯罪分子们也开始为年终奖“奋斗”了。由于强盗人数过于庞大,作案频繁,警方想查清楚到底有几个犯罪团伙实在是太不容易了,不过警方还是搜集到了一些线索。需要咱们帮忙分析一下:

现在有十个强盗:

1号强盗与2号强盗是同伙。

3号强盗与4号强盗是同伙。

5号强盗与2号强盗是同伙。

4号强盗与6号强盗是同伙。

2号强盗与6号强盗是同伙。

8号强盗与7号强盗是同伙。

9号强盗与7号强盗是同伙。

1号强盗与6号强盗是同伙。

2号强盗与4号强盗是同伙。

有一点需要注意:强盗同伙的同伙也是同伙。你能帮助警方查出有多少个独立的犯罪团伙吗?

5.源代码 

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int N=1111;
int father[N]={0};
int n;
int m;
int k;
int sum=0;

void initialize() //初始化函数,将father数组每个元素的下标等于自身的数字
{
    for (int i=1;i<=n;i++)
    {
        father[i]=i;
    }
}

int getfather(int v) //找祖宗递归函数
{
    if(father[v]==v) //如果编号等于祖宗的编号(或是自己的)算法结束
    {
        return v; //返回祖宗编号
    }
    else
    {
        //在返回时,把路上遇到的人的编号改为最后找到祖宗的编号
        //这是最最最祖宗的编号。
        //这样可以提高找到最最最最后的祖宗编号的速度。
        father[v]=getfather(father[v]); //否则递归查找祖宗编号
        return father[v]; //最终返回祖宗编号。
                                    //返回father[v]是可以用于递归查找。
    }
}

void Merge(int v,int u)
{
    int t1;
    int t2;
    t1=getfather(v); //第一个结点的编号
    t2=getfather(u); //第二个结点的编号
    if(t1!=t2) //如果编号不相等
    {
        father[t2]=t1; //将较小的编号赋予较大的编号,靠左原则。
    }
}

int main()
{
    int x;
    int y;
    int i;
    cout << "请输入盗贼的人数和目前知道的情报的条数" << endl;
    cin >> n>> m;
    initialize();
    cout << "请输入情报:谁和谁是同伙" << endl;
    for (i=1;i<=m;i++)
    {
        cin >> x>> y; //输入关系
        Merge(x,y); //将最终编号一样的结点合并。
    }
    for (i=1;i<=n;i++)
    {
        if(father[i]==i)
        {
            sum++;
        }
    }
    cout <<"犯罪团伙的个数有:" << endl;
    cout << sum;
    return 0;
}

6,测试结果 

 

发布了57 篇原创文章 · 获赞 9 · 访问量 3627

猜你喜欢

转载自blog.csdn.net/Jayphone17/article/details/102636871