代码随想录第五十六天| 108. 冗余连接 109. 冗余连接||

冗余连接

题目描述

有一个图,它是一棵树,拥有 n 个节点(节点编号 1 到 n)和 n - 1 条边的连通无环无向图。现在在这棵树的基础上,添加一条边(依然是 n 个节点,但有 n 条边),使这个图变成了有环图。请你找出冗余边,删除后,使该图可以重新变成一棵树。

输入输出格式

输入格式:
第一行包含一个整数 n,表示节点的数量。
接下来的 n 行,每行包含两个整数 s 和 t,表示一条边连接节点 s 和 t。

输出格式:
输出两个整数,表示冗余边的两个节点,按升序排列。

解题思路

  1. 问题分析

    • 原本是一个树结构,添加一条边后形成环。
    • 冗余边就是导致环形成的那条边。
    • 我们需要找出这条边并删除它,使图重新变成树。
  2. 并查集(Union-Find)数据结构

    • 并查集是一种用于处理动态连通性问题的数据结构。
    • 它支持两种操作:查找(find)和合并(union)。
    • 查找操作用于确定一个节点的根节点,合并操作用于将两个节点所在的集合合并。
  3. 算法步骤

    • 初始化并查集,每个节点的父节点初始化为自己。
    • 遍历每一条边,对于每一条边的两个节点 s 和 t:
      • 如果 s 和 t 已经连通(属于同一个集合),则这条边就是冗余边,记录下来。
      • 如果 s 和 t 不连通,则将它们合并到同一个集合中。
    • 最后输出记录的冗余边。
  4. 代码实现

    • 使用 Java 实现并查集类(DisJoint)。
    • 在主函数中读取输入,遍历每条边,利用并查集判断是否为冗余边。

代码实现

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        DisJoint disjoint = new DisJoint(n + 1);
        int[] lastRedundantEdge = new int[2];
        for (int i = 0; i < n; i++) {
            int s = sc.nextInt();
            int t = sc.nextInt();
            if (disjoint.isSame(s, t)) {
                lastRedundantEdge[0] = s;
                lastRedundantEdge[1] = t;
            } else {
                disjoint.join(s, t);
            }
        }
        System.out.println(lastRedundantEdge[0] + " " + lastRedundantEdge[1]);
    }
}

class DisJoint {
    private int[] father;

    public DisJoint(int N) {
        father = new int[N];
        for (int i = 0; i < N; ++i) {
            father[i] = i;
        }
    }

    public int find(int n) {
        return n == father[n] ? n : (father[n] = find(father[n]));
    }

    public void join(int n, int m) {
        n = find(n);
        m = find(m);
        if (n == m) return;
        father[m] = n;
    }

    public boolean isSame(int n, int m) {
        n = find(n);
        m = find(m);
        return n == m;
    }
}

示例

输入:

5
1 2
1 3
2 3
3 4
4 5

输出:

2 3

解释:

  • 原本的树结构是 1-2-3-4-5。
  • 添加边 2-3 后形成环。
  • 冗余边是 2-3,删除后图重新变成树。

冗余连接 II

题目描述

有一种有向树,该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有向树拥有 n 个节点和 n - 1 条边。

现在有一个有向图,该图是在有向树中的两个没有直接链接的节点中间添加一条有向边。该有向图有 n 个节点(节点编号从 1 到 n)和 n 条边。请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一棵有向树。

输入描述:
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表这是 s 节点连接并指向 t 节点的单向边。

输出描述:
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。

解题思路

  1. 问题分析

    • 原本是一个有向树,添加一条边后可能形成环或者导致某个节点有两个父节点。
    • 需要找出这条冗余边并删除,使图重新变成有向树。
  2. 两种情况

    • 情况一:存在一个节点有两个父节点。此时需要判断哪一条边是冗余的。
    • 情况二:图中存在环。此时需要找出形成环的最后一条边。
  3. 算法步骤

    • 记录入度:遍历所有边,记录每个节点的入度,找出是否有节点入度为 2。
    • 处理情况一:如果存在入度为 2 的节点,找出两条指向该节点的边,分别尝试删除,判断是否能形成树。
    • 处理情况二:如果不存在入度为 2 的节点,使用并查集找出形成环的最后一条边。
  4. 并查集(Union-Find)数据结构

    • 用于处理动态连通性问题,支持查找和合并操作。
    • 在检测环时,如果两个节点已经连通,则当前边是冗余边。
  5. 代码实现

    • 使用 Java 实现并查集类(DisJoint)。
    • 在主函数中读取输入,记录边和节点信息。
    • 根据是否存在入度为 2 的节点,分别处理两种情况。

代码实现

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        List<Edge> edges = new ArrayList<>();
        Node[] nodeMap = new Node[n + 1];
        for (int i = 1; i <= n; i++) {
            nodeMap[i] = new Node();
        }
        Integer doubleIn = null;
        for (int i = 0; i < n; i++) {
            int s = sc.nextInt();
            int t = sc.nextInt();

            nodeMap[t].in++;
            if (nodeMap[t].in > 1) doubleIn = t;
            Edge edge = new Edge(s, t);
            edges.add(edge);
        }
        Edge result = null;
        if (doubleIn != null) {
            List<Edge> doubleInEdges = new ArrayList<>();
            for (Edge edge : edges) {
                if (edge.t == doubleIn) doubleInEdges.add(edge);
                if (doubleInEdges.size() == 2) break;
            }
            Edge edge = doubleInEdges.get(1);
            if (isTreeWithExclude(edges, edge, nodeMap)) {
                result = edge;
            } else {
                result = doubleInEdges.get(0);
            }
        } else {
            result = getRemoveEdge(edges, nodeMap);
        }
        System.out.println(result.s + " " + result.t);
    }

    public static boolean isTreeWithExclude(List<Edge> edges, Edge excludeEdge, Node[] nodeMap) {
        DisJoint disjoint = new DisJoint(nodeMap.length + 1);
        for (Edge edge : edges) {
            if (edge == excludeEdge) continue;
            if (disjoint.isSame(edge.s, edge.t)) {
                return false;
            }
            disjoint.join(edge.s, edge.t);
        }
        return true;
    }

    public static Edge getRemoveEdge(List<Edge> edges, Node[] nodeMap) {
        int length = nodeMap.length;
        DisJoint disjoint = new DisJoint(length);

        for (Edge edge : edges) {
            if (disjoint.isSame(edge.s, edge.t)) return edge;
            disjoint.join(edge.s, edge.t);
        }
        return null;
    }

    static class DisJoint {
        private int[] father;

        public DisJoint(int N) {
            father = new int[N];
            for (int i = 0; i < N; ++i) {
                father[i] = i;
            }
        }

        public int find(int n) {
            return n == father[n] ? n : (father[n] = find(father[n]));
        }

        public void join(int n, int m) {
            n = find(n);
            m = find(m);
            if (n == m) return;
            father[m] = n;
        }

        public boolean isSame(int n, int m) {
            n = find(n);
            m = find(m);
            return n == m;
        }
    }

    static class Edge {
        int s;
        int t;

        public Edge(int s, int t) {
            this.s = s;
            this.t = t;
        }
    }

    static class Node {
        int id;
        int in;
        int out;
    }
}

示例

输入:

5
1 2
1 3
3 4
4 1
4 5

输出:

4 1

解释:

  • 原本的有向树结构被破坏,因为添加了边 4->1,形成了环。
  • 删除边 4->1 后,图重新变成有向树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值