算法期中考到一题关于拓扑序的题目,觉得很值得一写。

1.什么是拓扑序?

对一个有向无环图进行拓扑排序,假如图中存在一条从顶点A到顶点B的路径,则拓扑序中顶点A出现在顶点B的前面。要注意的是,这是对有向无环图而言的,假如图是有环的,拓扑序就无从谈起了。在这道题目中,已经假定了图是一个无环图。因此不需要进行检查。

2.怎么得出拓扑序?

有两种方法,分别基于BFS和DFS,时间复杂度都是O(|V| + |E|)。以这道题作为例子分别说一下:

(1)BFS

这是我最先想到的方法。我们需要:一个数组,统计每个顶点的入度数;一个队列用于bfs。首先遍历一次所有的边,将每个顶点的入度数都求出来,而入度数为0的顶点说明没有其他顶点指向它。因此先把入度数为0的顶点放进队列中。

接着用一个循环每次从队头取出front,把它放进返回结果的列表中。然后遍历一遍所有的边,假如当前边对应的起始顶点为front,则将边对应的终结顶点的入度数减1(我们把front放进结果中,也视作把front从图中去掉了,那入度数自然要减1了)。如果一个顶点的入度数变为0,说明已经没有其他顶点指向它了,就可以把它放入队尾。一直到队列为空时,说明拓扑序已经得到了。

代码如下:

class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
vector<int> res;
int * in_degree = new int[n];
queue<int> q;
for (int i = ; i < n; i++) {
in_degree[i] = ;
}
for (int i = ; i < edges.size(); i++) {
in_degree[edges[i].second]++;
}
for (int i = ; i < n; i++) {
if (in_degree[i] == ) {
q.push(i);
}
}
while (!q.empty()) {
int front = q.front();
q.pop();
res.push_back(front);
for (int i = ; i < edges.size(); i++) {
if (edges[i].first == front) {
in_degree[edges[i].second]--;
if (in_degree[edges[i].second] == ) {
q.push(edges[i].second);
}
}
}
}
return res;
}
};

(2)DFS

据说这是神书《算法导论》中提到的算法:用深度搜索来遍历整个图,采用一个数组来保存每个顶点完成的时间,这样这个数组就存放了按先后顺序访问完成的顶点了。然后我们按照顶点访问的完成时间从大到小排序,得到的就是一个拓扑序了,具体证明如下(来自其他博客):

在这道题中,肯定不用真的开一个数组啊,代码如下:

class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
vector<int> res;
stack<int> s;
int * isVisited = new int[n];
for (int i = ; i < n; i++) {
isVisited[i] = ;
}
for (int i = ; i < n; i++) {
if (!isVisited[i]) dfs(edges, s, isVisited, i);
}
while (!s.empty()) {
res.push_back(s.top());
s.pop();
}
return res;
}
void dfs(vector<pair<int, int> >& edges, stack<int> & s, int * isVisited, int u) {
isVisited[u] = ;
for (int i = ; i < edges.size(); i++) {
if (edges[i].first == u && !isVisited[edges[i].second]) {
dfs(edges, s, isVisited, edges[i].second);
}
}
s.push(u);
}
};

这样就得出结果了。(个人认为还是BFS易理解一点)

3.针对这道题......

So sad,以上两种方法在这道题都TLE了,因为对于每个顶点,我们都需要遍历一次边的数组,要想节省时间,我们就要花费空间,使对于每个顶点,我们只需要考虑以它为起始点的边。

真正能AC的代码:

class Solution {
public:
vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
vector<int> res;
vector<vector<int> > newedges(n, vector<int>());
queue<int> q;
vector<int> in_degree(n, );
for (int i = ; i < edges.size(); i++) {
in_degree[edges[i].second]++;
newedges[edges[i].first].push_back(edges[i].second);
}
for (int i = ; i < n; i++) {
if (in_degree[i] == ) {
q.push(i);
}
}
while (!q.empty()) {
int front = q.front();
q.pop();
res.push_back(front);
for (int i = ; i < newedges[front].size(); i++) {
in_degree[newedges[front][i]]--;
if (in_degree[newedges[front][i]] == ) q.push(newedges[front][i]);
}
}
return res;
}
};

4.抛开这道题目——有环情况的判断

可以利用上面的dfs方法,比如isVisited这个数组,我们可以多增一种情况,比如0为未访问,1为已访问,-1为正在访问,当dfs搜索时遇到了一条边终止顶点对应的isVisited元素为-1时,就说明图中有环了(为-1说明我们是从这个顶点开始dfs的,现在又遇到了这个顶点...)。

另外一种判断图是否有环的方法,借助bfs(dfs也可,但既然用了dfs,直接用上面的方法好了),假如“生成拓扑序”后,还有顶点不在这个“拓扑序”里面,则图就有环了(加双引号是因为不能真正称作“拓扑序”啊)。

LeetCode上有相应的题目,需要判断有无环(可以试试上面两种判断方法):

(1)https://leetcode.com/problems/course-schedule/description/

用了上面的第二种方法:

class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
int size = ;
vector<int> degree(numCourses, );
queue<int> q;
for (int i = ; i < prerequisites.size(); i++) {
degree[prerequisites[i].second]++;
}
for (int i = ; i < numCourses; i++) {
if (degree[i] == ) {
q.push(i);
}
}
while (!q.empty()) {
int front = q.front();
q.pop();
size++;
for (int i = ; i < prerequisites.size(); i++) {
if (prerequisites[i].first == front) {
if (--degree[prerequisites[i].second] == ) q.push(prerequisites[i].second);
}
}
}
return size == numCourses;
}
};

(2)https://leetcode.com/problems/course-schedule-ii/description/

用了第一种方法:

class Solution {
public:
vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<int> res;
vector<int> isVisited(numCourses, );
vector<vector<int>> v(numCourses, vector<int>());
for (int i = ; i < prerequisites.size(); i++) {
v[prerequisites[i].second].push_back(prerequisites[i].first);
}
stack<int> s;
bool isCircled = false;
for (int i = ; i < numCourses; i++) {
if (!isVisited[i]) {
dfs(i, s, v, isVisited, isCircled);
}
if (isCircled) {
break;
}
}
if (isCircled) return vector<int>();
while (!s.empty()) {
res.push_back(s.top());
s.pop();
}
return res;
}
void dfs(int u, stack<int> &s, vector<vector<int>>& v, vector<int> &isVisited, bool &isCircled) {
if (isCircled) return;
isVisited[u] = -;
for (int i = ; i < v[u].size(); i++) {
if (isVisited[v[u][i]] != ) {
if (isVisited[v[u][i]] == ) {
dfs(v[u][i], s, v, isVisited, isCircled);
}
else {
isCircled = true;
return;
}
}
}
isVisited[u] = ;
s.push(u);
}
};

拓扑排序(附LeetCode题目)的更多相关文章

  1. 拓扑排序的经典题目 UVA1572

    紫书172的例题: 题目大意:有n种正放形,每种正方形的数量可视为无限多.已知边与边之间的结合规则,而且正方形可以任意旋转和反转,问这n中正方形是否可以拼成无限大的图案 思路:首先因为是要无穷大,所以 ...

  2. POJ 2367 (裸拓扑排序)

    http://poj.org/problem?id=2367 题意:给你n个数,从第一个数到第n个数,每一行的数字代表排在这个行数的后面的数字,直到0. 这是一个特别裸的拓扑排序的一个题目,拓扑排序我 ...

  3. 正向与反向拓扑排序的区别(hdu 1285 确定比赛名次和hdu 4857 逃生)

    确定比赛名次 Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total Submis ...

  4. hiho一下 第四十八周 拓扑排序·二【拓扑排序的应用 + 静态数组 + 拓扑排序算法的时间优化】

    题目1 : 拓扑排序·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当 ...

  5. ZOJ - 3780-Paint the Grid Again-(拓扑排序)

    Description Leo has a grid with N × N cells. He wants to paint each cell with a specific color (eith ...

  6. [LeetCode] 207. 课程表(拓扑排序,BFS)

    题目 现在你总共有 n 门课需要选,记为 0 到 n-1. 在选修某些课程之前需要一些先修课程. 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1] 给定课程总量 ...

  7. LeetCode 207. Course Schedule(拓扑排序)

    题目 There are a total of n courses you have to take, labeled from 0 to n - 1. Some courses may have p ...

  8. LeetCode编程训练 - 拓扑排序(Topological Sort)

    拓扑排序基础 拓扑排序用于解决有向无环图(DAG,Directed Acyclic Graph)按依赖关系排线性序列问题,直白地说解决这样的问题:有一组数据,其中一些数据依赖其他,问能否按依赖关系排序 ...

  9. LeetCode 210. Course Schedule II(拓扑排序-求有向图中是否存在环)

    和LeetCode 207. Course Schedule(拓扑排序-求有向图中是否存在环)类似. 注意到.在for (auto p: prerequistites)中特判了输入中可能出现的平行边或 ...

随机推荐

  1. jquery监听事件on写法以及简单的拖拽效果

    引子——关于jquery的某些写法 我先不对监听事件做解释,我们先来看下jquery的一些写法吧!我们最常用的是jquery的css()方法,相信大家都会用! 假如用css设置一个属性,我们写法如下: ...

  2. VC中怎么输入特殊符号(如平方、立方等下标符号)

    同在列表控件里显示汉字一样,直接把输入法里特殊字符放进一个数组里,然后再赋值:CString F[]={"m²","∑","±"," ...

  3. 转载:Faster-RCNN详解

    原文:http://blog.csdn.net/zy1034092330/article/details/62044941 原文大神有很多经典之作,并且讲解得很透彻,建议前往,这里仅当学习使用. Fa ...

  4. SurvivalShooter学习笔记(五.敌人生命)

    敌人生命系统(受伤 死亡) 敌人生成后有初始生命,被攻击受伤有打击特效,降低生命值,直至死亡: 死亡后怪物:播放死亡音效,动画,然后下沉地表,销毁:玩家:得到相应分数. 敌人生命脚本如下: 1.变量: ...

  5. JavaWeb中servlet读取配置文件的方式

    我们在JavaWeb中常常要涉及到一些文件的操作,比如读取配置文件,下载图片等等操作.那我们能不能采用我们以前在Java工程中读取文件的方式呢?废话不多说我们来看看下我们以前在Java工程中读取文件是 ...

  6. HTML5 云知梦自觉,记录知识 点

    第一章(1--3) 文档类型:<!doctype html> 网站代码结构:<html> <head> <meta charset="UTF-8&q ...

  7. 《挑战程序设计竞赛》2.6 数学问题-快速幂运算 POJ1995

    POJ3641 此题应归类为素数. POJ1995 http://poj.org/problem?id=1995 题意 求(A1^B1+A2^B2+ - +AH^BH)mod M. 思路 标准快速幂运 ...

  8. canvas基本使用

    1.什么是CANVAS canvas是html5新增的画布元素,可以通过javascript来在画布上绘制图形,图标以及任何视觉性的图像. 2.canvas的用途 替代flash,做各种动态效果,做小 ...

  9. Vijos p1303导弹拦截(LIS+贪心)

    传送门:https://vijos.org/p/1303 背景 实中编程者联盟为了培养技术精湛的后备人才,必须从基础题開始训练. 描写叙述 某国为了防御敌国的导弹突击,研发出一种导弹拦截系统. 可是这 ...

  10. (4.17)sql server中的uuid获取与使用

    sql server中的uuid  建表: 1.自增长 studentno int primary key identity(1,1)——bigint也是可以的 2.创建uuidcustomerid  ...