华为OD机试 - 查找一个有向网络的头节点和尾节点 - 拓扑排序(Java 2024 D卷 200分)

在这里插入图片描述

华为OD机试 2024D卷题库疯狂收录中,刷题点这里

专栏导读

本专栏收录于《华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)》

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

一、题目描述

给定一个有向图,图中可能包含有环,图使用二维矩阵表示,每一行的第一列表示起始节点,第二列表示终止节点,如 [0, 1] 表示从 0 到 1 的路径。

每个节点用正整数表示。

求这个数据的首节点与尾节点,题目给的用例会是一个首节点,但可能存在多个尾节点。同时图中可能含有环。如果图中含有环,返回 [-1]。

说明:入度为 0 是首节点,出度为 0 是尾节点。

二、输入描述

第一行为后续输入 A 的键值对 Q 数量 (N ≥ 0)

第二行为 2N 个数字。每两个为一组一个起点,一个终点,如:

4
1 2 2 3 3 1 3 4

三、输出描述

输出一个首节点和尾节点。如果有多个尾节点,按从大到小的顺序输出。

备注

  • 如果图有环,输出为 [-1]
  • 所有输入均合法,不会出现不匹配的数据

四、测试用例

测试用例1:

1、输入

4
0 1 0 2 1 2 2 3

2、输出

0 3

3、说明

输入表示一个有向图,包含了4条边(键值对数量为4)。具体如下:

4
0 1
0 2
1 2
2 3

将输入分解后,我们得到以下4条边:

从节点 0 到节点 1
从节点 0 到节点 2
从节点 1 到节点 2
从节点 2 到节点 3

处理逻辑

计算每个节点的入度和出度:

节点 0:

入度:0(没有任何边指向节点 0)
出度:2(节点 0 指向节点 1 和节点 2)

节点 1:

入度:1(节点 0 指向节点 1)
出度:1(节点 1 指向节点 2)

节点 2:

入度:2(节点 0 和节点 1 都指向节点 2)
出度:1(节点 2 指向节点 3)

节点 3:

入度:1(节点 2 指向节点 3)
出度:0(没有任何边从节点 3 指出)

确定首节点和尾节点:

首节点(入度为0的节点):

节点 0 是入度为0的节点,因此它是首节点。

尾节点(出度为0的节点):

节点 3 是出度为0的节点,因此它是尾节点。

输出结果:

根据题目的要求,我们按从大到小的顺序输出首节点和尾节点。

测试用例2:

1、输入

2
0 1 0 2

2、输出

0 1 2

3、说明

五、解题思路

1、拓扑排序

拓扑排序是一种用于有向无环图(DAG)的线性排序,使得对于每个边 (u, v),节点 u 在节点 v 之前出现。简单来说,就是将有向图的所有节点排序,保证每个节点都在它指向的节点之前。

最常用的两种拓扑排序算法是Kahn算法和基于DFS(深度优先搜索)的算法。

2、为什么采用拓扑排序?

题目要求找到有向图中的首节点和尾节点,同时需要判断图中是否存在环。如果存在环,则输出-1;如果没有环,则输出所有首节点和尾节点,这个问题可以通过拓扑排序有效地解决。

3、具体步骤:

  1. 构建邻接表:
    • 从输入数据中读取节点和边,构建有向图的邻接表表示法。
    • 使用HashMap存储每个节点及其对应的邻居节点列表。
    • 使用HashSet存储所有出现过的节点,用于后续计算入度和出度。
  2. 检测环:
    • 使用拓扑排序算法(Kahn算法)检测图中是否有环。
    • 初始化每个节点的入度为0,遍历邻接表,计算每个节点的入度。
    • 使用队列保存所有入度为0的节点,并逐个处理这些节点,将其邻居节点的入度减1。如果邻居节点的入度变为0,将其加入队列。
    • 如果处理完所有入度为0的节点后,仍有未处理的节点,说明图中存在环。
  3. 找首节点和尾节点:
    • 首节点:入度为0的节点。
    • 尾节点:出度为0的节点。
    • 通过遍历所有节点,计算每个节点的入度和出度,找出入度为0的节点作为首节点,出度为0的节点作为尾节点。
  4. 输出结果:
    • 如果检测到图中有环,输出-1。
    • 如果没有环,按升序输出所有首节点和尾节点。

六、Java算法源码

public class OdTest {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取键值对的数量
        int n = scanner.nextInt();
        int[][] edges = new int[n][2];

        // 读取所有边的起始和结束节点
        for (int i = 0; i < n; i++) {
            edges[i][0] = scanner.nextInt();
            edges[i][1] = scanner.nextInt();
        }

        // 构建邻接表
        Map<Integer, List<Integer>> graph = new HashMap<>();
        Set<Integer> nodes = new HashSet<>();
        for (int[] edge : edges) {
            graph.putIfAbsent(edge[0], new ArrayList<>());
            graph.get(edge[0]).add(edge[1]);
            nodes.add(edge[0]);
            nodes.add(edge[1]);
        }

        // 检查图中是否有环
        if (hasCycle(graph, nodes)) {
            System.out.println(-1);
            return;
        }

        // 找首节点和尾节点
        List<Integer> heads = new ArrayList<>();
        List<Integer> tails = new ArrayList<>();

        // 初始化入度和出度
        Map<Integer, Integer> inDegree = new HashMap<>();
        Map<Integer, Integer> outDegree = new HashMap<>();

        for (int node : nodes) {
            inDegree.put(node, 0);
            outDegree.put(node, 0);
        }

        // 计算每个节点的入度和出度
        for (int node : graph.keySet()) {
            for (int neighbor : graph.get(node)) {
                inDegree.put(neighbor, inDegree.get(neighbor) + 1);
                outDegree.put(node, outDegree.get(node) + 1);
            }
        }

        // 找入度为0的节点(首节点)和出度为0的节点(尾节点)
        for (int node : nodes) {
            if (inDegree.get(node) == 0) {
                heads.add(node);
            }
            if (outDegree.get(node) == 0) {
                tails.add(node);
            }
        }

        // 对首节点和尾节点进行排序
        Collections.sort(heads);
        Collections.sort(tails);

        // 输出首节点和尾节点
        // 输出首节点和尾节点
        StringBuilder builder = new StringBuilder();
        for(int i = 0;i<heads.size();i++){
            builder.append(heads.get(i)).append(" ");
        }

        for(int i = 0;i<tails.size();i++){
            builder.append(tails.get(i)).append(" ");
        }

        System.out.println(builder.substring(0, builder.length()-1));
    }

    /**
     * 检查图中是否存在环
     * @param graph 邻接表表示的图
     * @param nodes 图中的所有节点
     * @return 如果存在环,返回true,否则返回false
     */
    private static boolean hasCycle(Map<Integer, List<Integer>> graph, Set<Integer> nodes) {
        // 初始化所有节点的入度
        Map<Integer, Integer> inDegree = new HashMap<>();
        for (int node : nodes) {
            inDegree.put(node, 0);
        }

        // 计算每个节点的入度
        for (int node : graph.keySet()) {
            for (int neighbor : graph.get(node)) {
                inDegree.put(neighbor, inDegree.get(neighbor) + 1);
            }
        }

        // 使用队列保存入度为0的节点
        Queue<Integer> queue = new LinkedList<>();
        for (int node : inDegree.keySet()) {
            if (inDegree.get(node) == 0) {
                queue.add(node);
            }
        }

        int count = 0;
        // 拓扑排序处理
        while (!queue.isEmpty()) {
            int current = queue.poll();
            count++;
            if (graph.containsKey(current)) {
                for (int neighbor : graph.get(current)) {
                    inDegree.put(neighbor, inDegree.get(neighbor) - 1);
                    if (inDegree.get(neighbor) == 0) {
                        queue.add(neighbor);
                    }
                }
            }
        }

        // 如果拓扑排序处理的节点数不等于总节点数,则存在环
        return count != nodes.size();
    }
}

七、效果展示

1、输入

4
0 1 0 2 1 2 2 3

2、输出

0 3

3、说明

在这里插入图片描述


🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 2024 D卷 200分)

🏆本文收录于,华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

在这里插入图片描述

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值