并查集主要用来解决:
(1)一群节点有分为几个集合问题。(朋友圈)
(2)判断两个点是不是在一个集合里。
(3)判断一个图里是不是有环。
并查集思路:
(参考bilibili的博主例子,为了查阅方便,记录下来。)
背景:我们想要判断以下图中是不是有环的问题。先构造出以下场景。
int connected[6][2] = {{0, 1}, {1, 2}, {1, 3},
{2, 3}, {3, 4}, {2, 5}};
其他场景的连线问题也是万变不离其宗。
可能会遇见不是整型,而是字符串的形式,比如:
string connected[N][2] = {{john, lisa},
{lisa, cale},
{lisa, jobe},
{laowang, xiaomei}
...};
遇见这种情况怎么办?
采用map或者unordered_map进行字符串和int整型对应
unordered_map<string, int> map;
int j = 0;
for(int ci : connected){
//一个字符对应一个数字编号
if(map.count(ci[0])==0) map.insert(make_pair(ci[0], j++));
if(map.count(ci[1])==0) map.insert(make_pair(ci[1], j++));
auto it1 = map.find(ci[0]);
auto it2 = map.find(ci[1]);
int x = it1->second;
int y = it2->Second;
...
}
上面的场景都可以 转化成下面的这个图的形式。
(1)最初0和1没有连线的时候,0和1处于两个部分(不同集合)。
(2)一旦连接我们就将他们连成树的形式(同一个集合)。
也即是读取{0, 1}
0和1连接,我们人为的给它一个指向,意思是1为根节点,0为子节点。
(3)现在继续将{1, 2}加入。
可以继续将1作为根节点。(也可以将2作为根节点)。
(4)就这样一直将所有的连线加入。
有了场景,下面就开始一步步具体运用并查集算法解决问题了,采用代码进行实现。
步骤一:初始化一个parent数组。初始parent数组里都为-1,也可以都为对应的下标值。(数组长度也就是节点个数)。
为什么采用parent数组?
parent[0] = 1
表示节点0的根节点为1,parent[2] = 1
表示节点1的根节点为2,如此你会发现,如果所有节点都连起来的话,那么parent数组最后只会有一个值还为-1,其他都不为-1了(其他都有依赖了)。这也可以看成有几个-1,最后就有几个集合。
//初始化数组
void infinite(int parent[], int n){
for(int i = 0; i < n; i++) parent[i] = -1;
}
int *parent = new int[N];
infinite(parent, N);
步骤二:寻找根节点。在加入节点的过程中,如果节点x和节点y本身就是在同一个根节点了,那么{x, y}的连线必定会形成一个环。
正如上图加入{0,1},{1,2}后,现在加入{0, 2}。因为节点0和节点2有公共的根节点, 因此再加入{0, 2}就会形成一个环。 当然这也能判断两个节点是不是在同一个集合里。
//循环判断,返回节点的根节点。
int find_root(int x, int parent[]){
int x_root = x;
while(parent[x_root] != -1) x_root = parent[x_root];
return x_root;
}
步骤三:将两个树(集合)拼接在一起。
//可以返回为bool或者int或者void
//返回为bool时候,返回false表示拼接失败,节点本身有共同的根节点,返回true表示拼接成功
bool union_gather(int x, int y, int parent[]){
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
//节点x和节点y有公共的根节点。
if(x_root == y_root) return false;
else {
parent[x_root] = y_root; //强行将y的根节点作为新的根节点
return true;
}
}
当然步骤三的一个问题就是强行将y节点的根节点作为新的根节点。这样有一定的缺点,就是如果是一个长链的时候,就是很长。
因此需要压缩路径,引入rank数组。rank数组初始化为0。表示两个树的高度,将高的树的根节点作为新的根节点。
步骤四:
最后就是在main函数中进行判断了
if(union_gather(x, y, parent)) cout<<没有环<<endl;
else cout<<"有环"<<endl;
现在依次总结开篇的三个问题:
(1)一群节点有分为几个集合问题。(朋友圈)
(2)判断两个点是不是在一个集合里。
(3)判断一个图里是不是有环。
多加一个,还要一个问题4
(4)统计某个节点所在集合的总数。
解决(1):在前面说过,只需要在所有连线连接完成后,统计parent数组里面的-1的个数,有几个-1,也就是有几个根节点,也就是有几个集合。
解决(2)(3):在union_gather的返回值为bool的时候,当x_root == y_root
的时候,就是这两个点在同一个集合,就是这个图有环。
解决(4):这个稍微复杂一点点,因为需要引入一个新的数组people,并初始化为1,长度也是节点个数。
void union_gather(int x, int y, int parent[], int people[]){
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
//节点x和节点y有公共的根节点。
if(x_root != y_root) {
parent[x_root] = y_root; //y_root为根节点
people[y_root] += people[x_root]; //加到y_root上
}
}
//最后直接找某个节点z的根节点在people数组中的数值就行了
int num = people[find_root(z, parent)];
以上
主要用来快速复习查看