拓扑排序

拓扑排序

在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pu4d0OCG-1629935023635)(images/image18.png)]

此时如果某个同学要完成这些任务,就需要指定出一个任务方案,我们只需要对图中的顶点进行排序,让它转换为一个线性序列,就可以解决问题,这时就需要用到一种叫拓扑排序的算法。

拓扑排序:

给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素,此时就可以明确的表示出每个顶点的优先级。下列是一副拓扑排序后的示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbHks6YD-1629935023637)(images/image16.png)]

1. 检测有向图中的环

如果学习x课程前必须先学习y课程,学习y课程前必须先学习z课程,学习z课程前必须先学习x课程,那么一定是有问题了,我们就没有办法学习了,因为这三个条件没有办法同时满足。其实这三门课程x、y、z的条件组成了一个环:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvcmzCbn-1629935023638)(images/image17.png)]

因此,如果我们要使用拓扑排序解决优先级问题,首先得保证图中没有环的存在。

1.1 检测有向环的API设计

类名DirectedCycle
构造方法DirectedCycle(Digraph G):创建一个检测环对象,检测图G中是否有环
成员方法1.private void dfs(Digraph G,int v):基于深度优先搜索,检测图G中是否有环
2.public boolean hasCycle():判断图中是否有环
成员变量1.private boolean[] marked: 索引代表顶点,值表示当前顶点是否已经被搜索
2.private boolean hasCycle: 记录图中是否有环
3.private boolean[] onStack:索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上

1.2 检测有向环实现

在API中添加了onStack[] 布尔数组,索引为图的顶点,当我们深度搜索时:

  1. 在如果当前顶点正在搜索,则把对应的onStack数组中的值改为true,标识进栈;
  2. 如果当前顶点搜索完毕,则把对应的onStack数组中的值改为false,标识出栈;
  3. 如果即将要搜索某个顶点,但该顶点已经在栈中,则图中有环;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mwaIwOlp-1629935023641)(images/image19.png)]

代码:

/**
 * 检测有向图中的环
 */
public class DirectedCycle {
    // 索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    // 记录图中是否有环
    private boolean hasCycle;
    // 索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上
    private boolean[] onStack;

    // 创建一个检测环对象,检测图G中是否有环
    public DirectedCycle(Digraph G) {
        // 初始化marked数组
        marked = new boolean[G.V()];
        // 初始化onStack数组
        onStack = new boolean[G.V()];
        // 默认没有环
        this.hasCycle = false;
        // 检测是否有环,遍历每一个顶点
        for (int v = 0; v < G.V(); v++) {
            // 如果当前顶点没有搜索过,则搜索
            if (!marked[v]) {
                dfs(G, v);
            }
        }
    }

    // 基于深度优先搜索,检测图G中是否有环
    private void dfs(Digraph G, int v) {
        // 标识当前顶点已被搜索
        marked[v] = true;
        // 让当前顶点进栈
        onStack[v] = true;
        // 遍历v顶点的邻接表
        for (Integer w : G.adj(v)) {
            // 如果当前顶点已进栈(表示当前条路径已有该顶点),表明图中有环,修改hasCycle标识,结束循环
            if (onStack[w]) {
                hasCycle = true;
                return;
            }
            // 如果当前顶点w没有被搜索过,则递归搜索与w顶点相通的其他顶点
            if (!marked[w]) {
                dfs(G, w);
            }
        }
        // 当前顶点已经搜索完毕,让当前顶点出栈
        onStack[v] = false;
    }

    // 判断有向图中是否有环
    public boolean hasCycle() {
        return hasCycle;
    }
}

2. 基于深度优先的顶点排序

如果要把图中的顶点生成线性序列其实是一件非常简单的事,使用了多次深度优先搜索之后,我们会发现其实深度优先搜索有一个特点,那就是在一个连通子图上,每个顶点只会被搜索一次,如果我们能在深度优先搜索的基础上,添加一行代码,只需要将搜索的顶点放入到线性序列的数据结构中,我们就能完成这件事。

2.1 顶点排序API设计

类名DepthFirstOrder
构造方法DepthFirstOrder(Digraph G):创建一个顶点排序对象,生成顶点线性序列;
成员方法1.private void dfs(Digraph G,int v):基于深度优先搜索,生成顶点线性序列
2.public Stack reversePost():获取顶点线性序列
成员变量1.private boolean[] marked: 索引代表顶点,值表示当前顶点是否已经被搜索
2.private Stack reversePost: 使用栈,存储顶点序列

2.2 顶点排序实现

在API的设计中,我们添加了一个栈reversePost用来存储顶点,当我们深度搜索图时,每搜索完毕一个顶点,把该顶点放入到reversePost中,这样就可以实现顶点排序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-muB1gCs5-1629935023644)(images/image20.png)]

代码:

/**
 * 基于深度优先的顶点排序--(有向图)
 */
public class DepthFirstOrder {
    // 索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    // 使用栈,存储顶点序列
    private Stack<Integer> reversePost;

    // 初始化属性,并将顶点存入顶点序列
    public DepthFirstOrder(Digraph G) {
        // 创建一个和图的顶点数一样大小的marked数组
        marked = new boolean[G.V()];
        // 初始化顶点序列
        reversePost = new Stack<Integer>();
        // 遍历搜索图中的每一个顶点
        for (int v = 0; v < G.V(); v++) {
            // 如果当前顶点没有搜索过,则搜索
            if (!marked[v]) {
                dfs(G, v);
            }
        }
    }

    // 基于深度优先搜索,将顶点存入顶点序列
    private void dfs(Digraph G, int v) {
        // 把当前顶点标记为已搜索
        marked[v] = true;
        // 遍历v顶点的邻接表,得到每一个顶点w
        for (Integer w : G.adj(v)) {
            // 如果当前顶点w没有被搜索过,则递归搜索与w顶点相通的其他顶点
            if (!marked[w]) {
                dfs(G, w);
            }
        }
        // 当前顶点已经搜索完毕,让当前顶点入栈
        reversePost.push(v);
    }

    // 获取顶点线性序列
    public Stack<Integer> reversePost() {
        return reversePost;
    }
}

3. 拓扑排序

前面已经实现了环的检测以及顶点排序,那么拓扑排序就很简单了,基于一幅图,先检测有没有环,如果没有环,则调用顶点排序即可。

API设计:

类名TopoLogical
构造方法TopoLogical(Digraph G):构造拓扑排序对象
成员方法public Stack order():获取拓扑排序的所有顶点
成员变量1.private Stack order: 顶点的拓扑排序

代码:

/**
 * 拓扑排序(有向图)
 */
public class TopoLogical {
    // 顶点的拓扑排序后的序列
    private Stack<Integer> order;

    // 构造拓扑排序对象
    public TopoLogical(Digraph G) {
        // 创建检测环对象,检测图G中是否有环
        DirectedCycle dCycle = new DirectedCycle(G);
        if (!dCycle.hasCycle()) {
            // 如果没有环,创建顶点排序对象,进行顶点排序
            DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
            order = depthFirstOrder.reversePost();
        }
    }

    // 获取拓扑排序的所有顶点
    public Stack<Integer> order() {
        return order;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

农村小白i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值