并查集简介:
并查集是一种用来合并不相交集合的算法,常用来检测一个图里面是否有环儿。
应用举栗🌰:
给出无向图的边,判断是否有环儿。
①设无向图的边为{0,1},{1,2},{1,3},{2,4},{3,4},{2,5},如下图所示。
②假设先选{0,1}这条边,设1是0的父节点。
③再选{1,2}这条边,设1是2的父节点。
④再选{3,4}这条边,设4是3的父节点。
⑤再选{1,3}这条边,但如果直接连接{1,3}(如下图所示)会形成特别长的路径,因此可以换一种思路:找到1和3的父节点然后进行合并。
⑥再找{2,4}这条边,发现2的父节点是4,4的父节点也是4,即有环儿。
代码如下:
#include<iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define N 6
void init(int parent[]){ //初始化parent数组为-1
for(int i=0;i<N;i++){
parent[i]=-1;
}
}
int find_root(int x,int parent[]){ //找父节点
int x_root = x;
while(parent[x_root] != -1){
x_root = parent[x_root];
}
return x_root;
}
int hebing(int x,int y,int parent[]){ //合并
int x_root = find_root(x,parent);
int y_root = find_root(y,parent);
if(x_root == y_root){ //合并失败
return 0;
}else{
parent[x_root] = y_root; //将x的父节点设置成y
return 1;
}
}
int main(){
int parent[N]={0};
int a[6][2]={
{0,1},{1,2},{1,3},
{2,4},{3,4},{2,5}
};
init(parent);
for(int i=0;i<6;i++){
int x = a[i][0]; //第i条边的第0个
int y = a[i][1]; //第i条边的第1个
if(hebing(x,y,parent) == 0){
printf("有环儿\n");
return 0;
}
}
printf("无环儿\n");
return 0;
}
应用变通🍌:
删掉{2,4}这条边,判断是否有环。
代码如下:
#include<iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define N 6
void init(int parent[]){ //初始化parent数组
for(int i=0;i<N;i++){
parent[i]=-1;
}
}
int find_root(int x,int parent[]){ //找父节点
int x_root = x;
while(parent[x_root] != -1){
x_root = parent[x_root];
}
return x_root;
}
int hebing(int x,int y,int parent[]){ //合并
int x_root = find_root(x,parent);
int y_root = find_root(y,parent);
if(x_root == y_root){
return 0; //合并失败
}else{
parent[x_root] = y_root; //将x的父节点设置成y
return 1;
}
}
int main(){
int parent[N]={0};
int a[5][2]={
{0,1},{1,2},{1,3},
{3,4},{2,5}
};
init(parent);
for(int i=0;i<5;i++){
int x = a[i][0]; //第i条边的第0个
int y = a[i][1]; //第i条边的第1个
if(hebing(x,y,parent) == 0){
printf("有环儿\n");
return 0;
}
}
printf("无环儿\n");
return 0;
}
应用优化📕:
路径压缩,设rank为树的深度,通过比较rank的大小来判定如何合并。
给出无向图的边{0,1},{1,2},{1,3},{2,4},{3,4},{2,5},判断是否有环儿。
代码如下:
#include<iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define N 6
void init(int parent[],int rank[]){ //初始化parent数组-1
for(int i=0;i<N;i++){
parent[i]=-1;
rank[i] = 0;
}
}
int find_root(int x,int parent[]){ //找父节点
int x_root = x;
while(parent[x_root] != -1){
x_root = parent[x_root];
}
return x_root;
}
int hebing(int x,int y,int parent[],int rank[]){ //rank用来记录树🌲的深度
int x_root = find_root(x,parent);
int y_root = find_root(y,parent);
if(x_root == y_root){
return 0; //合并失败
}else{
if(rank[x_root] > rank[y_root]){
parent[y_root] = x_root;
}else if(rank[x_root] < rank[y_root]){
parent[x_root] = y_root;
}else{
parent[x_root]=y_root;
rank[y_root++];
}
return 1;
}
}
int main(){
int parent[N]={0};
int rank[N]={0};
int a[6][2]={
{0,1},{1,2},{1,3},
{2,4},{3,4},{2,5}
};
init(parent,rank);
for(int i=0;i<6;i++){
int x = a[i][0]; //第i条边的第0个
int y = a[i][1]; //第i条边的第1个
if(hebing(x,y,parent,rank) == 0){
printf("有环儿\n");
return 0;
}
}
printf("无环儿\n");
return 0;
}
删掉{2,4}这条边,判断是否有环儿。
#include<iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define N 6
void init(int parent[],int rank[]){ //初始化parent数组
for(int i=0;i<N;i++){
parent[i]=-1;
rank[i] = 0;
}
}
int find_root(int x,int parent[]){ //找父节点
int x_root = x;
while(parent[x_root] != -1){
x_root = parent[x_root];
}
return x_root;
}
int hebing(int x,int y,int parent[],int rank[]){ //rank用来记录树🌲的深度
int x_root = find_root(x,parent);
int y_root = find_root(y,parent);
if(x_root == y_root){
return 0; //合并失败
}else{
if(rank[x_root] > rank[y_root]){
parent[y_root] = x_root;
}else if(rank[x_root] < rank[y_root]){
parent[x_root] = y_root;
}else{
parent[x_root]=y_root;
rank[y_root++];
}
return 1;
}
}
int main(){
int parent[N]={0};
int rank[N]={0};
int a[6][2]={
{0,1},{1,2},{1,3},
{3,4},{2,5}
};
init(parent,rank);
for(int i=0;i<5;i++){
int x = a[i][0]; //第i条边的第0个
int y = a[i][1]; //第i条边的第1个
if(hebing(x,y,parent,rank) == 0){
printf("有环儿\n");
return 0;
}
}
printf("无环儿\n");
return 0;
}