隐藏
Single

图论(二) 并查集

并查集主要有两个功能:

  • 将两个元素添加到一个集合中。
  • 判断两个元素在不在同一个集合

将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将三个元素连通在一起,如何连通呢。

只需要用一个一维数组来表示,即:father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。

并查集模板:

1.初始化–init()

2.并查集里寻根–find(int u)

3.判断u和v是否同根–isSame(int u,int v)

4.将v->u加入并查集–join(int u,intv)

寻找存在的路径

题目描述

给定一个包含 n 个节点的无向图中,节点编号从 1 到 n (含 1 和 n )。

你的任务是判断是否有一条从节点 source 出发到节点 destination 的路径存在。

输入描述

第一行包含两个正整数 N 和 M,N 代表节点的个数,M 代表边的个数。

后续 M 行,每行两个正整数 s 和 t,代表从节点 s 与节点 t 之间有一条边。

最后一行包含两个正整数,代表起始节点 source 和目标节点 destination。

输出描述

输出一个整数,代表是否存在从节点 source 到节点 destination 的路径。如果存在,输出 1;否则,输出 0。

输入示例

5 4
1 2
1 3
2 4
3 4
1 4
1
2
3
4
5
6

输出示例

1

提示信息

数据范围:

1 <= M, N <= 100。


思路:

将所有节点加入并查集,加入并查集相当于是构造出图了。

看给出的边对应的两个节点是否同根,同根即代表有路径。

ACM代码如下:

#include <iostream>

#include <vector>

using namespace std;

int N;

int n; // 边

vector<int> father;

void init() {

    father = vector<int>(101, 0);

    for (int i = 0; i < N; i++) {

        father[i] = i;

    }

}

int find(int u) {

    if (father[u] == u) {

        return u;

    } else {

        return father[u] = find(father[u]);

    }

}

bool isSame(int u, int v) {

    u = find(u);

    v = find(v);

    return u == v;

}

void join(int u, int v) {

    u = find(u);

    v = find(v);

    if (u == v)

        return;

    father[v] = u;

}

int hasPath(const vector<vector<int>>& edge, int u, int v) {

    init();

    for (int i = 0; i < n; i++) {

        join(edge[i][0], edge[i][1]);

    }

    return isSame(u, v);

}

int main() {

    cin >> N >> n;

    vector<vector<int>> edge(n, vector<int>(2, 0));

    for (int i = 0; i < n; i++) {

        cin >> edge[i][0] >> edge[i][1];

    }

    int source, destination;

    cin >> source >> destination;

    int res = hasPath(edge, source, destination);

    cout << res << endl;

    return 0;

}

684. 冗余连接

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。

 

示例 1:

输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

示例 2:

输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ai < bi <= edges.length
  • ai != bi
  • edges 中无重复元素
  • 给定的图是连通的

思路:

题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。

如果有多个答案,则返回二维数组中最后出现的边。

那么我们就可以从前向后遍历每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。

如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。

代码如下:

class Solution {

private:

    int n=1001;

    vector<int> father=vector<int>(n,0);

    void init(){

        for(int i=0;i<n;i++){

            father[i]=i;

        }

    }

    int find(int i){

        if(i==father[i]){

            return i;

        }

        else{

            return father[i]=find(father[i]);

        }

    }

    bool isSame(int u,int v){

        u=find(u);

        v=find(v);

        return u==v;

    }

    void join(int u,int v){

        u=find(u);

        v=find(v);

        if(u==v) return;

        father[v]=u;

    }

public:

    vector<int> findRedundantConnection(vector<vector<int>>& edges) {

        vector<int> res;

        init();

        for(int i=0;i<edges.size();i++){

            if(isSame(edges[i][0],edges[i][1])){

                res= edges[i];

            }

            else{

                join(edges[i][0],edges[i][1]);

            }

        }

        return res;

    }

};

685. 冗余连接 II

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

 

示例 1:

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

思路:

1. 并查集准备工作

  • 定义一个固定大小的数组 father[] 表示每个节点的父节点,用于并查集。

  • 定义 n 表示边的数量。

  • 实现并查集的基础操作:

    • 初始化:每个节点的父节点初始是自己。

    • 查找根节点:带路径压缩的 find()

    • 合并两个集合:将两个节点所在集合合并。

    • 判断两个节点是否属于同一集合


2. 判断一组边删除后是否构成树

  • 初始化并查集。

  • 遍历所有边,跳过指定删除的那条边。

  • 对于每一条边,判断是否会形成环:

    • 如果两个端点已经在同一个集合中,说明成环,不是树。

    • 否则将它们合并。


3. 找到成环的边(无入度为2的节点时)

  • 初始化并查集。

  • 遍历所有边:

    • 如果当前边的两个端点已经连通(即 same() 返回 true),说明这条边成环,应删除。

    • 否则合并。


4. 主函数逻辑:寻找冗余边

  • 初始化一个入度数组,用于记录每个节点的入度。

  • 遍历所有边,统计入度。

  • 找出所有入度为2的边(因为最多只有一个节点入度为2,对应最多两条边):

    • 为保证题意(返回最后出现在数组中的边),倒序查找。

  • 分情况讨论:

    • 情况1或2:存在一个入度为2的节点

      • 尝试删除这两条边中的一条,看哪一条删除后图变为树。

    • 情况3:没有入度为2的节点

      • 说明图中形成了有向环,找出形成环的边返回。

代码如下:

class Solution {

private:

    int N = 1001;

    vector<int> father = vector<int>(N, 0);

    int n; // 边

    void init() {

        for (int i = 0; i < N; i++) {

            father[i] = i;

        }

    }

    int find(int u) {

        if (father[u] == u) {

            return u;

        } else {

            return father[u] = find(father[u]);

        }

    }

    void join(int u, int v) {

        u = find(u);

        v = find(v);

        if (u == v) {

            return;

        }

        father[v] = u;

    }

    bool isSame(int u, int v) {

        u = find(u);

        v = find(v);

        return u == v;

    }

    bool isRemoveEdgeToTree(const vector<vector<int>>& edges, int delEdge) {

        init();

        for (int i = 0; i < n; i++) {

            if (i == delEdge)

                continue;

            if (isSame(edges[i][0], edges[i][1])) {

                return false;

            } else {

                join(edges[i][0], edges[i][1]);

            }

        }

        return true;

    }

    int findCycleEdge(const vector<vector<int>>& edges) {

        init();

        for (int i = 0; i < n; i++) {

            if (isSame(edges[i][0], edges[i][1])) {

                return i; // 找到导致环的边

            }

            join(edges[i][0], edges[i][1]);

        }

        return -1; // 没找到环

    }

public:

    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {

        n = edges.size();

        vector<int> inputVec(N, 0); // 入度数组

        for (int i = 0; i < n; i++) {

            inputVec[edges[i][1]]++;

        }

        vector<int> candidateEdges; // 存入度为2的节点对应的两条边的边编号

        for (int i = n - 1; i >= 0; i--) {

            if (inputVec[edges[i][1]] == 2) {

                candidateEdges.push_back(i);

            }

        }

        if (!candidateEdges.empty()) {

            if (isRemoveEdgeToTree(edges, candidateEdges[0])) {

                return edges[candidateEdges[0]];

            } else {

                return edges[candidateEdges[1]];

            }

        }

        int cycleEdge = findCycleEdge(edges);

        if (cycleEdge != -1) {

            return edges[cycleEdge];

        }

        return {};

    }

};

暂无评论

发表评论

HTMLCOPY