数据结构与算法(java):图的拓扑排序

图的拓扑排序

AOV网

在一个表示工程(例如拍戏、教学安排)的有向图中,用顶点表示活动(每个阶段该做的事情),用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网叫做AOV网(Activity On vertex NetWord)。AOV网中的弧表示活动之间存在某种制约关系。并且AOV网中不能出现回路。
举个例子:拍戏这个工程,必须先把剧本给定好,然后再开始挑演员,选场地,进行拍摄,一步一步来,不能在拍摄过程中场地还没安排好就开始拍摄,也就是说拍摄的前提必须时场地给选好了。

拓扑序列

设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列V1,V2,V…满足若从顶点Vi到Vj有一条路径,则在顶点序列中顶点Vi比在顶点Vj之前。则经这样一个序列成为拓扑序列。

例如:
在这里插入图片描述
结点1必须在结点2、3之前
结点2必须在结点3、4之前
结点3必须在结点4、5之前
结点4必须在结点5之前
则一个满足条件的拓扑序列为[1, 2, 3, 4, 5]
而这样的序列可能不止一条。

拓扑排序

拓扑排序就是对一个有向图构造拓扑序列的过程。

在任何有向无环图中,拓扑排序满足这样一种条件:对于有向图中任意两个顶点u和v,若存在一条有向边从u指向v,则在拓扑排序中u一定出现在v前面。

拓扑排序的前提

必须是一个有向无环图(directed acyclic graph,DAG)

为什么说必须是有向无环图呢,看如下例子
在这里插入图片描述
假设想从A开始,而根据拓扑排序原理,C在A的前面,那应该从C开始,而不是A,同样的B又排在C的前面,那也应该是从B开始而不是A或C…这样就分不清到底是从哪个点开始的,也就导致分不清顶点优先级。

两种方式实现拓扑排序

  • 邻接表+深度优先搜索(DFS)实现拓扑排序

要想实现深度优先,那么就必定用到递归,而要实现拓扑排序就要进行有向图是否有环的判断,并且要对每个结点进行递归判断其邻接表是否成环。
为什么要对每个结点进行成环的判断呢?如下:
在这里插入图片描述
这是一幅不连通的图。如果我们只对一个结点进行判断,例如对D进行判断,那D和E构成的图确实不成环,但实际上整幅图的A、B、C结点却是成环的,这就导致与实际情况不符,所以要对每个结点进行递归判断是否成环

  • 代码实现
    //拓扑排序---DFS实现
    //返回值是集合
    public ArrayList<String> topSortByDFS() {
        //定义一个集合类型数组,其下元素均为集合
        List[] lists = new List[numOfVertex];

        //初始化集合数组中的所有集合
        for (int i = 0; i < numOfVertex; i++) {
            lists[i] = new ArrayList<Integer>();
        }

        //获取每个顶点的邻接表,将邻接表存到整数类型的集合中,各顶点的邻接表代表该顶点将来会访问的点
        for (int i = 0; i < numOfVertex; i++) {
            EdgeNode eNode = headVertex[i].firstEdge;
            while (eNode != null) {
                lists[i].add(eNode.EdgeData);
                eNode = eNode.nextEdge;
            }
        }

        //定义一个栈用来存储被访问了的顶点
        Stack<String> stack = new Stack<>();

        //用于标记顶点是否被访问过
        boolean[] globalMarked = new boolean[numOfVertex];
        //用于标记各个顶点对应的邻接表中的所有元素是否被访问过
        boolean[] localMarked = new boolean[numOfVertex];

        //如果遍历所有顶点后存在环,那么返回空集合
        for (int i = 0; i < numOfVertex; i++) {
            if(hasCircle(globalMarked,localMarked,lists,i,stack)){
                return new ArrayList<>();
            }
        }
        //如果遍历完所有顶点后发现并不存在环,那么将栈中存储的元素弹出并存放到集合中
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < numOfVertex; i++) {
            list.add(stack.pop());
        }
        return list;
    }

    //判断是否构成环
    private boolean hasCircle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] lists, int index, Stack<String> stack) {

		//如果邻接表中的元素被访问过,那么就存在环
        if(localMarked[index]){
            return true;
        }

		//true表示访问过,如果索引为index的点被访问过,则回溯
        if(globalMarked[index]){
            return false;
        }

        globalMarked[index] = true;
        localMarked[index] = true;

        //lists[index]集合中存储的是顶点的邻接表的集合
        for(int nextNode : lists[index]){
            if(hasCircle(globalMarked,localMarked,lists,nextNode,stack)){
                return true;
            }
        }

		//对邻接表中元素判断完后都要重新赋值为false,方便对下一个顶点的邻接表进行判断
        localMarked[index] = false;
        stack.push(getNameOfVertexByIndex(index));
        return false;
    }

PS:代码参考


  • 邻接表+广度优先搜索(BFS)实现拓扑排序

为了说明如何得到一个有向无环图的拓扑排序,我们首先需要了解有向图结点的入度(indegree)和出度(outdegree)的概念。
假设有向图中不存在环,也就是不存在起点和重点为统一结点的有向边。

入度: 设有向图中有一结点v,其入度即为当前所有从其他结点出发,终点为v的的边的数目。也就是所有指向v的有向边的数目。
出度: 设有向图中有一结点v,其出度即为当前所有起点为v,指向其他结点的边的数目。也就是所有由v发出的边的数目。

  • 主要思路
    (1)选择一个入度为0的顶点并输出。
    (2)从AOV网中删除此入度为0的顶点及其所有出边。

无法遍历完所有的结点,则意味着当前的图不是有向无环图,存在环,也就不存在拓扑排序。

  • 代码实现
    //拓扑排序
    public ArrayList<String> topSortByBFS(){
        //该数组存储各个顶点的入度值
        int[] inDegree = new int[numOfVertex];

        EdgeNode v;

        //初始化所有入度值;headVertex是顶点结点
        for(VertexNode vertex : headVertex){
            v = vertex.firstEdge;
            while(v!=null){
                inDegree[v.EdgeData]++;
                v = v.nextEdge;
            }
        }

        //定义一个队列,存储入度为0的结点数据值
        Deque<String> deque = new ArrayDeque<>();

        //定义一个集合,存储出栈的值
        ArrayList<String> list = new ArrayList<>();

        //将所有入度为0的结点入队
        for(int i = 0; i<numOfVertex; i++){
            if(inDegree[i] == 0){
                deque.offer(headVertex[i].vertexData);
            }
        }

        //当队列中存在元素时
        while(!deque.isEmpty()){
            //让最先入队的出队
            String curr = deque.poll();
            //出队后的元素用集合接收
            list.add(curr);
            //根据结点数据域获取入队为0的顶点的下标
            int index = getIndexByString(curr);
            //利用下标获取对应顶点
            VertexNode vertex = headVertex[index];

            EdgeNode e = vertex.firstEdge;
            //遍历该顶点的邻接表
            while(e != null){
               int k = e.EdgeData;
               //每次父节点弹出队列后,其直接子节点的入度减少
               inDegree[k]--;
               if(inDegree[k] == 0){
                   deque.offer(headVertex[k].vertexData);
               }
               e = e.nextEdge;
            }
        }

        //如果list集合元素个数等于顶点个数,表示拓扑排序完成,没有环存在,反之不等于时表示图中存在环,则返回空集合
        return list.size() == numOfVertex ? list : new ArrayList<>();
    }
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java拓扑排序可以通过使用深度优先搜索算法来实现。步骤如下:1.选择一个未被访问的顶点作为当前顶点。2.标记当前顶点为已访问。3.确定当前顶点的所有未访问邻接顶点。4.对每个未被访问的邻接顶点,递归地执行以上步骤。5.将当前顶点添加到答案中。 ### 回答2: Java拓扑排序是一种用于解决有向中节点依赖关系排序问题的算法。下面是一个基于深度优先搜索(DFS)的Java拓扑排序实现: 1. 首先,定义一个Java类用于表示有向中的节点。该类可以包含节点的标识符和一个包含该节点的所有后继节点的列表。 ```java class Node { int id; List<Node> successors; public Node(int id) { this.id = id; this.successors = new ArrayList<>(); } } ``` 2. 接下来,定义一个Java方法用于实现深度优先搜索算法。该方法接受一个有向的起始节点作为参数,并使用递归方式进行深度优先搜索。 ```java void dfs(Node node, Stack<Node> stack, Set<Node> visited) { visited.add(node); for (Node successor : node.successors) { if (!visited.contains(successor)) { dfs(successor, stack, visited); } } stack.push(node); } ``` 3. 最后,定义一个Java方法用于实现拓扑排序算法。该方法接受一个有向的起始节点列表作为参数,并返回一个按照拓扑排序顺序排列的节点列表。 ```java List<Node> topologicalSort(List<Node> nodes) { List<Node> sortedNodes = new ArrayList<>(); Stack<Node> stack = new Stack<>(); Set<Node> visited = new HashSet<>(); for (Node node : nodes) { if (!visited.contains(node)) { dfs(node, stack, visited); } } while (!stack.isEmpty()) { sortedNodes.add(stack.pop()); } return sortedNodes; } ``` 可以通过调用`topologicalSort`方法,并传入有向的起始节点列表,来获取拓扑排序后的节点列表。 注意:上述代码中未包含对有向是否存在环的检测。在实际应用中,可能需要添加环检测来确保有向可以进行拓扑排序。 ### 回答3: Java中的拓扑排序可以通过深度优先搜索(DFS)来实现。拓扑排序通常用于有向无环(DAG)中,它能够对中的节点进行一种排序,使得对于任意一对有向边(u,v),在排序中的节点u总是在节点v之前。 首先,我们需要创建一个表示数据结构。可以使用邻接列表或邻接矩阵来表示有向,其中每个节点作为索引,存储其所有的出边或入边。 接下来,我们需要实现一个DFS函数,该函数将从任意节点开始遍历,并在遍历过程中记录节点的访问状态以及节点的访问顺序。 在DFS函数中,我们首先将当前节点标记为已访问,并迭代遍历当前节点的所有邻接节点。对于每个邻接节点,如果其尚未访问,则递归调用DFS函数并继续遍历该节点。经过递归调用后,我们可以确定当前节点的访问顺序为它的邻接节点的访问顺序之后。 最后,我们将递归调用DFS函数遍历所有未访问的节点。在遍历的过程中,我们可以将访问顺序按照逆序记录下来,以得到拓扑排序的结果。 以下是基于DFS实现拓扑排序Java代码示例: ```java import java.util.*; class Graph { private int numCourses; private List<List<Integer>> adjList; private boolean[] visited; private Stack<Integer> stack; public Graph(int numCourses) { this.numCourses = numCourses; this.adjList = new ArrayList<>(); for (int i = 0; i < numCourses; i++) { adjList.add(new ArrayList<>()); } this.visited = new boolean[numCourses]; this.stack = new Stack<>(); } public void addEdge(int u, int v) { adjList.get(u).add(v); } public int[] topologicalSort() { for (int i = 0; i < numCourses; i++) { if (!visited[i]) { dfs(i); } } int[] result = new int[numCourses]; int i = 0; while (!stack.isEmpty()) { result[i++] = stack.pop(); } return result; } private void dfs(int node) { visited[node] = true; for (int neighbor : adjList.get(node)) { if (!visited[neighbor]) { dfs(neighbor); } } stack.push(node); } } public class Main { public static void main(String[] args) { int numCourses = 6; Graph graph = new Graph(numCourses); graph.addEdge(2, 3); graph.addEdge(3, 1); graph.addEdge(4, 0); graph.addEdge(4, 1); graph.addEdge(5, 0); graph.addEdge(5, 2); int[] result = graph.topologicalSort(); System.out.println("Topological order:"); for (int node : result) { System.out.print(node + " "); } } } ``` 在上述代码中,我们首先创建了一个有向表示有6个节点的。然后添加了一些有向边。最后调用topologicalSort函数,得到了拓扑排序的结果并输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值