在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x)Find(x) 返回 xx 所属集合的代表,而 Union 使用两个集合的代表作为参数。
目录
一、并查集介绍
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
二、并查集案例
1. 无向环连接
给出一个二维数组,如vec[6][2]={{0,1},{1,2},{1,3},{2,4},{3,5},{4,5}},判断其中是否存在连通环(由多个节点组成的连通区域,其中每个节点都经过n步后返回自身节点)。
如:
图1 图2
上图1中{1,2,3,4,5}组成一个环,即存在联通环;而图2中无连通区域。
下面给出该代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
private:
/* 寻找每一个节点的父节点函数*/
int find_root(vector<int>& parent, int x){
int x_root = x;
/*如果每个节点指向本身,则自身为父节点;如果自己指向另一个节点,则父节点为其他节点,依次迭代,找到最高的父节点*/
while(parent[x_root] != x_root){
x_root = parent[x_root];
}
/* 找到父节点后,返回父节点的标号*/
return x_root;
}
/* 联合函数,如果两个节点x和y的父节点不相同,则可以将两个父节点联合起来;如果父节点相同,则说明两个连点在一个连通域中,无需改变*/
/* 输出:0-表示两个节点处于一个连通域中,无需进行联合;1-两个节点处于不同的连通域中,并将父节点进行联合*/
int union_join(vector<int>& parent, int x, int y,vector<int>& rank){
/* 先找到x y节点的父节点,然后判断两个节点是否在一个连通域中*/
int x_root = find_root(parent,x);
int y_root = find_root(parent,y);
cout << x_root << " " << y_root << endl;
/* 如果两个节点不在一个连通域中,则进行联合,联合方向随意(但是可能存在一个最长路径问题,后面会解决)*/
if(x_root != y_root){
if(rank[x_root] > rank[y_root]){
parent[y_root] = x_root;
}else{
parent[x_root] = y_root;
}
return 1;
}else{
return 0;
}
}
public:
// 寻找联通区域
int findCircleNum(vector<vector<int> >& vec){
int n = vec.size();
cout << n << endl;
/* 创建父节点目录,此时所有节点的父节点都为自己,指向-1*/
vector<int> parent(6);
/* rank表示每个节点的高度,为了使路径最短,应该将父节点表示为rank最大的元素*/
vector<int> rank(6);
// 此时需要将每个节点指向父节点(-1),n表示节点的数量
for(int i=0;i<6;i++){
parent[i] = i;
rank[i] = 0;
cout << parent[i] << " ";
}
cout << endl;
for(int i=0;i<n;i++){
if ( union_join(parent,vec[i][0],vec[i][1], rank) == 0 ) return 1;
}
return 0;
}
};
int main(){
vector<vector<int> > parent;
parent.push_back(vector<int>());
parent[0].push_back(0);
parent[0].push_back(1);
parent.push_back(vector<int>());
parent[1].push_back(1);
parent[1].push_back(2);
parent.push_back(vector<int>());
parent[2].push_back(1);
parent[2].push_back(3);
parent.push_back(vector<int>());
parent[3].push_back(2);
parent[3].push_back(5);
parent.push_back(vector<int>());
parent[4].push_back(3);
parent[4].push_back(4);
parent.push_back(vector<int>());
parent[5].push_back(4);
parent[5].push_back(5);
Solution S;
int x = S.findCircleNum(parent);
if(x) cout << "Circle detected!" << endl;
else cout << "Circle not detected!" << endl;
}
2. Leetcode案例-省份连接案例
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
示例 2:
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-provinces
class Solution {
public:
int Find(vector<int>& parent, int index) {
while(parent[index] != index){
index = parent[index];
}
return parent[index];
}
void Union(vector<int>& parent, int index1, int index2) {
parent[Find(parent, index1)] = Find(parent, index2);
}
int findCircleNum(vector<vector<int>>& isConnected) {
int provinces = isConnected.size();
vector<int> parent(provinces);
for (int i = 0; i < provinces; i++) {
parent[i] = i;
}
for (int i = 0; i < provinces; i++) {
for (int j = i + 1; j < provinces; j++) {
if (isConnected[i][j] == 1) {
Union(parent, i, j);
}
}
}
int circles = 0;
for (int i = 0; i < provinces; i++) {
if (parent[i] == i) {
circles++;
}
}
return circles;
}
};