用并查集判断图的连通性

最近写代码遇到一个问题。如图一个3*3*3的Cube在这里插入图片描述

当两个立方体之间共享一个面,称为面邻居(R-6);当两个立方体仅共享一条边,称为边邻居(R-18);仅共享一个点,称为点邻居(R-26)。在细化骨架的算法中,判断一个体素是否为简单体素的时候要判断,删除体素前后欧拉示性数、连通分量数是否发生改变。这个连通分量数,把立方体当成图中的点,两个立方体之间点相邻代表他们之间有边,可以转换成图深搜、广搜去判断没有中心体素前后的连通分量数。在体网格模型里,从点邻接关系独立抽出中心体素的R-26有点麻烦,所以改变角度,从八分圆的邻接关系去判断。八分圆就是中心体素八个顶点为中心的2*2*2的Cube,当两个八分圆之间有重叠部分代表两个八分圆相邻。
因此问题还是回到图的连通判断。判断图的连通性可以深搜、广搜、并查集。
下面这道题目的场景非常相似。

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/friend-circles
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

第一版可以AC的代码。结果:52ms,14.1mb。

//Union_Find
class solver {
public:
	solver(int n) {
		count = n;
		root.resize(n);
		for (int i = 0; i < n; i++)
			root[i] = i;
	}
	//connect x and y
	void connect(int x, int y) {
		int xroot = find(x);
		int yroot = find(y);
		if (xroot == yroot) {
			return;
		}
		root[yroot] = xroot;
		count--;
	}
	//judge two point is neighbor
	bool isConnected(int x, int y) {
		int xroot = find(x);
		int yroot = find(y);
		return xroot == yroot;
	}
	//return count
	int count_num() {
		return count;
	}
private:
	int count;
	vector<int> root;
	//get root of x
	int find(int x) {
		while (root[x] != x)
			x = root[x];
		return x;
	}
};

//LeetCode内置的判题系统太复杂,上面写了一个求解器来包含并查集的函数,简单易懂
class Solution {
public:
	int findCircleNum(vector<vector<int>>& M) {
		int m = M.size();
		if (M.size() == 0)
			return 0;
		solver* s = new solver(m);
		for (int i = 0; i < M[0].size(); i++) {
			for (int j = i + 1; j < m; j++) {//对称矩阵
				//有邻接关系
				if (M[i][j] != 0)
					s->connect(i, j);
			}
		}
		return s->count_num();
	}
};

并查集中连接两个节点可以当成是树的构造,树最怕的就是退化成一串链表,上述连接过程不好的地方在于没有避免这点。优化的方式在于优化树的高度,让树尽量平衡。
可以考虑引入一个重量数组,记录树有多重,也就是每一个节点下有多少个节点。这样就可以在考虑是要root[yroot] = xroot;还是root[xroot] = yroot;的时候可以根据树的重量来选择。这一步叫做平衡性优化。O(N)->O(logN)。但是呢,加了这一步之后,运行时间反倒边久了,可能要在数据量大的时候才能体现出来。
最后一步优化也叫做路径压缩,在每一次查root的过程中,让每一个节点都能往上走,和最root的节点连接。
如下:

//Union_Find
class solver{
public:
    solver(int n){
        count = n;
        root.resize(n);
        size.resize(n);
        for(int i=0;i<n;i++){
            root[i]=i;
            size[i]=1;
        }
    }
    //connect x and y
    void connect(int x, int y){
        int xroot = find(x);
        int yroot = find(y);
        if(xroot == yroot){
            return;
        }
        //平衡性优化
        if(size[xroot] < size[yroot]){
            root[xroot] = yroot;
            size[yroot] += size[xroot];
        }else{
            root[yroot] = xroot;
            size[xroot] += size[yroot];
        }
        //root[yroot] = xroot;
        count--;
    }

    bool isConnected(int x, int y){
        int xroot = find(x);
        int yroot = find(y);
        return xroot == yroot;
    }
    //return count
    int count_num(){
        return count;
    }
private:
    int count;
    vector<int> root;
    vector<int> size;
    //get root of x
    int find(int x){
        while(root[x]!=x){
        	//路径压缩
           root[x] = root[root[x]];
           x = root[x];
        }
        return x;
    }
};


class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        int m=M.size();
        if(M.size() == 0)
            return 0;
        solver* s = new solver(m);
        for(int i=0;i<M[0].size();i++){
        	//这里和深搜的范围不一样,这里的意思是把点相邻当成有向图。
            for(int j = i+1; j < m; j++){//对称矩阵
                if(M[i][j]!=0)
                    s->connect(i, j);
                    
            }
        }
        return s->count_num();
    }
};

这下面是深搜解法。

class Solution {
public:
    void dfs(vector<vector<int>>& M, vector<int>& visited, int i){
        visited[i]=1;
        //注意这里j的遍历范围,矩阵每一行、每一列代表该下标的邻接关系,不能像并查集那样
        for(int j=0; j<M.size(); j++){
            if(M[i][j]==1 && visited[j]==0){
                dfs(M, visited, j);
            }
        }
    }
    int findCircleNum(vector<vector<int>>& M) {
        int m = M.size();
        vector<int> visited(m, 0);//1-visited
        int count = 0;
        for(int i=0;i<m;i++){
            visited[i]=0;
        }
        for(int i=0;i<m;i++){
            if(visited[i] == 0){
                dfs(M, visited, i);
                count++;
            }
        }
        return count;
    }
};

这下面是广搜的解法。

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        int m = M.size();
        vector<int> visited(m, 0);//1-visited
        int count = 0, temp;
        queue<int> bfs;
        for(int i=0;i<m;i++){
            if(visited[i]==0){
                count++;
                bfs.push(i);
                while(!bfs.empty()){
                    temp = bfs.front();
                    bfs.pop();
                    //访问过的出队
                    visited[temp]=1;
                    for(int j=0;j<m;j++)
                    	//没访问的入队
                        if(M[temp][j]==1 && visited[j]==0)
                            bfs.push(j);    
                }
            }
        }
        return count;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值