拓扑排序两种实现方式

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

拓扑排序能解决的问题

在一个项目中会有很多源代码文件。编译器在编译代码的时候需要按照依赖关系,依次编译每个源文件。例如A.java依赖B.java,那就需要先编译B.java,再编译A.java。要想完整编译整个项目就需要确定一个全局的编译顺序。确定这样一个全局的编译顺序就用到拓扑排序。

拓扑排序就是解决有向无环图的图中所有顶点的满足依赖条件的顶点顺序

解决思路

可以将每个源文件看做一个顶点,源文件和源文件之间的依赖关系看做一条边。图的基本结构如下。


public class Graph {
  private int v; // 顶点的个数
  private LinkedList<Integer> adj[]; // 邻接表

  public Graph(int v) {
    this.v = v;
    adj = new LinkedList[v];
    for (int i=0; i<v; ++i) {
      adj[i] = new LinkedList<>();
    }
  }

  public void addEdge(int s, int t) { // s先于t,边s->t
    adj[s].add(t);
  }
}

排序算法有两种方式BFS和DFS。

BFS遍历

BFS遍历,也称为Khan算法。在构建图的时候如果A.java依赖B.java,那就从B到A有一条边:B->A。那入度为0的点就是最先编译的。 找到入度为0的顶点X,将其输出到拓扑排序结果列表中,然后删除以X为起点的所有的边。继续查找入度为0的顶点,添加到结果列表中。

	public List<Integer> topSortByKahn(){
        int[] inDegree = new int[v];
        for(int i = 0; i< adjacency.length; i++){
            for(Edge edge : adjacency[i]){
                inDegree[edge.tid] ++;
            }
        }
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0;i<inDegree.length;i++){
            if(inDegree[i] == 0){
                queue.add(i);
            }
        }

        List<Integer> path = new ArrayList<>();
        while(! queue.isEmpty()){
            int node = queue.poll();
            path.add(node);
            for(Edge edge : adjacency[node]){
                inDegree[edge.tid]--;
                if(inDegree[edge.tid] == 0){
                    queue.offer(edge.tid);
                }
            }
        }
        return path;
    }

DFS遍历

按照深度优先搜索的方式,遍历每个顶点。假如有条路径是:A->B->C->E、A->D->C。
DFS的时候,如果先走的是第一条要先访问了C、E才会访问D->C这条路线。这样的话,就不能找到C什么时候可以执行。所以需要将邻接矩阵转为逆邻接矩阵。
E->C->B->A、C->D->A。
说明A先执行了才能执行B,B、D先执行才能执行C,C执行了才能执行 E。这个顺序符合要求。

在DFS处理环节,把一个顶点所依赖的所有节点先输出,再输出本节点。

 	public List<Integer> topSortByDFS(){
        LinkedList<Integer>[] inverseAdg = new LinkedList[this.v];
        for(int i = 0; i< adjacency.length; i++){
            inverseAdg[i] = new LinkedList<>();
        }
        for(int i = 0; i< adjacency.length; i++){
            for(Edge edge : adjacency[i]){
                inverseAdg[edge.tid].add(edge.sid);
            }
        }
        boolean[] visited = new boolean[v];
        List<Integer> path = new ArrayList<>();
        for(int i=0;i<this.v;i++){
            if(visited[i] == false){
                dfs(i,inverseAdg,visited,path);
            }
        }
        return path;
    }

    private void dfs(int sid, LinkedList<Integer>[] inverseAdg, boolean[] visited,List<Integer> path) {
        visited[sid] = true;
        for(int tid   : inverseAdg[sid]){
            if(visited[tid] == false){
                dfs(tid,inverseAdg,visited,path);
            }
        }
        path.add(sid);
    }

完整代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值