Java实现拓扑排序的示例代码

下面是Java实现拓扑排序的完整攻略:

1. 理解拓扑排序的概念

拓扑排序是一种常用于有向无环图(DAG)的算法,用于确定图中所有节点的相对顺序关系。具体来说,拓扑排序可以将一个DAG的所有节点线性排序,使得对于任何一条有向边(u, v),起点u在拓扑排序中都出现在终点v的前面。

2. 实现拓扑排序的算法

一个直接的想法是通过深度优先搜索(DFS)来实现拓扑排序。首先,我们需要先构建DAG模型,然后从任意一个节点开始进行DFS搜索,具体如下:

  1. 对于起始节点start,递归访问它的所有出边,对于每一条出边(u, v),递归访问v节点的所有出边;
  2. 当某个节点v没有未访问的出边时,将v节点添加到顺序列表中,表示v节点可以在排序中出现在它的所有前驱节点之后;
  3. 重复以上过程,直到所有的节点都被添加到顺序列表中,所得到的顺序列表即为一种拓扑排序的序列。

3. 示例说明

示例1

假设有下面这个图,我们可以通过拓扑排序来确定节点间的执行顺序关系:

    1 -> 3 -> 5
    |         ^
    v         |
    2 -------> 4

我们从任意一个节点开始进行DFS搜索,比如从节点1开始,具体步骤如下:

  1. 访问节点1,递归访问节点3;
  2. 访问节点3,递归访问节点5;
  3. 将节点5加入到顺序列表中;
  4. 回溯到节点3,访问节点4;
  5. 将节点4加入到顺序列表中;
  6. 回溯到节点1,访问节点2;
  7. 将节点2加入到顺序列表中。

至此,所有节点都已被添加到顺序列表中,所得到的拓扑排序序列是[1, 3, 2, 5, 4]。

示例2

假设现在有一个更复杂的图,其中包含一个环路:

    1 -> 2 -> 3
          ^    |
          |    v
          5 <- 4

这时候我们使用上述DFS搜索的方法不再适用,因为存在环路导致无法确定节点的相对顺序。但是,可以使用另外一种更加高效的拓扑排序算法——Kahn算法。

  • Kahn算法:

先建一个入度表inDegree,记录每个节点的入度,即有多少条边指向该节点。节点的入度为0表示该节点是入口节点。具体步骤如下:

  1. 从所有入口节点开始将其入队,入队的顺序不影响最后的输出结果;
  2. 当队列不空时,取出队首节点,将其邻接节点的入度减一,若邻接节点入度变为0,则将其入队;
  3. 重复步骤2直到队列为空,最后输出的序列即为一种可行的拓扑排序序列。

对于上面这个图,Kahn算法可以生成的任意一种可行拓扑排序序列是[1, 2, 3, 5, 4]。

代码实现:

下面给出Java实现拓扑排序的示例代码,包括使用DFS搜索和Kahn算法两种方式实现:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.List;

class Graph {
    private int V; // 图中顶点个数
    private List<Integer>[] adjList; // 记录图中边的信息

    public Graph(int V) {
        this.V = V;
        adjList = new ArrayList[V];
        for (int i = 0; i < V; i++) {
            adjList[i] = new ArrayList<>();
        }
    }

    // 添加有向边
    public void addEdge(int u, int v) {
        adjList[u].add(v);
    }

    // 拓扑排序(DFS) 
    public List<Integer> topoSortDFS() {
        List<Integer> order = new ArrayList<>();
        boolean[] visited = new boolean[V];

        // 从每个未访问的节点开始进行DFS搜索
        for (int i = 0; i < V; i++) {
            if (!visited[i]) {
                dfs(i, visited, order);
            }
        }

        return order;
    }

    // DFS搜索
    private void dfs(int u, boolean[] visited, List<Integer> order) {
        visited[u] = true;

        for (int v : adjList[u]) {
            if (!visited[v]) {
                dfs(v, visited, order);
            }
        }

        order.add(0, u); // 将节点u插入到顺序列表的最前面
    }

    // 拓扑排序(Kahn算法)
    public List<Integer> topoSortKahn() {
        List<Integer> order = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();
        int[] inDegree = new int[V];

        // 初始化入度表
        for (int u = 0; u < V; u++) {
            for (int v : adjList[u]) {
                inDegree[v]++;
            }
        }

        // 将所有入度为0的节点入队
        for (int i = 0; i < V; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }

        // 循环遍历入队的节点
        while (!queue.isEmpty()) {
            int u = queue.poll();
            order.add(u);

            // 将邻接节点的入度减1,并将入度为0的加入队列中
            for (int v : adjList[u]) {
                if (--inDegree[v] == 0) {
                    queue.offer(v);
                }
            }
        }

        return order;
    }
}

public class Main {
    public static void main(String[] args) {
        Graph graph = new Graph(6);

        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 3);
        graph.addEdge(2, 3);
        graph.addEdge(2, 4);
        graph.addEdge(3, 5);
        graph.addEdge(4, 5);

        System.out.println(graph.topoSortDFS()); // [0, 2, 4, 1, 3, 5]
        System.out.println(graph.topoSortKahn()); // [0, 2, 1, 4, 3, 5]
    }
}

注意:本示例代码仅提供参考,可能还存在人为错误和不足之处,具体问题应结合具体实现情况进行考虑。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java实现拓扑排序的示例代码 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • 小程序获取用户信息的两种方法详解(getUserProfile和头像昵称填写)

    小程序获取用户信息的两种方法包括getUserProfile和头像昵称填写。下面将详细讲解这两种方法的使用攻略和示例说明。 getUserProfile方法详解 什么是getUserProfile? getUserProfile是一种小程序的API,可以获取到用户的个人信息,包括昵称、头像、性别等。 如何使用getUserProfile? getUserPr…

    Java 2023年5月23日
    00
  • Android编程实现随机生成颜色的方法示例

    下面就为您详细讲解“Android编程实现随机生成颜色的方法示例”的完整攻略。 一、问题描述 在Android应用程序中,我们有时需要使用随机生成的颜色来装饰或突出显示某些元素,那么如何在Android编程中实现随机生成颜色的功能呢? 二、实现思路 在Android编程中,我们可以使用Java的Random类来生成随机颜色,并将其应用于要装饰或突出显示的元素…

    Java 2023年6月1日
    00
  • Spring源码系列(补充):详解ApplicationContext

    前言 在之前的文章中,我们已经对Spring源码中的一些核心概念进行了分析。由于篇幅限制,我们并没有详细解释ApplicationContext类所继承的父接口及其作用。因此,本文将单独为ApplicationContext进行详细说明,包括其继承的父接口及其作用。 ApplicationContext父接口 MessageSource 大家应该都比较熟悉M…

    Java 2023年4月22日
    00
  • java中方法递归的简单示例

    下面是讲解“java中方法递归的简单示例”的攻略。 什么是方法递归 方法递归是指在一个方法方法体内调用自身的过程。当方法被递归调用时,程序将重复执行该方法,直到满足退出递归调用的条件为止。 如何使用方法递归 为了使用方法递归,需要将方法定义为递归方法。递归方法通常具有以下特点: 递归方法必须调用自身。 递归方法必须具有一个退出递归的条件。 下面是两个简单的示…

    Java 2023年5月26日
    00
  • MyBatis中resultType和parameterType和resultMap使用总结

    下面我将为您介绍“MyBatis中resultType和parameterType和resultMap使用总结”的完整攻略: 1. resultType 在MyBatis中,resultType是指SQL语句执行后返回的结果集类型,该类型可以是任何Java类,包括:Java基本数据类型、JavaBean、Map等。 1.1 使用Java基本数据类型作为res…

    Java 2023年5月20日
    00
  • 详解Java数据库连接池

    详解Java数据库连接池 什么是数据库连接池? 数据库连接池是一种用于管理数据库连接的技术。通俗地说,它就像一个存放数据库连接的池子,程序从池子里取连接,用完之后再放回池子里,这样可以减少连接的创建和关闭的时间,在提高程序性能的同时也降低了数据库服务器的压力。 为什么要使用数据库连接池? 在使用数据库操作时,每次打开连接、关闭连接都需要一定的时间。长时间使用…

    Java 2023年5月19日
    00
  • Java单例模式的深入了解

    Java单例模式的深入了解 单例模式是一种常用的设计模式,它确保一个类只有一个实例,同时提供一种全局访问的方式。 在Java中,单例模式有多种实现方式,我们既可以使用经典的饿汉式实现,也可以使用懒汉式、静态内部类等方式实现。本篇攻略将为大家深入讲解Java单例模式的各种实现方式及其优缺点,同时提供一些示例说明。 一、饿汉式单例模式 饿汉式单例模式是最简单的一…

    Java 2023年5月19日
    00
  • 详解SpringBoot项目整合Vue做一个完整的用户注册功能

    我们来详细讲解一下“详解SpringBoot项目整合Vue做一个完整的用户注册功能”。这个攻略分两个部分:服务器端和客户端。我们分别来讲解。 服务器端 1. 创建SpringBoot项目 首先,我们需要在IDE中创建一个SpringBoot项目。可以使用Spring Initializr创建一个简单的Java Web项目,或者自己使用Maven创建。 2. …

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部