[数据结构与算法]通俗易懂入门并查集

并查集,顾名思义,具有将两个或以上的集合合并和查找的作用。所以讨论这个数据结构即讨论两个函数,一个是查找函数find(),另一个是合并函数join()。
为了便于理解,我们从题目入手:    hdoj畅通工程

leetcode朋友圈

用leetcode的题目做例子,简单的说就是,假设1和2是朋友,2和3是朋友,4和5是朋友,那1,2,3可以组成一个朋友圈,4和5可以组成另一个朋友圈,所以一共有两个朋友圈。题目给出N个人之间的关系,问到底有几个朋友圈。

解决题目前,我们先来看个故事:宠物小精灵里面有熔岩队和海洋队,两队互为彼此的劲敌,双方队员们要是遇上难免会干上一架,试想这样的一个场景,假设你是熔岩队的一名队员,一天你在路上遇到了一个人,但你不确定对方到底是自己队的还是海洋队。为了弄清对方的身份,你会问对方:你的队长是谁。如果对方回答:我的队长是赤炎松(熔岩队的队长)。那你就会意识到,哦,原来跟我是一样的队长,那大家都是熔岩队的,于是两人就手牵手和和睦睦走在一起了。。。但要是对方回答:我的队长是水梧桐(海洋队的队长)。嗯?居然跟我不是一个队长,那我不能怂,来干吧。。

从上面我们引出一个数组 p[i] = j。i代表某个人,j代表i这个人的队长,套进上面的场景就是p[我] = 赤炎松。所以想要知道某某人属于哪一个队的,就等于求p[某某人] 等于什么,然后就可以知道所属队伍。但是有一个临界点:p[我] = 赤炎松 这个很容易理解,即我的队长是赤炎松,但是p[赤炎松]又等于什么呢?难道赤炎松上面还有队长吗?当然是没有了,赤炎松已经是顶端的了,于是对于顶端的情况,我们另p[赤炎松] = 赤炎松,即p[i] = i(i已经是顶端的情况),对应的,p[水梧桐] = 水梧桐。

综上,我们不难写出一个find函数,用来确定我对上的是谁。

int find(int x)  //查找x最顶端的上司是谁
{
	int r = x;
	while (p[r] != r)
		r = p[r];
	return r;
}

可能有些小伙伴会疑问:为什么会有个while循环,又回到刚才的场景,假如熔岩队存在分级制度,即:队长->组长->队员。

而“我”是属于队员这个级别,又假如我不知道自己的队长(即最顶端的那个人)是谁,但是我肯定知道比自己高一级的组长是谁,于是我可以通过问组长:组长组长,我们的队长是谁啊?组长就会跟我说他对上的那个人是谁,而那个人就是我们的队长了,这就是间接知道自己的队长是谁。现在讨论的是三个等级的情况,可能还会存在n个等级的情况,但是方法类似,通过一步步向上问,直到问到不能再问,那个人就是队长了。于是我们可以通过找到队长是谁的方式来确定两个人到底是不是一个队伍的。

突然有一天,熔岩队和海洋队的队长决定以后要和睦相处不打架了,于是可以令p[赤炎松] = 水梧桐,也就是另水梧桐成为赤炎松的队长,如此一来两队的队长都是水梧桐了,当然也可以令p[水梧桐] = 赤炎松。于是可以写出一个合并的函数

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

到此,我们已经把两个需要的函数都写完了,但是对于find函数我们还可以进行改进,如果存在n级的情况,我们每一次查找自己的队长是谁的时候都要一步一步往上问,也就是这样的链式:我->组长->队长。 当n越大,那查找的效率越低,于是我们把这个链式改成这样:我->队长<-组长,也就是我们每一个人只需要一步便可查找到队长是谁,也就是压缩路径。对find函数的修改如下

int find(int x)
{
	int r = x;
	while (p[r] != r)
		r = p[r];   //找到r是最顶端的那一个
	int i = x, j;
	while (i != r)      //通过循环将每一个人都与最顶端的那个人连接起来
	{
		j = p[i];
		p[i] = r;
		i = j;
	}
	return r;
}

回到朋友圈的问题,我们只需一开始令每个人的朋友=自己本身,然后根据题意,谁跟谁是朋友,就谁跟谁调用join()函数连接在一起,最后利用find函数查看并统计每个人顶端的那个人有几个不同的人,便是几个朋友圈。可以利用set去重也可以打表去重。

附上AC代码


class Solution {
public:
    int find(vector<int>& v,int x)
    {
        int r = x;
        while(v[r] != r)
            r = v[r];
        int i = x,j;
        while(i != r)
        {
            j = v[i];
            v[i] = r;
            i = j;
        }
        return r;
    }
    void join(vector<int>& v,int x,int y)
    {
        int fx = find(v,x),fy = find(v,y);
        if(fx!=fy)
            v[fx] = fy;
    }
    int findCircleNum(vector<vector<int>>& M) {
        vector<int> v(M.size());
        for(int i = 0;i<v.size();i++)
            v[i] = i;  //一开始每个人都是自己朋友
        for(int i = 0;i<M.size();i++)
        {
            for(int j = 0;j<M[i].size();j++)
            {
                if(i!=j&&M[i][j] == 1)
                {
                    join(v,i,j);
                }
            }
        }
        set<int> s;//利用set去重
        for(int i = 0;i<v.size();i++)
            s.insert(find(v,i));
        return s.size();
    }
};

hdoj那道题也是类似的方法解决,最后要修的道路数=不同的城市圈-1。在这就不贴代码了,自己尝试用并查集做一遍吧。

猜你喜欢

转载自blog.csdn.net/Uupton/article/details/82669984