现实生活中的的顺序并不是简单的大小比较,而是一种依赖关系。比如我们穿衣服必须先穿内裤再穿外裤,先穿袜子再穿鞋。再比如,编译文件的时候先编译没有依赖的文件,再编译依赖文件。

那么对于这样一种关系我们可以使用 DAG(有向无环图)表示。而拓扑排序(Topological sorting)要解决的问题就是如何给一个有向无环图的所有节点排序。

为了更好的实现拓扑排序,定义接口如下:

package cn.lishiyuan.algorithm.graph;  
  
import java.util.List;  
  
/**  
 * 拓扑排序  
 */  
public interface TopoSort<V> {  
    List<V> sort();  
}

Kahn 算法

Kahn 算法实际上用的是贪心算法思想。

处理思路是,首先获取到没有入度的顶点,添加到输出。然后再检查上个阶段输出的顶点的出度连接的顶点,如果顶点没有入度了,就将该顶点添加到输出。依此类推,将所有的顶点输出为止。

实现基于[[图1:图的表示与存储]] 里面的邻接表实现

package cn.lishiyuan.algorithm.graph;  
  
import cn.lishiyuan.algorithm.queue.LeeQueue;  
import cn.lishiyuan.algorithm.queue.LinkedQueue;  
  
import java.util.*;  
  
/**  
 * 邻接表  
 */  
public class AdjacencyList<V> implements Graph<V>,TopoSort<V> {  
    // 顶点  
    private Set<V> vertices;  
    // 边  
    private Map<V,Map<V,Integer>> edges;  
  
    private GraphType type;  
  
  
    @Override  
    public List<V> sort() {  
        // Kahn 算法  
        // 构建逆邻接表  
        Map<V,Map<V,Integer>> inverseEdges = new HashMap<>();  
        for (Map.Entry<V, Map<V, Integer>> entry : edges.entrySet()) {  
            V key = entry.getKey();  
            Map<V,Integer> edgeInfo = entry.getValue();  
            for (Map.Entry<V, Integer> value : edgeInfo.entrySet()) {  
                Map<V, Integer> integerMap = inverseEdges.getOrDefault(value.getKey(), new HashMap<>());  
                integerMap.put(key,value.getValue());  
                inverseEdges.put(value.getKey(),integerMap);  
            }  
        }  
  
        List<V> list = new ArrayList<>();  
  
        LeeQueue<V> queue = new LinkedQueue<>();  
        // 将一开始就没有入度元素加入到队列  
        for (V vertex : vertices) {  
            Map<V, Integer> vIntegerMap = inverseEdges.get(vertex);  
            // 没有入度  
            if(vIntegerMap == null || vIntegerMap.isEmpty()){  
                queue.enqueue(vertex);  
            }  
        }  
  
        while (!queue.isEmpty()){  
            V vertex = queue.dequeue();  
            list.add(vertex);  
  
            inverseEdges.remove(vertex);  
            // 获取出度  
            Map<V, Integer> map = edges.get(vertex);  
            if (map != null){  
                for (V v : map.keySet()) {  
                    // 删除对应顶点的入度  
                    Map<V, Integer> edgesOrDefault = inverseEdges.getOrDefault(v, new HashMap<>());  
                    edgesOrDefault.remove(vertex);  
                    // 没有入度了  
                    if (edgesOrDefault.isEmpty()){  
                        queue.enqueue(v);  
                    }  
                }  
            }  
  
        }  
  
        return list;  
    }  
  
}

DFS

深度优先搜索思想也可以用作处理拓扑排序。

处理思路是如果顶点没有入度则添加到输出,检查该顶点的的出度连接的顶点,如果没有入度,则输出,再检查该顶点的出度连接的顶点,一样的检查输出。依此类推直到将所有的顶点按照顺序全部输出。

实现基于[[图1:图的表示与存储]] 里面的邻接矩阵实现。

package cn.lishiyuan.algorithm.graph;  
  
import cn.lishiyuan.algorithm.stack.LeeStack;  
import cn.lishiyuan.algorithm.stack.LinkedStack;  
  
import java.util.*;  
  
/**  
 * 邻接矩阵  
 *  
 */public class AdjacencyMatrix<V> implements Graph<V>,TopoSort<V> {  
  
    /**  
     * 顶点,可以使用 Map 存储元素与index的关系  
     */  
    private V[] vertices;  
  
    /**  
     * 边  
     */  
    private int[][] edges;  
  
    private GraphType type;  
    
  
    @Override  
    public List<V> sort() {  
        // dfs 算法,深度优先  
        LeeStack<Integer> stack = new LinkedStack<>();  
        boolean[] visited = new boolean[vertices.length];  
        Arrays.fill(visited, false);  
  
        // 入度为0的放入stack 出度[from][to]  
        for (int i = 0; i < vertices.length; i++) {  
            boolean flag = false;  
            for (int j = 0; j < vertices.length; j++) {  
                if(edges[j][i] > 0){  
                    flag = true;  
                    break;                }  
            }  
            if(!flag){  
                visited[i] = true;  
                stack.push(i);  
            }  
        }  
  
        List<V> result = new ArrayList<>();  
        while (!stack.isEmpty()){  
            Integer index = stack.pop();  
            result.add(vertices[index]);  
            // 处理完该节点后  
            // 基础在少了该节点的入度的情况下 入度为0的index  
            for (int i = 0; i < vertices.length; i++) {  
                boolean flag = false;  
                for(int j=0;j<vertices.length;j++){  
                    if( index != j && edges[j][i] > 0){  
                        flag = true;  
                        break;                    }  
                }  
                if(!flag && !visited[i]){  
                    visited[i] = true;  
                    stack.push(i);  
                }  
            }  
        }  
  
        return result;  
    }  
}

标签: 算法基础

添加新评论