拓扑排序的两种实现模板

拓扑排序

拓扑排序是,从题目中知道 事情是存在先后顺序被执行,则可以想到

  • 构建一副图
  • 对图中的顶点进行拓扑排序 ,同时需要判断图中是否存在环

拓扑排序存在两种实现方式

  • 深度优先搜索
  • 广度优先搜索

深度优先拓扑排序的实现

import java.util.List;

/**
 * 拓扑排序 深度优先搜索模板
 * 深度优先搜索是一种「逆向思维」,最先被收集节点是在拓扑排序中最后面的节点
 */
public class OrderBfs {
    /**
     * 构造有向图对象 优先级高的顶点指向 优先级低的
     */

    List< List< Integer > > edgs;
    /**
     * 记录顶点的搜索情况 ,每个顶点在搜索的过程中 有三种状态
     * 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
     */
    int[] marked;
    /**
     * 标记图中是否存在环
     */
    boolean hasCycle;
    /**
     * 收集拓扑排序的结果
     */
    int[] result;
    /**
     * 每次都是遍历到最底层,然后对应收集顶点
     */
    int index;


    public int[] findOrder (int numCourses, int[][] prerequisites) {
        //从题意知道是需要用拓扑排序,先构建一副图

        //第一步 初始化存放顶点的图
        edgs = new ArrayList<>();

        //第二步 每个顶点的邻接表也初始化
        for (int i = 0; i < numCourses; i++) {
            edgs.add(new ArrayList<>());
        }

        //根据题意顶点的邻接表进行赋值
        //这里注意,要让优先级高的顶点 指向优先级点低的
        for (int[] arr : prerequisites) {
            edgs.get(arr[1]).add(arr[0]);
        }

        //初始化顶点搜索情况标记数组
        marked = new int[numCourses];

        //初始化 标记是否存在环 最开始是没有
        hasCycle = false;

        //初始化 结果数组
        result = new int[numCourses];

        //因为每次都是从深度搜索到底层,顶点是优先加入的数组的话 则 index = 0
        index = numCourses - 1;


        //遍历顶点
        for (int i = 0; i < numCourses; i++) {
            //当已经出现环了,则不可能存在拓扑排序结果
            if (hasCycle) {
                return new int[0];
            } else {
                //从未搜索过的顶点 开始深度搜索
                if (marked[i] == 0) {
                    dfs(i);
                }
            }

        }
        //如果没有环,那么就有拓扑排序
        return result;
    }

    /**
     * 从顶点i 开始进行深度搜索
     *
     * @param i 顶点i
     */
    private void dfs (int i) {
        //将顶点标记为「搜索中」
        marked[i] = 1;
        //遍历顶点i 的邻接表
        for (int v : edgs.get(i)) {
            /**
             * 对于当前搜索的顶点的 ,存在三种情况
             * 第一种 情况,当前的顶点未搜索过
             */
            if (marked[v] == 0) {
                //对这个顶点进行深度搜索
                dfs(v);
                //经过一轮搜索,需要判断是否存在环
                if (hasCycle) {
                    //存在环 直接中止
                    return;
                }
                /**
                 * 如果当前要处理顶点 已经处于正在搜索的过程
                 * 说明这个顶点在这次深度搜索中 反复被搜索,形成了环
                 */
            } else if (marked[v] == 1) {

                //标记出现了环
                hasCycle = true;
                //搜索中止
                return;
                /**
                 * 第三种情况,这个顶点已经是搜索完了,因此不用处理
                 */
            } else {
                //不做处理
            }

        }

        //当对顶点的邻接表进行深度搜索完后 ,标记该顶点已经被搜索过了
        marked[i] = 2;

        //拓扑排序数组中收集该顶点
        result[index--] = i;
    }
}
广度优先搜索实现拓扑排序

使用一个队列来进行广度优先搜索,开始时,所有入度为 0 的节点都被放入队列中,它们就是可以作为拓扑排序最前面的节点,并且它们之间的相对顺序是无关紧要的

1、在广度优先搜索的每一步中,我们取出队首的节点 u:

2、我们将 u 放入答案中;

3、我们移除 u 的所有出边,也就是将 u 的所有相邻节点的入度减少 11。如果某个相邻节点 vv 的入度变为 00,那么我们就将 v 放入队列中

广度搜索 每次都是加入到入度为0 的顶点,在搜索完 判断我们收集到的顶点是否 和 图中顶点相等,相等说明存在环

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 通过广度优先搜索实现拓扑排序
 * 这是一种正向思维,顺序地生成拓扑排序
 */
public class OrderBFS {
    /**
     * 构造有向图对象 优先级高的顶点指向 优先级低的
     */

    List< List< Integer > > edgs;
    /**
     * 记录下每个顶点的入度
     */

    int[] inDegree;
    /**
     * 收集拓扑排序的结果
     */
    int[] result;
    /**
     * result 收集的下标
     */
    int index;

    public int[] findOrder (int numCourses, int[][] prerequisites) {
        //从题意知道是需要用拓扑排序,先构建一副图

        //初始化记录顶点入度的数组
        inDegree = new int[numCourses];

        // 初始化存放顶点的图
        edgs = new ArrayList<>();

        // 每个顶点的邻接表也初始化
        for (int i = 0; i < numCourses; i++) {
            edgs.add(new ArrayList<>());
        }

        //根据题意顶点的邻接表进行赋值
        //这里注意,要让优先级高的顶点 指向优先级点低的
        for (int[] arr : prerequisites) {
            edgs.get(arr[1]).add(arr[0]);
            //每次被指向的顶点 对应的入度加1
            ++inDegree[arr[0]];
        }

        //初始化收集拓扑排序结果的数组
        result = new int[numCourses];

        //初始化收集数组开始收集 的索引
        index = 0;

        //创建队列,队列中存放的是 入度为0 的顶点
        Queue< Integer > queue = new LinkedList<>();

        //先将入度为0的顶点入队
        for (int v = 0; v < inDegree.length; v++) {
            if (inDegree[v] == 0) {
                queue.add(v);
            }
        }
//        下面这种写法是错误 ,因为加入队列 是数组顶点为0索引 而不是 对应的值
//        for (int v : inDegree) {
//            if (v == 0) {
//                queue.add(v); 相当于 queue.add(0);
//            }
//        }

        //开始弹出顶点,进行操作
        while (!queue.isEmpty()) {
            //从对首弹出一个顶点
            int u = queue.poll();
            //收集顶点
            result[index++] = u;
            //遍历u的邻接表,将u顶点指向的边消除,也就是u指向的顶点,入度全部减一
            for (int v : edgs.get(u)) {
                //v顶点的入度减1
                --inDegree[v];
                //判断消除u -> v边后,v顶点的入度是否为0 ,为0则入队
                if (inDegree[v] == 0) {
                    queue.add(v);
                }
            }
        }
        //最后通过判断我们收集到入度为0 的顶点数量 是否等于图的顶点数量
        //如果不相等说明 存在环,无法获得拓扑排序结果
        if (index != numCourses) {
            return new int[0];
        }
        return result;


    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值