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日

相关文章

  • 详解Spring data 定义默认时间与日期的实例

    关于详解 Spring Data 定义默认时间与日期的实例的攻略,以下是完整的步骤: 第一步:在 Entity 类中定义默认时间和日期 在 Spring Data 中,我们可以通过定义一个 BaseEntity 来设置默认的时间和日期。在 BaseEntity 中,我们定义了 @CreatedDate 和 @LastModifiedDate 注解,可以用于更…

    Java 2023年6月16日
    00
  • 浅谈Java基准性能测试之JMH

    浅谈Java基准性能测试之JMH 什么是基准性能测试? 基准性能测试是一种通过对软件或硬件系统进行压力测试来衡量其性能水平的方法。通常,在执行基准性能测试之前,我们需要明确目标,比如检查系统的吞吐量、响应时间和负载下的资源消耗等。 为什么要进行基准性能测试? 在软件开发过程中,我们需要不断地优化代码,以期提高系统的性能和可靠性。而基准性能测试为我们提供了一种…

    Java 2023年5月26日
    00
  • 高效的java版排列组合算法

    高效的Java版排列组合算法 前言 排列组合是数学中的一种常见问题,例如给定数列[1,2,3],对其进行排列组合可以得到以下六种可能: 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 在Java中,我们可以使用递归和循环等方式来实现排列组合,但是如果数列过长,将会十分耗时,因此我们需要一种高效的实现方式。 算法基础 排列 排列的基本概…

    Java 2023年5月19日
    00
  • Java如何实现图片裁剪预览功能

    下面是Java实现图片裁剪预览功能的完整攻略。 简介 图片裁剪和预览功能是很多网站或APP必备的功能之一,其中预览功能可以帮助用户选择需要裁剪的具体区域,增加用户的交互体验。而图片裁剪是在预览的基础上对图片进行裁剪,并最终将裁剪后的图片保存到数据库或文件系统中。 Java如何实现图片裁剪预览功能?下面我们将通过两个示例分别介绍基于Java的后端技术和前端技术…

    Java 2023年6月15日
    00
  • Java OOM原因以及解决方案

    Java OOM原因以及解决方案 在Java应用程序运行的过程中,由于程序中申请的内存空间超过了JVM所能提供的内存空间,就会出现OOM(Out of Memory)错误。下面我们将详细讨论OOM的原因、解决方案以及示例说明。 OOM原因 内存泄漏 当一个对象不再被程序使用时,它所占用的内存空间应该被JVM的垃圾回收机制清理掉。但是,如果程序中存在内存泄漏,…

    Java 2023年5月27日
    00
  • Java String类详解_动力节点Java学院整理

    Java String类详解 在Java中,String类是一个非常重要的类。本篇文章将对Java String类进行详细的讲解,包括String类的定义、String类的常用方法、String类与其他数据类型的转换以及String类的不可变性等。 String类的定义 在Java中,String类是一个表示字符串的类。每个字符串都是由多个字符组成的字符序列…

    Java 2023年5月26日
    00
  • 详解Java8函数式编程之收集器的应用

    详解Java8函数式编程之收集器的应用 概述 Java8引入了函数式接口和lambda表达式,同时也增强了集合框架的功能,新增了Stream API来优雅地解决集合的数据处理问题。Stream可以看作是一个高级版本的Iterator,它能够得到更好的性能,更加简洁明了的代码。本文主要介绍Java8中Stream API的一项重要功能,收集器的应用。 收集器 …

    Java 2023年5月26日
    00
  • 常见的Java垃圾回收器有哪些?

    我们来详细讲解一下“常见的Java垃圾回收器有哪些?”这个问题的完整使用攻略。 问题背景 Java是一种垃圾自动回收语言,它通过垃圾回收器来自动管理内存。Java垃圾回收器根据内存使用情况,周期性地清理没有被引用的对象。Java垃圾回收器有多种不同的类型,每种类型都有其自身的特点和优劣势。 常见的Java垃圾回收器 Java垃圾回收器主要分为以下几种: Se…

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