拓扑排序详解------转
来源:互联网 发布:冒泡排序法的代码c语言 编辑:程序博客网 时间:2024/06/03 20:20
转载来自点击打开链接
以下将文章的一些内容粘贴过来以方便自己查看,请尊重原创,转载请注明
假设我们有一组任务要完成,并且有些任务要在其它任务完成之后才能开始,所以我们必须非常小心这些任务的执行顺序。
如果这些任务的执行顺序足够简单的话,我们可以用链表来存储它们,这是一个很好的方案,让我们可以准确知道任务的执行顺序。问题是有时候不同任务之间的关系是非常复杂的,有些任务依赖于两个甚至更多的任务,或者反过来很多任务依赖自己。
因此我们不能通过链表或者树的数据结构来对这个问题建模。对这类问题唯一合理的数据结构就是图。我们需要哪种图呢?很显然,我们需要有向图来描述这种关系,而且是不能循环的有向图,我们称之为有向无环图。要通过拓扑排序对图形进行排序,这些图必须是不能循环和有向的。为什么这些图不能循环呢?答案很明显,如果图形是循环的,我们无法知道哪个任务该优先执行,也不可能对任务进行排序。现在我们一要做的是对图中的每个节点排序,组成一条条边(u,v),u在v之前执行。然后我们就可以得到所有任务的线性顺序,并按这种顺序执行任务就一切都OK了。
例如,下面的图的一个拓扑排序是“5 4 2 3 1 0”。一个图可以有多个拓扑排序。
另一个拓扑排序是“4 5 2 3 1 0”。拓扑排序的第一个顶点总是入度为0。
方法一
现在我们可以得到这个算法的基本步骤:
1
1.构造空列表 L和S;
2
2.把所有没有依赖节点(入度为0)的节点放入L;
3
3.当L还有节点的时候,执行下面步骤:
4
3.1 L中拿出一个节点n(从L中remove掉),并放入S
5
3.2 对每一个邻近n的节点m,
6
3.2.1 去掉边(n,m);(表示加入最终结果集S)
7
3.2.2 如果m没有依赖节点(入度为零),把m放入L;
核心就是:每次都选取入度为0的节点,再更新其相邻的节点的入度 。
这个是相对比较直观的算法,也是常见的一种算法。我们用一个数组degree[]记录所有顶点的入度。删除点时更新该数组。
参考下面代码函数:topologicalSort1()
方法二
另外一种方法是参考DFS,对图的深度优先遍历做些修改。我们确信在有向图中如果存在一条边(u,v),那么顶点u会先于顶点v进入列表中。因此在深度遍历时,用栈来存储遍历的顺序。参考下面代码的函数:topologicalSort2()
C++实现
001
// C++实现的拓扑排序算法
002
#include<iostream>
003
#include <list>
004
#include <stack>
005
using
namespace
std;
006
007
// 图类
008
class
Graph
009
{
010
int
V;
//顶点个数
011
012
// 邻接表
013
list<
int
> *adj;
014
// 拓扑排序方法 2的辅助函数
015
void
topologicalSortRecall(
int
v,
bool
visited[], stack<
int
> &Stack);
016
017
public
:
018
Graph(
int
V);
019
020
// 添加边
021
void
addEdge(
int
v,
int
w);
022
023
//拓扑排序普通方法
024
void
topologicalSort1();
025
026
// 拓扑排序方法二
027
void
topologicalSort2();
028
};
029
030
Graph::Graph(
int
V)
031
{
032
this
->V = V;
033
adj =
new
list<
int
>[V];
034
}
035
036
void
Graph::addEdge(
int
v,
int
w)
037
{
038
adj[v].push_back(w);
//
039
}
040
041
//类似深度优先遍历,将和V相邻的顶点(且为访问过的)放入栈中
042
void
Graph::topologicalSortRecall(
int
v,
bool
visited[], stack<
int
> &stk)
043
{
044
//标记v为访问过的
045
visited[v] =
true
;
046
047
// 对每个顶点进行递归调用
048
list<
int
>::iterator i;
049
for
(i = adj[v].begin(); i != adj[v].end(); ++i)
050
if
(!visited[*i])
051
topologicalSortRecall(*i, visited, stk);
052
053
// 保存顶点
054
stk.push(v);
055
}
056
057
// 方法二,使用递归调用实现拓扑排序
058
void
Graph::topologicalSort2()
059
{
060
stack<
int
> stk;
061
bool
*visited =
new
bool
[V];
062
for
(
int
i = 0; i < V; i++)
063
visited[i] =
false
;
064
065
//每个顶点都调用一次
066
for
(
int
i = 0; i < V; i++)
067
if
(visited[i] ==
false
)
068
topologicalSortRecall(i, visited, stk);
069
070
// 打印
071
while
(stk.empty() ==
false
)
072
{
073
cout << stk.top() <<
" "
;
074
stk.pop();
075
}
076
}
077
078
// 方法一
079
void
Graph::topologicalSort1()
080
{
081
list<
int
>::iterator j;
082
int
degree[V];
083
//遍历所有的边,计算入度
084
for
(
int
i=0; i<V; i++){
085
degree[i] = 0;
086
for
(j = adj[i].begin(); j != adj[i].end(); ++j){
087
degree[*j]++;
088
}
089
}
090
list<
int
> zeroNodes;
//所有入度为0的点
091
list<
int
> result;
//所有入度为0的点
092
for
(
int
i=0; i<V; i++){
093
if
(degree[i] == 0){
094
zeroNodes.push_back(i);
095
}
096
}
097
while
(zeroNodes.size() > 0){
098
int
top = zeroNodes.back();
099
zeroNodes.pop_back();
100
result.push_back(top);
101
for
(j = adj[top].begin(); j != adj[top].end(); ++j){
102
degree[*j]--;
//删除和top相邻的边,并更新其它顶点的入度
103
if
(degree[*j] == 0) zeroNodes.push_back(*j);
104
}
105
}
106
107
//打印结果
108
for
(j= result.begin(); j != result.end(); j++)
109
cout << (*j) <<
" "
;
110
}
111
112
int
main()
113
{
114
// 创建文中所以的图
115
Graph g(6);
116
g.addEdge(5, 2);
117
g.addEdge(5, 0);
118
g.addEdge(4, 0);
119
g.addEdge(4, 1);
120
g.addEdge(2, 3);
121
g.addEdge(3, 1);
122
123
cout <<
"Following is a Topological Sort of the given graph using topologicalSort1\n"
;
124
g.topologicalSort1();
125
cout << endl;
126
cout <<
"Following is a Topological Sort of the given graph using topologicalSort2\n"
;
127
g.topologicalSort2();
128
return
0;
129
}
- 拓扑排序详解------转
- 拓扑排序 详解
- 拓扑排序 详解
- 拓扑排序 详解
- 拓扑排序算法详解
- 数据结构---拓扑排序详解
- 拓扑排序 之 Java详解
- poj3687~拓扑排序(附上拓扑排序详解)
- 拓扑排序(转)
- (转)拓扑排序
- 拓扑排序(一)之 C语言详解
- 拓扑排序详解以及java实现
- 拓扑排序
- 拓扑排序
- 拓扑排序
- 拓扑排序
- 拓扑排序
- 拓扑排序
- ----------配置路由--------------
- 每天复制表,表名为日期
- CSS3实例教程:详解calc()函数功能
- Android通过代码模拟物理、屏幕点击事件
- Java学习笔记---集合(一)Set
- 拓扑排序详解------转
- Android中设置控件可见与不可见详解
- lua cjson 解决_luaL_setfuncs
- YII的重写规则与URL的管理
- 每个人都应该知道的14个电脑使用技巧
- 封装一个日期时间选择器
- 关于table 、td设置边距的问题
- Android WebView 开发详解(三)
- 微信公众号开发---红包模块