并查集是一种用于处理集合的数据结构,主要支持两种操作:合并(Union)和查找(Find)。这种数据结构通常被用来解决等价关系问题,例如连接问题、连通性问题等。本文将介绍并查集的基本原理、实现方法和一些应用场景。
并查集的基本原理是维护一个森林,其中每个树都表示一个集合。每个节点都有一个指针指向它的父节点,树的根节点代表集合的代表元素。通过路径压缩和按秩合并两种优化方式,可以使得并查集操作的时间复杂度接近于常数。
并查集在计算机科学和算法领域有许多实际应用,其中一些常见的应用包括:
连通性问题: 并查集广泛应用于处理图的连通性问题,例如判断图中两个节点是否连通,或者求解图中的连通分量。
最小生成树: Kruskal 算法是一种常见的最小生成树算法,它使用并查集来判断两个节点是否在同一连通分量中,以避免形成环。
社交网络: 在社交网络中,可以使用并查集来管理用户之间的关系,例如判断两个用户是否属于同一社交圈。
最优路径查询: 在一些路径查询问题中,通过使用并
首先,我们需要定义并查集的基本数据结构,包括初始化、查找和合并等操作:
以下是一个简单的 C++ 实现并查集的例子。该实现包括了基本的初始化、查找、合并操作,以及路径压缩和按秩合并的优化。
#include
#include
using namespace std;
class UnionFind {
private:
vector<int> parent;
vector<int> rank;
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
//rank阵列用于优化合并操作
//1、如果树的rank不同,将排名小的树连接到排名大的树上,不影响排名的变化。
//2、如果树的rank相同,将排名小的树连接到排名大的树上,此时排名大的树的排
//名会增加1。
// 初始化,每个元素的父节点是自己,秩为0
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
// 查找根节点(代表元素)
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x]; //返回根节点的值
}
// 合并两个集合
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
// 按秩合并,将秩较小的集合连接到秩较大的集合上
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootX] = rootY;
rank[rootY]++; //更新rank
}
}
}
};
int main() {
int n = 10; // 元素个数
UnionFind uf(n);
// 合并集合
uf.unionSets(2, 4);
uf.unionSets(5, 7);
uf.unionSets(1, 3);
uf.unionSets(8, 9);
uf.unionSets(1, 2);
uf.unionSets(5, 6);
uf.unionSets(2, 3);
// 查找集合
if(uf.find(3) == uf.find(4)){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
system("pause");
if(uf.find(7) == uf.find(1)){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
system("pause");
if(uf.find(8) == uf.find(9)){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
return 0;
}
题目描述:有 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
1、先建立一个并查集
2、不同城市之间依次调用uf.find(x)函数
3、用一个ans来记录集合的个数(即省份的数量)
class Solution {
public:
class unionFind{
private:
vector<int> parent;
vector<int> rank;
public:
unionFind(int n){
parent.resize(n);
rank.resize(n,0);
for(int i = 0 ; i < n ;i++){
parent[i] = i;
}
}
// 查找根节点(代表元素)
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x]; //返回根节点的值
}
// 合并两个集合
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
// 按秩合并,将秩较小的集合连接到秩较大的集合上
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootX] = rootY;
rank[rootY]++; //更新rank
}
}
}
};
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size();
//建立并查集
unionFind uf(n);
for(int i = 0 ; i < n ;i++){
for(int j = i+1 ; j < n ;j++){
if(isConnected[i][j] == 1){
uf.unionSets(i,j);
}
}
}
int ans = 0;
for(int i = 0 ; i < n ;i++){
if(uf.find(i) == i){
ans++;
}
}
return ans;
}
};
树可以看成是一个连通且无环的无向图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,
且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi]
表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数
组 edges 中最后出现的那个。
示例 1:
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
示例 2:
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
1、在建立并查集的时候,先检查两个顶点是否已经在同一个集合中了。
2、如果两个顶点已经在同一个集合中,则表明已经形成了环,此时应该将这两个节点保存到结果数组中。
3、如果两个顶点没有在同一个集合中,则将其放入同一个集合之中。
class Solution {
public:
class unionFind{
private:
vector<int>parent;
vector<int>rank;
public:
unionFind(int n){
parent.resize(n);
rank.resize(n,0);
for(int i = 0 ; i < n ;i++){
parent[i] = i;
}
}
int find(int x){
if(parent[x] != x){
parent[x] = find(parent[x]);
}
return parent[x];
}
void unionSets(int x,int y){
int rootx = find(x);
int rooty = find(y);
if(rootx != rooty){
if(rank[rootx] < rank[rooty]){
parent[rootx] = rooty;
}else if(rank[rootx] > rank[rooty]){
parent[rooty] = rootx;
}else{
parent[rootx] = rooty;
rank[rooty]++;
}
}
}
};
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size()+1;
vector<int> ans;
//初始化并查集
unionFind uf(n);
for(int i = 0; i < n-1;i++){
//查找,如果x,y已经在一棵树里面了,再将其相连必定形成环
if(uf.find(edges[i][0]) == uf.find(edges[i][1])){
ans.push_back(edges[i][0]);
ans.push_back(edges[i][1]);
return ans;
}
//如果不能形成环,将x,y合并
uf.unionSets(edges[i][0],edges[i][1]);
}
return ans;
}
};