逆向拓扑排序

思路来自:与PKU3687一样

在基本的拓扑排序的基础上又增加了一个要求:编号最小的节点要尽量排在前面;在满足上一个条件的基础上,编号第二小的节点要尽量排在前面;在满足前两个条件的基础上,编号第三小的节点要尽量排在前面……依此类推。(注意,这和字典序是两回事,不可以混淆。)

如图 1 所示,满足要求的拓扑序应该是:6 4 1 3 9 2 5 7 8 0。

图 1 一个拓扑排序的例子

一般来说,在一个有向无环图中,用 BFS 进行拓扑排序是比较常见的做法,如算法 1 所示。但是它不一定能得到本题要求的拓扑序。

  1. 把所有入度为 0 的节点放进队列 Q

  2. WHILE: Q 不是空队列

  3. 从 Q 中取出队列首元素 a,把 a 添加到答案的尾部。
  4. FOR:所有从 a 出发的边 a → b
  5. 把 b 的入度减 1。如果 b 的入度变为 0,则把 b 放进队列 Q。

算法 1

用 BFS 进行拓扑排序

为了解决本问题,下面让我来探究一下拓扑序的一些性质。以图 1 为例,节点 0 毫无疑问排在最后。除了节点 0 以外,有三条互相平行的路径:6 → 4 → 1、 3 → 9 → 2 和 5 → 7 → 8。一条路径上的各个节点的先后关系都是不能改变的,比如路径 6 → 4 → 1 上的三个节点在拓扑序中,一定是 6 在最前,1 在最后。但是,互相平行的各条路径,在总的拓扑序中任意交错都是合法的。比如,以下都是图 1 的合法拓扑序:

6 4 1 3 9 2 5 7 8 0、 3 6 9 4 5 1 7 8 2 0、 5 6 4 7 3 8 1 9 2 0、 3 5 6 4 1 7 9 2 8 0、 6 5 7 8 4 3 9 2 1 0。

怎么才能找出题目要求的拓扑序呢?在这里,我想用字典序最先的拓扑序来引出这个算法。算法 2 可以求出字典序最先的拓扑序。

  1. 把所有入度为 0 的节点放进优先队列 PQ

  2. WHILE: PQ 不是空队列

  3. 从 PQ 中取出编号最小的元素 a,把 a 添加到答案的尾部。
  4. FOR:所有从 a 出发的边 a → b
  5. 把 b 的入度减 1。如果 b 的入度变为 0,则把 b 放进优先队列PQ。

算法 2 求出字典序最先的拓扑序

可见,算法 2 和算法 1 基本一样,只是把队列改成了优先队列。用它求出的图 1 的字典序最先的拓扑序为:3 5 6 4 1 7 8 9 2 0。但是这显然不是本题要求的答案,因为节点 1 的位置还不够靠前。

算法 2 可以算是一个贪心算法,每一步都找编号最小的节点。但是对于图 1 中的三条路径,头的编号比较小的,不一定要先出队列。正确的步骤应该如下:

节点 0 的位置是铁定在最后的,不用考虑。只考虑剩下的三条路径。
先找编号最小的,节点 1。把它和它所在的路径中位于它前面的节点全部拿出来。目前的答案是 6 4 1,这样, 节点 1 就尽量靠前了。
再找剩下的节点中编号最小的,节点 2。把它和它所在的路径中位于它前面的节点全部拿出来。目前的答案是 6 4 1 3 9 2 ,这样,节点 2 就尽量靠前了。
只剩下一条路径了,只能依次把其中的节点拿出来。最后答案就是 6 4 1 3 9 2 5 7 8 0。
显然,算法 2 的贪心策略对于这个问题是不可行的。不能着眼于每条路径的头,而是要找编号最小的节点在哪条路径上,优先把这条路径拿出来。但问题在于,在 BFS 的过程中,我们只能看到每条路径的头,看不到后面的节点,这该怎么办呢?

让我们换个角度想一想,节点 3 和 6,应该是 6 先出队列,因为节点 1 在 6 的后面。这和节点 3 和 6 的编号大小没有任何关系。但是,再看另外两条路径的尾部,节点 2 和 8,可以肯定地说,2 一定先出队列,因为它们后面都没有别的节点了,这个时候完全以这两个节点本身的编号大小决定顺序。归纳起来就是说,对于若干条平行的路径,小的头部不一定排在前面,但是大的尾部一定排在后面。于是,就有了算法 3。

算法3

  1. 把所有出度为 0 的节点放进优先队列 PQ
  2. WHILE: PQ 不是空队列
  3. 从 PQ 中取出编号最大的元素 a,把 a 添加到答案的头部。
  4. FOR:所有指向 a 的边 b → a
  5. 把 b 的出度减 1。如果 b 的出度变为 0,则把 b 放进优先队列PQ。

  1. 什么类型的题目需要反向建图 ?
    通过做题发现,对于要求编号小的靠前输出这类题需要反向建图(菜鸡做题少...未完待续

2.为什么要反向建图 ?
上面已经说过什么类型的题目需要反向建图。

比如说:有1 , 2 , 3 共 3 个数,要求 3 在 1 前面输出(对 2 没有要求

如果没有反向建图的话,会造成输出 2 3 1这样的错解( 实际上应该输出 3 1 2 ...

3.详细步骤
对于给定顺序:

6 -> 4 -> 7     ( 对于这一行,要求先输出6,在输出4,最后输出 7

3 -> 9  ->2      ( 同上。。。

5  ->1  ->8        (同上。。。

这样我们反向建图,每次都处理(比较)入度为 0 的点

6 <- 4 <- 7

3 <- 9 <- 2

5 <- 1 <- 8

这样我们先比较 7 2 8 这一列( 入度为 0 嘛),大的肯定是最后输出( 要求小的先输出

因此 ans[1] = 8 ,把 8 剔除出去,与 8 相连的点的入度 -1

6 <- 4 <- 7

3 <- 9 <- 2

5 <- 1

此时入度为 0 的点有 7 2 1 ,然后再把 最大的剔除出去

因此 ans[2] = 7,与 7 相连的点的入度 -1

6 <- 4 

3 <- 9 <- 2

5 <- 1

此时入度为 0 的点有 4 2 1 ,然后再把 最大的剔除出去

因此 ans[3] = 4 与 4 相连的点的入度 -1

反复执行上述步骤,直至处理完全部入度为 0 的点( 可能有环。。。)

最后得到 ans[1] = 8 ; ans[2] = 7 ;ans[3] = 4 ; ans[4] = 6 ;ans[5] = 2 ; ans[6] = 9 ;ans[7] = 3 ;ans[8] = 1 ; ans[9] = 5

此时,我们再反向输出,即可以得到答案

5 1 3 9 2 6 4 7 8

4.题目链接
Reward

逃生

5.结束

再补充一点:关于字典序最小 和 编号最小 优先输出的区别!

对比 HDU 1285 和 HDU 4875

我们发现前一道是要求 字典序最小优先输出 即可 后面一道却是要求 编号最小优先输出!!

那么,有什么区别吗?

对于这张图,字典序最小优先输出是 1 3 4 2

BUT 编号最小优先输出是 1 4 2 3

转载于:https://www.cnblogs.com/Roni-i/p/9179488.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、 功能简介 本课件是一个动态演示数据结构算法执行过程的辅助教学软件, 它可适应读者对算法的输入数据和过程执行的控制方式的不同需求, 在计算机的屏幕上显示算法执行过程中数据的逻辑结构或存储结构的变化状况或递归算法执行过程中栈的变化状况。整个系统使用菜单驱动方式, 每个菜单包括若干菜单项。每个菜单项对应一个动作或一个子菜单。系统一直处于选择菜单项或执行动作状态, 直到选择了退出动作为止。 二、 系统内容 本系统内含84个算法,分属13部分内容,由主菜单显示,与《数据结构》教科书中自第2章至第11章中相对应。各部分演示算法如下: 1. 顺序表 (1)在顺序表中插入一个数据元素(ins_sqlist) (2)删除顺序表中一个数据元素(del_sqlist) (3)合并两个有序顺序表(merge_sqlist) 2. 链表 (1)创建一个单链表(Crt_LinkList) (2)在单链表中插入一个结点(Ins_LinkList) (3)删除单链表中的一个结点(Del_LinkList) (4)两个有序链表求并(Union) (5)归并两个有序链表(MergeList_L) (6)两个有序链表求交(ListIntersection_L) (7)两个有序链表求差(SubList_L) 3. 栈和队列 (1)计算阿克曼函数(AckMan) (2)栈的输出序列(Gen、Perform) (3)递归算法的演示  汉诺塔的算法(Hanoi)  解皇后问题的算法(Queen)  解迷宫的算法(Maze)  解背包问题的算法(Knap) (4)模拟银行(BankSimulation) (5)表达式求值(Exp_reduced) 4. 串的模式匹配 (1)古典算法(Index_BF) (2)求Next 函数值(Get_next)和按Next 函数值进行匹配 (Index_KMP(next)) (3)求 Next 修正值(Get_nextval)和按 Next 修正值进行匹配(Index_KMP(nextval)) 5. 稀疏矩阵 (1)矩阵转置 (Trans_Sparmat) (2)快速矩阵转置 (Fast_Transpos) (3)矩阵乘法 (Multiply_Sparmat) 6. 广义表 (1)求广义表的深度(Ls_Depth) (2)复制广义表(Ls_Copy) (3)创建广义表的存储结构(Crt_Lists) 7. 二叉树 (1)遍历二叉树  二叉树的线索化  先序遍历(Pre_order)  中序遍历(In_order)  后序遍历(Post_order) (2) 按先序建二叉树(CrtBT_PreOdr) (3) 线索二叉树  二叉树的线索化  生成先序线索(前驱或后继) (Pre_thre)  中序线索(前驱或后继) (In_thre)  后序线索(前驱或后继) (Post_thre)  遍历中序线索二叉树(Inorder_thlinked)  中序线索树的插入(ins_lchild_inthr)和删除(del_lchild_inthr)结点 (4)建赫夫曼树和求赫夫曼编码(HuffmanCoding) (5)森林转化成二叉树(Forest2BT) (6)二叉树转化成森林(BT2Forest) (7)按表达式建树(ExpTree)并求值(CalExpTreeByPostOrderTrav) 8. 图 (1)图的遍历  深度优先搜索(Travel_DFS)  广度优先搜索(Travel_BFS) (2)求有向图的强连通分量(Strong_comp) (3)有向无环图的两个算法  拓扑排序(Toposort)  关键路径(Critical_path) (4)求最小生成树  普里姆算法(Prim)  克鲁斯卡尔算法(Kruscal) (5)求关节点和重连通分量(Get_artical) (6)求最短路径  弗洛伊德算法(shortpath_Floyd)  迪杰斯特拉算法(shortpath_DIJ) 9. 存储管理 (1)边界标识法 (Boundary_tag_method) (2)伙伴系统 (Buddy_system) (3)紧缩无用单元 (Storage_compactio

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值