介绍
假设我们有一组任务要完成,并且有些任务要在其它任务完成之后才能开始,所以我们必须非常小心这些任务的执行顺序。
如果这些任务的执行顺序足够简单的话,我们可以用链表来存储它们,这是一个很好的方案,让我们可以准确知道任务的执行顺序。问题是有时候不同任务之间的关系是非常复杂的,有些任务依赖于两个甚至更多的任务,或者反过来很多任务依赖自己。
因此我们不能通过链表或者树的数据结构来对这个问题建模。对这类问题唯一合理的数据结构就是图。我们需要哪种图呢?很显然,我们需要有向图来描述这种关系,而且是不能循环的有向图,我们称之为有向无环图。
![](https://i-blog.csdnimg.cn/blog_migrate/127179038df218df502a20b0f61a4c00.png)
要通过拓扑排序对图形进行排序,这些图必须是不能循环和有向的。
为什么这些图不能循环呢?答案很明显,如果图形是循环的,我们无法知道哪个任务该优先执行,也不可能对任务进行排序。
现在我们一要做的是对图中的每个节点排序,组成一条条边(u,v),u在v之前执行。然后我们就可以得到所有任务的线性顺序,并按这种顺序执行任务就一切都OK了。
![](https://i-blog.csdnimg.cn/blog_migrate/591147b9ef460f85a58ad4aadd5782ed.png)
拓扑排序的结果是节点的列表!这类排序称为“拓扑”排序(topsot),它是非常基础的图形算法之一。
综述
好的,现在我们有一个有向无环图,我们怎么遍历图的所有节点来得到排序后的链表呢?因为是一个无环图, 我们知道它一定有一个节点是没有依赖任务(一定要先执行的任务)的。所以我们可以把所有这类节点(没有依赖节点)先放进我们的链表。
第一步,我们就得到了所有没有依赖节点的节点了!
这个方法回答了我们的一个疑问:图形的拓扑图排序的正确结果是否不止一个呢?的确如此,我们只是把所有节点放到正确的顺序上,但是那些没有依赖节点的顺序都可以组合成一个正确的拓扑图排序结果。
我们从上面的图可以看到,即使是有依赖节点的节点的排序结果也可能有多种:[9, 6, 2, 7, 4, 1]是一个有效的有序拓扑图, [6, 9, 2, 7, 4, 1]也是如此。
现在我们可以得到这个算法的基本步骤:
1.构造空列表 L和S;
2.把所有没有依赖节点的节点放入L;
3.当L还有节点的时候,执行下面步骤:
3.1 L中拿出一个节点n(从L中remove掉),并放入S
3.2 对每一个邻近n的节点m,
3.2.1 去掉边(n,m);(表示加入最终结果集S)
3.2.2 如果m没有依赖节点,把m放入L;
上面这幅图描述了算法的 3.2 的执行过程。
代码
下面算法非常简单的PHP语言的实现。你可以从这简短的代码中看到这个算法有多简单,而它对于计算机和编程的意义都是非常巨大的。
class G
{
protected$_g = array(
array(0,1, 1,0, 0,0, 0),
array(0,0, 0,1, 0,0, 0),
array(0,0, 0,0, 1,0, 0),
array(0,0, 0,0, 1,0, 0),
array(0,0, 0,0, 0,0, 1),
array(0,0, 0,0, 0,0, 1),
array(0,0, 0,0, 0,0, 0),
);
protected$_list = array();
protected$_ts = array();
protected$_len = null;
publicfunction __construct()
{
$this->_len = count($this->_g);
// 找到没有依赖节点的节点
$sum =0;
for($i = 0; $i < $this->_len; $i++) {
for($j = 0; $j < $this->_len; $j++) {
$sum += $this->_g[$j][$i];
}
if(!$sum) {
//把它放入_list中
array_push($this->_list, $i);
}
$sum =0;
}
}
publicfunction topologicalSort()
{
while($this->_list) {
$t = array_shift($this->_list);
array_push($this->_ts, $t);
foreach ($this->_g[$t] as $key => $vertex) {
if($vertex == 1) {
$this->_g[$t][$key] =0;
$sum =0;
for($i = 0; $i < $this->_len; $i++) {
$sum += $this->_g[$i][$key];
}
if(!$sum) {
array_push($this->_list, $key);
}
}
$sum =0;
}
}
print_r($this->_ts);
}
}
应用
正如我上面提到的,这个算法通常是用来确定相互依赖的任务的执行顺序的,当然不止这一个用处。实际上任何一种相互依赖的对象都可以用这种图形来建模。虽然有些时候这种图形是一种树,但大多数情况并不是这样。