原理
三色标记DFS天然适合拓扑排序。因为三色标记DFS是先查看栈顶元素,如果不是黑色,说明子元素(邻居)还没压栈。这个时候再将子元素压入栈,后续出栈的肯定是邻居。如果栈顶元素是黑色,说明所有邻居已经完成,可以出栈。那么我们就可以这样,先出栈的就往链表头部插入。DFS过程完成后,最后出栈的,是完全不依赖其他节点的,就在链表的头部了。这样就完成了DFS过程。
三色标记不能用来查环。因为在栈里查找性能太低,所以需要额外使用一个布尔数组作为路径,为了优化性能,可以使用位集。这个算法可以参考前面我写的有向图DFS查环算法。
Python代码
# _*_ coding:utf-8 _*_
white = 0
gray = 1
black = 2
class UnweightedGraph:
def __init__(self, vertices, edges):
self.__vertices = vertices
self.__edges = edges
def topological_sort(self):
colors = [white for _ in self.__vertices]
# 遍历森林
result = []
path = [False for _ in self.__vertices]
for root, _ in enumerate(self.__vertices):
if not colors[root] == white:
continue
stack = [root]
colors[root] = gray
while len(stack) > 0:
v = stack[-1]
if colors[v] == black:
stack.pop()
path[v] = False
result.insert(0, self.__vertices[v])
else:
path[v] = True
for neighbor in self.__edges[v]:
if colors[neighbor] == white:
stack.append(neighbor)
colors[neighbor] = gray
elif path[neighbor]:
raise RuntimeError(f'存在环,回边为{self.__vertices[v]}->{self.__vertices[neighbor]}')
colors[v] = black
return result
def to_dot(self):
s = 'digraph s {\n'
for v in self.__vertices:
s += f'"{v}";\n'
for i, e in enumerate(self.__edges):
for t in e:
s += f'\"{self.__vertices[i]}\"->"{self.__vertices[t]}";\n'
s += '}\n'
return s
测试数据
我准备了两组测试数据,一组无环,一组有环。
测试代码如下,自测是完全没问题的:
# _*_ coding:utf-8 _*_
import unittest
from com.youngthing.graph.topological_sort_dfs import UnweightedGraph
class TopologicalTestCase(unittest.TestCase):
def test(self):
# 定义一个图,测试BFS搜索
graph = self.create_graph()
path = graph.topological_sort()
print(path)
def test2(self):
# 定义一个图,测试BFS搜索
graph = self.create_graph2()
path = graph.topological_sort()
print(path)
print(graph.to_dot())
def create_graph(self):
vertices = ['赚钱', '买房', '买车', '彩礼', '结婚']
edges = [
[1, 2, 3],
[4], [4], [4], []
]
graph = UnweightedGraph(vertices, edges)
return graph
def create_graph2(self):
vertices = ['赚钱', '买房', '买车', '彩礼', '结婚']
edges = [
[1, 2, 3],
[4], [4], [4], [0]
]
graph = UnweightedGraph(vertices, edges)
return graph