侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 675 篇文章
  • 累计创建 128 个标签
  • 累计收到 26 条评论

目 录CONTENT

文章目录

【图, DFS, 并查集】省份数量

GabrielxD
2022-05-23 / 0 评论 / 0 点赞 / 231 阅读 / 1,145 字
温馨提示:
本文最后更新于 2022-07-26,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

题目

547. 省份数量


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

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

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

返回矩阵中 省份 的数量。

示例 1:

image-20220523170457672

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

示例 2:

image-20220523170507389

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

提示:

  • 1 <= n <= 200
  • n == isConnected.length
  • n == isConnected[i].length
  • isConnected[i][j]10
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i]

解题

方法一:DFS

思路

遍历所有城市,对于每个城市,如果该城市尚未被访问过,则从该城市开始深度优先搜索,通过矩阵 isConnected 得到与该城市直接相连的城市有哪些,这些城市和该城市属于同一个连通分量,然后对这些城市继续深度优先搜索,直到同一个连通分量的所有城市都被访问到,即可得到一个省份。 遍历完全部城市以后,即可得到连通分量的总数,即省份的总数。

代码

class Solution {
    private int cities;
    private boolean[] visited;
    private int provinces = 0;

    public int findCircleNum(int[][] isConnected) {
        cities = isConnected.length;
        visited = new boolean[cities];

        for (int i = 0; i < cities; i++) {
            if (visited[i]) continue;
            dfs(i, isConnected);
            provinces++;
        }

        return provinces;
    }

    public void dfs(int i, int[][] isConnected) {
        for (int j = 0; j < cities; j++) {
            if (visited[j] || isConnected[i][j] == 0) continue;
            visited[j] = true;
            dfs(j, isConnected);
        }
    }
}

方法二:并查集

思路

图的连通分量:
  • 无向图G的极大连通子图称为G的连通分量( Connected Component)。任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量。

  • 简单举例来说:

    image-20220523171414911

    该图连通分量为 1

    image-20220523171528089

    该图连通分量为 3

并查集的基本概念(借鉴):
  • 并查集是一种数据结构
  • 并查集这三个字,一个字代表一个意思:
    • 并(Union): 合并
    • 查(Find): 查找
    • 集(Set): 这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素
  • 并查集的典型应用是有关连通分量的问题。
  • 并查集解决单个问题(添加,合并,查找)的时间复杂度都是 $O(1)$。
  • 因此,并查集可以应用到在线算法中。
并查集的实现:
// 并查集跟树有些类似,只不过它跟树是相反的。
// 在树中,每个节点会记录它的子节点。
// 而在并查集中,每个节点会记录它的父节点。
class UnionFind {
    private Map<Integer,Integer> nodeToRoot;

    public UnionFind() {
        nodeToRoot = new HashMap<Integer, Integer>();
    }
    // 初始化
    // 当把一个新节点添加到并查集中,它的父节点应该为空
    public void add(int x) {
        if (!nodeToRoot.containsKey(x)) nodeToRoot.put(x, null);
    }
    // 如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。
    // 这里究竟把谁当做父节点一般是没有区别的。
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) nodeToRoot.put(rootP, rootQ);
    }
    // 查找祖先,如果祖先不是初始化的 null 就一直迭代
    public int find(int n) {
        while (nodeToRoot.get(n) != null) {
            nodeToRoot.put(n, find(nodeToRoot.get(n)));
            n = nodeToRoot.get(n);
        }
        return n;
    }
}

故本期其实就是求 isConnected 这个邻接矩阵表示的图的连通分量为多少,很适合用并查集直接求解。已经知道了节点的数量 isConnected.length 就可以使用数组代替字典实现并查集。

代码

class Solution {
    private int[] nodeToRoot;

    public int findCircleNum(int[][] isConnected) {
        int cities = isConnected.length;
        nodeToRoot = new int[cities];
        for (int i = 0; i < cities; i++) nodeToRoot[i] = i;
        for (int i = 0; i < cities; i++) {
            for (int j = i + 1; j < cities; j++) {
                if (isConnected[i][j] == 1) union(i, j);
            }
        }
        int provinces = 0;
        for (int i = 0; i < cities; i++) {
            if (i == nodeToRoot[i]) provinces++;
        }
        return provinces;
    }

    private void union(int p, int q) {
        nodeToRoot[find(p)] = find(q);
    }

    private int find(int n) {
        if (n != nodeToRoot[n]) nodeToRoot[n] = find(nodeToRoot[n]);
        return nodeToRoot[n];
    }
}
0

评论区