【问题标题】:Enumerating cycles in a graph using Tarjan's algorithm使用 Tarjan 算法枚举图中的循环
【发布时间】:2014-09-17 18:45:42
【问题描述】:

我正在尝试使用 Tarjan 的算法确定有向图中的循环,该算法在他 1972 年 9 月的研究论文“有向图的基本电路的枚举”中提出。

我使用 Python 来编写算法,并使用邻接表来跟踪节点之间的关系。

所以在下面的“G”中,节点0指向节点1,节点1指向节点4,6,7...等

G = [[1], [4, 6, 7], [4, 6, 7], [4, 6, 7], [2, 3], [2, 3], [5, 8], [5, 8], [], []]
N = len(G)

points = []
marked_stack = []
marked = [False for x in xrange(0,N)]

g = None
def tarjan(s, v, f):
    global g
    points.append(v)
    marked_stack.append(v)
    marked[v] = True

    for w in G[v]:
        if w < s:            
            G[v].pop(G[v].index(w))
        elif w == s:
            print points
            f = True
        elif marked[w] == False:
            if f == g and f == False:
                f = False
            else:
                f = True
            tarjan(s, w, g)

    g = f
    
    if f == True:
        u = marked_stack.pop()
        while (u != v):
            marked[u] = False
            u = marked_stack.pop()
        #v is now deleted from mark stacked, and has been called u
        #unmark v
        marked[u] = False
    points.pop(points.index(v))

for i in xrange(0,N):
    marked[i] = False

for i in xrange(0,N):
    points = []
    tarjan(i,i, False)
    while (len(marked_stack) > 0):
        u = marked_stack.pop()
        marked[u] = False

Tarjan 的算法检测以下循环:

[2, 4]

[2, 4, 3, 6, 5]

[2, 4, 3, 7, 5]

[2, 6, 5]

[2, 6, 5, 3, 4]

[2, 7, 5]

[2, 7, 5, 3, 4]

[3,7,5]

我也完成了 Tiernan 的算法,它(正确地)找到了 2 个 extra 循环:

[3,4]

[3,6,5]

如果能帮助我找出 Tarjan 跳过这两个周期的原因,我将不胜感激。可能是我的代码有问题?

【问题讨论】:

标签: python algorithm graph-theory tarjans-algorithm


【解决方案1】:

在这些行中:

for w in G[v]:
    if w < s:            
        G[v].pop(G[v].index(w))

您正在遍历一个列表并从中弹出元素。这会阻止迭代按预期工作。

如果您更改代码以制作列表的副本,则会产生额外的循环:

for w in G[v][:]:

【讨论】:

  • StackOverflow 永远不会让人失望。非常感谢,您为我省了很多麻烦!
  • 您好,我想提出一个问题:'g' 标志是用来做什么的?
  • 在 Tarjan 的论文中,g 用于保存递归调用 backtrack 的返回值,这表明是否找到了延续堆栈上部分路径的基本电路(在这种情况下,顶点需要未标记,因为可能存在更多循环)。如果您想了解更多细节,最好提出一个新问题。
  • @PeterdeRivaz - 提出了一个关于此与具有低链接值并动态分配索引的算法版本的问题!你似乎有深厚的专业知识,所以我想标记你:)
【解决方案2】:

我有点好奇,因为我没有看到使用低链接值或动态分配索引,因为我认为 Tarjan 的查找 SCC 的算法就是这种情况。这是不同的算法还是修改?

我重命名了一些变量以使这更容易理解:

G = [
    [1],
    [4, 6, 7],
    [4, 6, 7],
    [4, 6, 7],
    [2, 3],
    [2, 3],
    [5, 8],
    [5, 8],
    [],
    [],
]
# %%


def entry_tarjan(G_):

    G = G_.copy()

    marked = [False] * len(G_)
    cycles = []
    point_stack = []
    marked_stack = []

    def tarjan(src, v):
        nonlocal cycles, marked, G
        cycle_found = False
        point_stack.append(v)
        marked_stack.append(v)
        marked[v] = True

        for nei in G[v]:
            if nei < src:
                # prevent prior traversals
                G[nei] = []

            elif nei == src:
                # neighbor is source => cycle
                cycles.append(point_stack.copy())
                cycle_found = True

            elif marked[nei] == False:
                # dfs continues
                cycle_in_nei = tarjan(src, nei)
                cycle_found = cycle_found or cycle_in_nei

        if cycle_found == True:
            # adjust marked to current vertex
            while marked_stack[len(marked_stack) - 1] != v:
                u = marked_stack.pop()
                marked[u] = False
            marked_stack.pop()
            marked[v] = False

        point_stack.pop()
        return cycle_found

    for i in range(len(G)):
        # start at each vertex
        tarjan(i, i)

        while marked_stack:
            u = marked_stack.pop()
            marked[u] = False

    return cycles


print(entry_tarjan(G))

【讨论】:

    【解决方案3】:

    下面的代码运行没有错误,并且有预期的输出。

    G = [[1], [4, 6, 7], [4, 6, 7], [4, 6, 7], [2, 3], [2, 3], [5, 8], [5, 8], [], []]
    N = len(G)
    
    points = []
    marked_stack = []
    marked = [False for x in xrange(0,N)]
    
    g = None
    def tarjan(s, v, f):
        global g
        points.append(v)
        marked_stack.append(v)
        marked[v] = True
    
        for w in G[v][:]:
            if w < s:            
                G[v].pop(G[v].index(w))
            elif w == s:
                print points
                f = True
            elif marked[w] == False:
                tarjan(s, w, g)
    
        g = f
        
        if f == True:
            u = marked_stack.pop()
            while (u != v):
                marked[u] = False
                u = marked_stack.pop()
            #v is now deleted from mark stacked, and has been called u
            #unmark v
            marked[u] = False
        points.pop(points.index(v))
    
    for i in xrange(0,N):
        marked[i] = False
    
    for i in xrange(0,N):
        points = []
        tarjan(i,i, False)
        while (len(marked_stack) > 0):
            u = marked_stack.pop()
            marked[u] = False
    

    输出:

    [2, 4]
    [2, 4, 3, 6, 5]
    [2, 4, 3, 7, 5]
    [2, 6, 5]
    [2, 6, 5, 3, 4]
    [2, 7, 5]
    [2, 7, 5, 3, 4]
    [3, 4]
    [3, 6, 5]
    [3, 7, 5]
    

    【讨论】:

      猜你喜欢
      • 2012-01-30
      • 2017-05-21
      • 2015-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-13
      • 1970-01-01
      相关资源
      最近更新 更多