首页 > 代码库 > 算法笔记_023:拓扑排序(Java)

算法笔记_023:拓扑排序(Java)

目录

1 问题描述

2 解决方案

2.1 基于减治法实现

2.2 基于深度优先查找实现

 


1 问题描述

给定一个有向图,求取此图的拓扑排序序列。

那么,何为拓扑排序?

定义:将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面

 

 


2 解决方案

2.1 基于减治法实现

实现原理:不断地做这样一件事,在余下的有向图中求取一个源(source)(PS:定义入度为0的顶点为有向图的源),它是一个没有输入边的顶点,然后把它和所有从它出发的边都删除。(如果有多个这样的源,可以任意选择一个。如果这样的源不存在,算法停止,此时该问题无解),下面给出《算法设计与分析基础》第三版上一个配图:

 技术分享

 

具体代码如下:

package com.liuzhen.chapterFour;

import java.util.Stack;

public class TopologicalSorting {
    //方法1:基于减治法:寻找图中入度为0的顶点作为即将遍历的顶点,遍历完后,将此顶点从图中删除
    /*
     * 参数adjMatrix:给出图的邻接矩阵值
     * 参数source:给出图的每个顶点的入度值
     * 该函数功能:返回给出图的拓扑排序序列
     */
    public char[] getSourceSort(int[][] adjMatrix,int[] source){
        int len = source.length;          //给出图的顶点个数
        char[] result = new char[len];   //定义最终返回路径字符数组
        int count = 0;                  //用于计算当前遍历的顶点个数
        boolean judge = true;
        while(judge){
            for(int i = 0;i < source.length;i++){
                if(source[i] == 0){                 //当第i个顶点入度为0时,遍历该顶点
                    result[count++] = (char) (‘a‘+i);
                    source[i] = -1;                  //代表第i个顶点已被遍历
                    for(int j = 0;j < adjMatrix[0].length;j++){   //寻找第i个顶点的出度顶点
                        if(adjMatrix[i][j] == 1)
                            source[j] -= 1;          //第j个顶点的入度减1 
                    }
                }
            }
            if(count == len)
                judge = false;
        }
        return result;
    }
    /*
     * 参数adjMatrix:给出图的邻接矩阵值
     * 函数功能:返回给出图每个顶点的入度值
     */
    public int[] getSource(int[][] adjMatrix){
        int len = adjMatrix[0].length;
        int[] source = new int[len];
        for(int i = 0;i < len;i++){          
            //若邻接矩阵中第i列含有m个1,则在该列的节点就包含m个入度,即source[i] = m
            int count = 0;
            for(int j = 0;j < len;j++){
                if(adjMatrix[j][i] == 1)
                    count++;
            }
            source[i] = count;
        }
        return source;
    }
    
    
    public static void main(String[] args){
        TopologicalSorting test = new TopologicalSorting();
        int[][] adjMatrix = {{0,0,1,0,0},{0,0,1,0,0},{0,0,0,1,1},{0,0,0,0,1},{0,0,0,0,0}};
        int[] source = test.getSource(adjMatrix);
        System.out.println("给出图的所有节点(按照字母顺序排列)的入度值:");
        for(int i = 0;i < source.length;i++)
            System.out.print(source[i]+"\t");
        System.out.println();
        char[] result = test.getSourceSort(adjMatrix, source);
        
        System.out.println("给出图的拓扑排序结果:");
        for(int i = 0;i < result.length;i++)
            System.out.print(result[i]+"\t");
    }
}

 运行结果:

给出图的所有节点(按照字母顺序排列)的入度值:
0    0    2    1    2    
给出图的拓扑排序结果:
a    b    c    d    e    

 

2.2 基于深度优先查找实现

引用自网友博客中一段解释:

除了使用上面2.1中所示算法之外,还能够借助深度优先遍历来实现拓扑排序。这个时候需要使用到栈结构来记录拓扑排序的结果。

同样摘录一段维基百科上的伪码:

L Empty list that will contain the sorted nodes
S Set of all nodes with no outgoing edges
for each node n in S do
    visit(n) 
function visit(node n)
    if n has not been visited yet then
        mark n as visited
        for each node m with an edgefrom m to ndo
            visit(m)
        add n to L

DFS的实现更加简单直观,使用递归实现。利用DFS实现拓扑排序,实际上只需要添加一行代码,即上面伪码中的最后一行:add n to L。

需要注意的是,将顶点添加到结果List中的时机是在visit方法即将退出之时

 

此处重点在于理解:上面伪码中的最后一行:add n to L,对于这一行的理解重点在于对于递归算法执行顺序的理解,递归执行顺序的核心包括两点:1.先执行递归,后进行回溯;2.遵循栈的特性,先进后出。此处可以参考本人另外一篇博客:算法笔记_017:递归执行顺序的探讨(Java)

 

下面请看一个出自《算法设计与分析基础》第三版上一个配图:

 技术分享

具体代码如下:

package com.liuzhen.chapterFour;

import java.util.Stack;

public class TopologicalSorting {
    
    //方法2:基于深度优先查找发(DFS)获取拓扑排序
    public int count1 = 0;
    public Stack<Character> result1;
    /*
     * adjMatrix是待遍历图的邻接矩阵
     * value是待遍历图顶点用于是否被遍历的判断依据,0代表未遍历,非0代表已被遍历
     */
    public void dfs(int[][] adjMatrix,int[] value){
        result1 = new Stack<Character>();
        for(int i = 0;i < value.length;i++){
            if(value[i] == 0)        
                dfsVisit(adjMatrix,value,i);
        }            
    }
     /*
    * adjMatrix是待遍历图的邻接矩阵
    * value是待遍历图顶点用于是否被遍历的判断依据,0代表未遍历,非0代表已被遍历
    * number是当前正在遍历的顶点在邻接矩阵中的数组下标编号
    */
    public void dfsVisit(int[][] adjMatrix,int[] value,int number){
        value[number] = ++count1;               //把++count1赋值给当前正在遍历顶点判断值数组元素,变为非0,代表已被遍历
        for(int i = 0;i < value.length;i++){
            if(adjMatrix[number][i] == 1 && value[i] == 0)         //当,当前顶点的相邻有相邻顶点可行走且其为被遍历
                dfsVisit(adjMatrix,value,i);   //执行递归,行走第i个顶点
        }
        char temp = (char) (‘a‘ + number);
        result1.push(temp);
    }
    
    public static void main(String[] args){
        TopologicalSorting test = new TopologicalSorting();
        int[][] adjMatrix = {{0,0,1,0,0},{0,0,1,0,0},{0,0,0,1,1},{0,0,0,0,1},{0,0,0,0,0}};
   
        int[] value = http://www.mamicode.com/new int[5];
        test.dfs(adjMatrix, value);
        System.out.println();
        System.out.println("使用DFS方法得到拓扑排序序列的逆序:");
        System.out.println(test.result1);
        System.out.println("使用DFS方法得到拓扑排序序列:");
        while(!test.result1.empty())
            System.out.print(test.result1.pop()+"\t");
        
        
    }
}

运行结果:

使用DFS方法得到拓扑排序序列的逆序:
[e, d, c, a, b]
使用DFS方法得到拓扑排序序列:
b    a    c    d    e    

 

 

参考资料:

       1.拓扑排序的原理及其实现

算法笔记_023:拓扑排序(Java)