2021.11.21 力扣-省份数量-并查集练习

本文通过解决LeetCode上的“省份数量”问题,介绍了并查集数据结构的应用。具体讲解了如何利用并查集来确定图中连通组件的数量,并详细解释了按秩合并的策略以提高效率。

题目链接: 547. 省份数量 - 力扣(LeetCode)

题目描述:

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

方法一:

按秩合并:对于每个结点,维护一个秩,表示以该结点为根的子树高度的一个上界。按秩合并策略让具有较小秩的根指向具有较大秩的根。

这里每个结点的秩为负数并且初始化为-1,所以数字越小说明实际上秩越大。

class Solution {
public:
    class province
    {
    public:
        //如果x是根节点,parent[x]保存节点x的秩的相反数;如果x不是根节点,parent[x]保存节点x的父亲节点
        //这里数组原本的命名是father,但是寻找根节点是用find()函数,因为经常混淆,所以这里改名为parent好点
        vector<int> parent;
        province(int n)
        {
            //一开始每个节点的parent数组的值初始化为只有一个节点的树的高度
            //我原本习惯初始化为-1,但吉大教材中树的高度是从0开始的,因此这里改为初始化为0比较好
            parent.resize(n, 0);
        }
        //寻找节点x的根节点
        int Find(int x)
        {
            //如果秩是负数或0,说明是根节点
            if (parent[x] <= 0)
            {
                return x;
            }
            //否则就继续寻找根节点,并且使用路径压缩,将各个节点指向根节点。
            //在Union操作中,如果将树A的根节点连接到另一棵树B的根节点上,那么只有树A根节点的parent数组的值改变了
            //树A中其他节点的parent数组还是指向了原来的根节点,这样不利于后续的查找速度,所以在这里使用路径压缩
            return parent[x] = Find(parent[x]);
        }
        //合并两棵树,将较小的树合并到较大的树上去
        void Union(int x, int y)
        {
            //fx和fy分别是节点x和节点y所在的树的根节点
            //这里不能用parent[x]和parent[fy],因为对于根节点,parent数组中存储的是秩
            //对于非根节点,没有经过路径压缩,parent数组中存储的不一定就是真正的根节点
            int fx = Find(x);
            int fy = Find(y);
            //如果节点x和节点y的根节点相同,说明处在同一棵树上,直接返回
            if (fx == fy)
            {
                return;
            }
            //如果不在同一棵树上,就比较他们的树的秩的大小,即根节点的parent数组值的大小
            //如果x所在的树的秩比y所在的树的秩大,就把y所在的树接到x所在的树上
            if (parent[fx] < parent[fy])
            {
                parent[fy] = fx;
            }
            else
            {
                //否则,就把x所在的树接到y所在的树上
                //其中,但两棵树的秩相等时,需要先将y所在树的秩的负数-1
                if (parent[fx] == parent[fy])
                {
                    parent[fy]--;
                }
                parent[fx] = fy;
            }
        }
    };
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n = isConnected.size();
        province p(n);
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (isConnected[i][j] == 1)
                {
                    //如果i和j的根节点相同,则在同一棵树上
                    //其实这一步也可以不用判断,直接调用p.unioncity()函数即可,因为该函数中也有判断两个节点是否在同一棵树上
                    if (p.Find(i) == p.Find(j))
                    {
                        continue;
                    }
                    //否则就连接对应的两棵树
                    else
                    {
                        p.Union(i, j);
                    }
                }
            }
        }
        set<int> sets;
        //寻找有几个根节点,即有几个省份
        for (int i = 0; i < n; i++)
        {
            //注意,这里找i的根节点时不能用parent[i],因为如果i是根节点,那么parent[x]保存的是节点x的秩的相反数
            //如果i不是根节点,那么可能parent[i]还未经过路径压缩,因此parent[i]不一定是i现在的根节点
            sets.insert(p.Find(i));
        }
        return sets.size();
    }
};

练习一下并查集的题目,好久没用过了,关于find()函数和father数组的区别都搞得有点晕乎乎的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值