⭐算法OJ⭐并查集的应用/DFS/BFS:省份数量问题 Number of Provinces

547. Number of Provinces

There are n cities. Some of them are connected, while some are not. If city a is connected directly with city b, and city b is connected directly with city c, then city a is connected indirectly with city c.

A province is a group of directly or indirectly connected cities and no other cities outside of the group.

You are given an n x n matrix isConnected where isConnected[i][j] = 1 if the i^th city and the jth city are directly connected, and isConnected[i][j] = 0 otherwise.

Return the total number of provinces.

Example 1:

复制代码
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2

Example 2:

复制代码
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3

问题描述

给定一个 n x n 的矩阵 isConnected,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,isConnected[i][j] = 0 表示二者不相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份是一组直接或间接相连的城市,组内不含其他没有连接的城市。要求返回省份的总数。

解决思路

一眼"并查集",这个sense是要有的。不熟悉的同学可以复习并查集(Union-Find)数据结构详解。简单地说,在计算机科学中,并查集 (Disjoint-set data structure,直译为不交集数据结构)是一种数据结构,用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并及查询问题。

并查集支持如下操作:

  • 查询:查询某个元素属于哪个集合,通常是返回集合内的一个"代表元素"。这个操作是为了判断两个元素是否在同一个集合之中。
  • 合并:将两个集合合并为一个。
  • 添加:添加一个新集合,其中有一个新元素。

由于支持查询和合并这两种操作,并查集在英文中也被称为联合-查找数据结构 (Union-find data structure)或者合并-查找集合 (Merge-find set),简称并查集

在省份数量问题中:

  • 每个城市最初属于自己独立的省份(即自己是自己的父节点)。
  • 当两个城市相连时,我们合并它们所在的省份。
  • 最终,剩余的独立集合数量就是省份的总数。

详细步骤解析

  1. 初始化并查集

    cpp 复制代码
    class UnionFind {
    public:
        UnionFind(int size) : parent(size), count(size) {
            for (int i = 0; i < size; ++i) {
                parent[i] = i;  // 每个城市初始时父节点指向自己
            }
        }
        // ...
    private:
        vector<int> parent;  // 记录每个节点的父节点
        int count;           // 记录当前集合/省份的数量
    };
    • parent数组:parent[i]表示城市i的父节点
    • count:初始值为n,表示最初有n个独立省份(每个城市一个)
  2. Find操作 - 查找根节点

    cpp 复制代码
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩优化
        }
        return parent[x];
    }
    • 递归地查找x的根节点

    • 路径压缩:在查找过程中将节点直接指向根节点,减少后续查找时间

    • 示例:查找城市3的根节点

      复制代码
      1 → 2 → 3 → 4 (根节点是4)
      查找3后变为:
      1 → 2 → 4
      3 → 4
  3. Union操作 - 合并集合

    cpp 复制代码
    void unionSet(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {   // 只有当两个城市不在同一集合时才合并
            parent[rootY] = rootX;
            --count;  // 合并后省份数量减1
        }
    }
    • 找到两个城市的根节点
    • 如果根节点不同,则将其中一个根节点指向另一个
    • 减少省份计数
  4. 主算法流程

    cpp 复制代码
    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.unionSet(i, j);  // 合并相连的城市
                }
            }
        }
        
        return uf.getCount();  // 返回最终省份数量
    }
    • 遍历所有城市对(只需要遍历矩阵上半部分,避免重复)
    • 当两个城市相连时,合并它们所在的集合
    • 最终剩余的集合数量就是省份总数

C++ 实现:并查集(Union-Find)

cpp 复制代码
#include <vector>
using namespace std;

class UnionFind {
public:
    UnionFind(int size) : parent(size), count(size) {
        for (int i = 0; i < size; ++i) {
            parent[i] = i;
        }
    }
    
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    
    void unionSet(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootY] = rootX;
            --count;
        }
    }
    
    int getCount() const {
        return count;
    }

private:
    vector<int> parent;
    int count;
};

class Solution {
public:
    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.unionSet(i, j);
                }
            }
        }
        
        return uf.getCount();
    }
};

深度优先搜索(DFS)解法

  1. 使用递归实现深度优先搜索
  2. 通过visited数组避免重复访问
  3. 每次遇到未访问城市就开始新的DFS,代表发现新省份
cpp 复制代码
#include <vector>
using namespace std;

int findCircleNum(vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    vector<bool> visited(n, false); // 访问标记数组
    int provinces = 0; // 省份计数器
    
    // DFS递归函数
    function<void(int)> dfs = [&](int city) {
        visited[city] = true;
        for (int neighbor = 0; neighbor < n; ++neighbor) {
            // 如果相连且未访问,则递归访问
            if (isConnected[city][neighbor] == 1 && !visited[neighbor]) {
                dfs(neighbor);
            }
        }
    };
    
    // 遍历所有城市
    for (int city = 0; city < n; ++city) {
        if (!visited[city]) { // 发现未访问的城市
            ++provinces; // 增加省份计数
            dfs(city); // DFS遍历该省份所有城市
        }
    }
    
    return provinces;
}

广度优先搜索(BFS)解法

  1. 使用队列实现广度优先搜索
  2. 同样使用visited数组避免重复访问
  3. 每次遇到未访问城市就开始新的BFS,代表发现新省份
cpp 复制代码
#include <vector>
#include <queue>
using namespace std;

int findCircleNum(vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    vector<bool> visited(n, false); // 访问标记数组
    int provinces = 0; // 省份计数器
    queue<int> q; // BFS队列
    
    for (int city = 0; city < n; ++city) {
        if (!visited[city]) { // 发现未访问的城市
            ++provinces; // 增加省份计数
            q.push(city); // 当前城市入队
            visited[city] = true; // 标记已访问
            
            // BFS遍历
            while (!q.empty()) {
                int current = q.front();
                q.pop();
                
                // 检查所有可能相连的城市
                for (int neighbor = 0; neighbor < n; ++neighbor) {
                    if (isConnected[current][neighbor] == 1 && !visited[neighbor]) {
                        visited[neighbor] = true; // 标记已访问
                        q.push(neighbor); // 相连城市入队
                    }
                }
            }
        }
    }
    
    return provinces;
}

复杂度分析

方法 时间复杂度 空间复杂度
DFS O ( n 2 ) O(n²) O(n2) O ( n ) O(n) O(n)
BFS O ( n ² ) O(n^²) O(n²) O ( n ) O(n) O(n)
并查集 O ( n 2 α ( n ) ) O(n² α(n)) O(n2α(n)) O ( n ) O(n) O(n)
相关推荐
奋进的小暄2 分钟前
贪心算法(13)(java)合并区间
算法
快来卷java14 分钟前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
阿巴~阿巴~16 分钟前
C/C++蓝桥杯算法真题打卡(Day11)
算法
tpoog19 分钟前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
刚入门的大一新生39 分钟前
排序算法3-交换排序
算法·排序算法
滴答滴答嗒嗒滴1 小时前
Python小练习系列 Vol.5:数独求解(经典回溯 + 剪枝)
python·深度优先·剪枝
董董灿是个攻城狮2 小时前
Transformer 通关秘籍7:词向量的通俗理解
算法
卷卷的小趴菜学编程2 小时前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法
komo莫莫da2 小时前
Day14 动态规划(3)
算法·深度优先·动态规划
地平线开发者3 小时前
【征程 6】工具链 VP 示例为什么能运行
算法·自动驾驶