★并查集: 理解、实现和应用

引言

并查集是一种用于处理集合的数据结构,主要支持两种操作:合并(Union)和查找(Find)。这种数据结构通常被用来解决等价关系问题,例如连接问题、连通性问题等。本文将介绍并查集的基本原理、实现方法和一些应用场景。

基本原理

并查集的基本原理是维护一个森林,其中每个树都表示一个集合。每个节点都有一个指针指向它的父节点,树的根节点代表集合的代表元素。通过路径压缩和按秩合并两种优化方式,可以使得并查集操作的时间复杂度接近于常数。

操作

  1. 初始化:将每个元素初始化为单独的集合,每个集合的代表元素就是自己;
  2. 查找(Find):*查找某个元素所在的集合,通常是找到树的根节点,即代表元素。
  3. 合并(Union):** 将两个集合合并为一个集合,通常是将其中一个树的根节点连接到另一个树的根节点上。 初始化:将每个元素初始化为单独的集合,每个集合的代表元素就是自己;

并查集的应用

并查集在计算机科学和算法领域有许多实际应用,其中一些常见的应用包括:

  1. 连通性问题: 并查集广泛应用于处理图的连通性问题,例如判断图中两个节点是否连通,或者求解图中的连通分量。

  2. 最小生成树: Kruskal 算法是一种常见的最小生成树算法,它使用并查集来判断两个节点是否在同一连通分量中,以避免形成环。

  3. 社交网络: 在社交网络中,可以使用并查集来管理用户之间的关系,例如判断两个用户是否属于同一社交圈。

  4. 最优路径查询: 在一些路径查询问题中,通过使用并

数组实现

基本结构

首先,我们需要定义并查集的基本数据结构,包括初始化、查找和合并等操作:
以下是一个简单的 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;
}

力扣实例:

547. 省份数量

题目描述:有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:
★并查集: 理解、实现和应用_第1张图片

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
示例 2:

★并查集: 理解、实现和应用_第2张图片

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

解题思路:

1、先建立一个并查集
2、不同城市之间依次调用uf.find(x)函数
3、用一个ans来记录集合的个数(即省份的数量)

c++代码

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;
    }
};

684. 冗余连接

题目描述:

树可以看成是一个连通且无环的无向图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,
且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 
表示图中在 ai 和 bi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数
组 edges 中最后出现的那个。

示例 1:

★并查集: 理解、实现和应用_第3张图片

输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
示例 2:

★并查集: 理解、实现和应用_第4张图片

输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

思路

1、在建立并查集的时候,先检查两个顶点是否已经在同一个集合中了。
2、如果两个顶点已经在同一个集合中,则表明已经形成了环,此时应该将这两个节点保存到结果数组中。
3、如果两个顶点没有在同一个集合中,则将其放入同一个集合之中。

c++代码

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;
    }
};

你可能感兴趣的:(高级数据结构,c++,算法,数据结构)