洛谷每日一题(P1536 村村通) 并查集

原题目链接:

P1536 村村通 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

原题目截图:

思路分析:

思路一:并查集法

我们先看一个例子:

可见,我们只要求出题目给的数据,能够组成多少联通分量,就可以求出要加几条路了。

而并查集,就是我们处理这道题的关键。

什么是并查集:

并查集(Union-Find)是一种用于处理一些不交集(Disjoint Sets)的集合操作的数据结构。它支持两种主要的操作:

  1. Find:确定一个元素属于哪个子集。通常使用“路径压缩”技术来优化查询的效率。
  2. Union:将两个元素所属的集合合并。

并查集通常用于处理一些不相交集合的合并及查询问题,比如网络连接性、等价类划分、图的连通分量等。

我在csdn上看见一个写的很好的博客,详细的说明了什么是并查集和用法:

传送门:【算法与数据结构】—— 并查集-CSDN博客

解决代码:
#include<iostream>
#include<vector>
using namespace std;

struct DSU {
    vector<int> pre;
    vector<int> rank;
    int num;       // 集合的个数
    DSU(int size) : pre(size), rank(size, 0), num(size) {
        for (int i = 0; i < size; i++)
            pre[i] = i;
    }

    int find(int x) {   // 查找x的代表元
        if (x == pre[x]) return x;
        return pre[x] = find(pre[x]);     // 路径压缩算法
    }

    void union_set(int x, int y) {
        // 找到x,y的代表元
        int _x = find(x), _y = find(y);
        if (_x == _y) return;      // 代表元相同说明已经是一个集合,不用处理
        if (rank[_x] > rank[_y]) pre[_y] = _x;
        else if (rank[_x] < rank[_y]) {
            pre[_x] = _y;
        }
        else {
            pre[_x] = _y;
            rank[_y]++;
        }
        num--;         // 合并后,总集合数减一
    }

    bool isconected(int x, int y) { // 判断两个元素是否在同一个集合
        return find(x) == find(y);
    }
};

int main() {
    int n, m;
    while (true) {
        cin >> n;
        if (n == 0) break;
        cin >> m;
        DSU dsu(n);   // 因为从1开始数的
        for (int i = 0; i < m; i++) {
            int town_x, town_y;
            cin >> town_x >> town_y;
            dsu.union_set(town_x-1 , town_y-1 ); // 假设输入是从1开始的,需要减1
        }
        cout << dsu.num-1<< endl;  // 输出剩余的集合数量
    }

    return 0;
}
思路二:BFS广度优先搜索

不过很显然,相比并查集方法,BFS方法有些许的问题:

第一需要储存图,占用空间很大。

第二如果这个图的连通分量很多,那么就需要多次调用BFS,时间开销大。

当然,这道题因为数据量并不大,因此也能通过。

解决代码:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

// BFS算法寻找连通分量
int bfs(int start, vector<bool>& visited, const vector<vector<int>>& graph) {
    queue<int> q;
    q.push(start);
    visited[start] = true;
    int components = 0;

    while (!q.empty()) {
        int current = q.front();
        q.pop();
        components++;
        for (int neighbor : graph[current]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
    return components > 0 ? 1 : 0; // 如果有访问过的节点,返回1,否则返回0
}

int main() {
    int n, m;
    while (cin >> n && n != 0) {
        cin >> m;
        vector<vector<int>> graph(n + 1); // 创建图
        for (int i = 0; i < m; i++) {
            int town_x, town_y;
            cin >> town_x >> town_y;
            graph[town_x].push_back(town_y);
            graph[town_y].push_back(town_x); // 因为是无向图
        }

        vector<bool> visited(n + 1, false);
        int components = 0;
        for (int i = 1; i <= n; i++) {
            if (!visited[i]) {
                components += bfs(i, visited, graph);
            }
        }

        // 输出结果:最少还需要建设的道路数目
        cout << components - 1 << endl;
    }

    return 0;
}

总结两种方法的利弊:

并查集

优势

  1. 高效性并查集特别适用于处理动态连通性问题,即频繁地执行合并和查找操作的场景。其平均时间复杂度为O(α(n)),其中α是阿克曼函数的反函数,增长非常慢,近似于O(1)。

  2. 路径压缩:并查集通过路径压缩技术,可以在查找操作中减少查找深度,使得操作更加快速。

  3. 按秩合并:并查集通过按秩合并策略,可以保持树的平衡,避免形成过深的树结构,从而保持操作的高效性。

劣势

  1. 实现复杂:并查集的实现相对复杂,尤其是路径压缩和按秩合并的优化策略。

  2. 不保留具体路径:并查集不保留元素之间的具体路径,只记录了元素所属的集合,因此如果需要路径信息,则并查集不适合。(这是并查集特点之一,不关注具体怎么连接,只关注是否连接)

  3. 不能撤销操作:一旦两个集合合并,无法撤销合并操作。

BFS算法

优势

  1. 直观简单:BFS算法的实现相对直观和简单,容易理解和实现。

  2. 保留路径信息:BFS在搜索过程中可以记录路径,如果需要路径信息,BFS是更好的选择

  3. 适用性广:BFS不仅可以用来找连通分量,还广泛应用于其他图搜索问题,如最短路径问题。

劣势

  1. 效率较低:对于大规模数据,BFS的时间复杂度为O(V+E),其中V是顶点数,E是边数,相比于并查集的近似O(1),效率较低。

  2. 空间消耗大:BFS需要使用队列来存储待访问的节点,对于大规模数据,可能需要较大的内存空间。

  3. 重复访问:在没有优化的情况下,BFS可能会重复访问节点,需要额外的逻辑来避免重复访问。

总结
  • 并查集更适合处理动态连通性问题,尤其是需要频繁进行合并和查找操作的场景。

  • BFS算法更适合解决需要路径信息的图搜索问题,且实现相对简单直观。

因此,在这一道“村村通工程”问题中,如果只关心连通性而不关心路径,那么并查集是一个更高效的选择。如果需要知道具体的连通路径或者问题规模较小,BFS也是一个不错的选择。

今天的博客就写到这里了,明天就是国庆节了,预祝各位国庆节have a good day!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值