算法训练营第六十九天 | 并查集理论基础、寻找存在的路径、冗余连接、冗余连接II

发布于:2024-06-24 ⋅ 阅读:(129) ⋅ 点赞:(0)

并查集理论基础

  • 并查集解决的主要是连通性问题,即判断两个节点是否连通,无论是直接连通还是间接连通

思路方法:

  • 用一个数组记录每个节点的父节点,这个父节点是可以变化的,在加入新的边的过程中,我们可以不断更新这个数组使其指向一个最远的节点,遇到并不指向一处的新节点,则将这个最远的节点指向新节点;

  • 如果两个节点都是新节点,则将前面一个节点的父节点设置为后一个节点

本质

  • 并查集的背后,其实就是一个路径压缩的过程​​​​​​​

模板代码如下:

int father[101];

void init() {
    for (int i = 0; i <= 100; i++) father[i] = i;
}

int find(int a) {
    while (father[a] != a) {
        a = father[a];
    }
    return a;
}

bool isSame(int a, int b) {
    a = find(a);
    b = find(b);
    return a == b;
}

void join(int a, int b) {
    if (isSame(a, b)) return;
    father[find(a)] = b;
    return;
}

卡码网107 寻找存在的路径

这是一个无向图找路径的问题,之前我们已经用深搜的方法将路径打印出来了,但这题只需要找是否存在路径,那么就可以用并查集判断连通性来节省时间了

#include <iostream>

using namespace std;

int father[101];

void init() {
    for (int i = 0; i <= 100; i++) father[i] = i;
}

int find(int a) {
    while (father[a] != a) {
        a = father[a];
    }
    return a;
}

bool isSame(int a, int b) {
    a = find(a);
    b = find(b);
    return a == b;
}

void join(int a, int b) {
    if (isSame(a, b)) return;
    father[find(a)] = b;
    return;
}

int main() {
    int m, n;
    cin >> n >> m;
    int s, t;
    init();
    for (int i = 0; i < m; i++) {
        cin >> s >> t;
        join(s, t);
    }
    int source, destination;
    cin >> source >> destination;
    if (isSame(source, destination)) cout << 1 << endl;
    else cout << 0 << endl;
    return 0;
}

卡码网108 冗余路径

其实本质上是要从环中删除一条边,如果遇到两个已经连同的节点又在一条新输入的边中出现时,即可打印输出了。和题目要求中输出最后一条可删除的边的要求并不冲突

#include <iostream>

using namespace std;

int father[1001];

void init() {
    for (int i = 0; i <= 1000; i++) father[i] = i;
}

int find(int a) {
    while (father[a] != a) {
        a = father[a];
    }
    return a;
}

bool isSame(int a, int b) {
    a = find(a);
    b = find(b);
    return a == b;
}

void join(int a, int b) {
    if (isSame(a, b)) return;
    father[find(a)] = b;
    return;
}

int main() {
    int n;
    cin >> n;
    int s, t;
    init();
    for (int i = 0; i < n; i++) {
        cin >> s >> t;
        if (isSame(s, t)) {
            cout << s << ' ' << t << endl;
            return 0;
        }
        join(s, t);
    }
    return 0;
}

卡码网109 冗余连接II

这题加上了对于节点入度的判断,入度为2的节点需要着重处理,如果删除后其余节点仍旧能够构成有向环,说明删除的边是必不可少的边,而留下的才是构成了有向环的那一条边
而如果没有入度为2的节点,直接按照上一题的删除思路来就行了

#include <iostream>
#include <vector>
using namespace std;

int father[1001];

void init() {
    for (int i = 0; i <= 1000; i++) father[i] = i;
}

int find(int a) {
    while (father[a] != a) {
        a = father[a];
    }
    return a;
}

bool isSame(int a, int b) {
    a = find(a);
    b = find(b);
    return a == b;
}

void join(int a, int b) {
    if (isSame(a, b)) return;
    father[find(a)] = b;
    return;
}

void getRemovedEdge(vector<vector<int>>& edge) {
    init();
    for (int i = 0; i < edge.size(); i++) {
        if (isSame(edge[i][0], edge[i][1])) {
            cout << edge[i][0] << ' ' << edge[i][1] << endl;
            return;
        } else {
            join(edge[i][0], edge[i][1]);
        }
    }
}

int isTreeAfterRemoveEdge(const vector<vector<int>>& edge, int deleteEdge) {
    init(); // 初始化并查集
    for (int i = 0; i < edge.size(); i++) {
        if (i == deleteEdge) continue;
        if (isSame(edge[i][0], edge[i][1])) { // 构成有向环了,一定不是树
            return false;
        }
        join(edge[i][0], edge[i][1]);
    }
    return true;
}


int main() {
    int n;
    cin >> n;
    int s, t;
    vector<vector<int>> edge;
    vector<int> inDegree(n+1, 0);
    for (int i = 0; i < n; i++) {
        cin >> s >> t;
        inDegree[t]++;
        edge.push_back({s, t});
    }
    vector<int> vec;
    for (int i = n-1; i >= 0; i--) {
        if (inDegree[edge[i][1]] == 2) {
            vec.push_back(i);
        }
    }
    if (vec.size() > 0) {
        if (isTreeAfterRemoveEdge(edge, vec[0])) {
            cout << edge[vec[0]][0] << ' ' << edge[vec[0]][1] << endl;
        }    
        else {
            cout << edge[vec[1]][0] << ' ' << edge[vec[1]][1] << endl;
        }
    }
    else 
        getRemovedEdge(edge);
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到