Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I add finishing times for iterative depth-first search?

I'm trying to create depth-first algorithm that assigns finishing times (the time when a vertex can no longer be expanded) which are used for things like Kosaraju's algorithm. I was able to create a recursive version of DFS fairly easily, but I'm having a hard time converting it to an iterative version.

I'm using an adjacency list to represent the graph: a dict of vertices. For example, the input graph {1: [0, 4], 2: [1, 5], 3: [1], 4: [1, 3], 5: [2, 4], 6: [3, 4, 7, 8], 7: [5, 6], 8: [9], 9: [6, 11], 10: [9], 11: [10]} represents edges (1,0), (1,4), (2,1), (2,5), etc. The following is the implementation of an iterative DFS that uses a simple stack (LIFO), but it doesn't compute finishing times. One of the key problems I faced was that since the vertices are popped, there is no way for the algorithm to trace back its path once a vertex has been fully expanded (unlike in recursion). How do I fix this?

def dfs(graph, vertex, finish, explored):
    global count

    stack = []
    stack.append(vertex)

    while len(stack) != 0:
        vertex = stack.pop()

        if explored[vertex] == False:
            explored[vertex] = True

            #add all outgoing edges to stack:
            if vertex in graph:                 #check if key exists in hash -- since not all vertices have outgoing edges
                for v in graph[vertex]:
                    stack.append(v)

        #this doesn't assign finishing times, it assigns the times when vertices are discovered:                                            
        #finish[count] = vertex
        #count += 1

N.b. there is also an outer loop that complements DFS -- though, I don't think the problem lies there:

#outer loop:
for vertex in range(size, 0, -1):
    if explored[vertex] == False:
        dfs(hGraph, vertex, finish, explored)
like image 755
Eyeofpie Avatar asked Sep 20 '25 03:09

Eyeofpie


2 Answers

Think of your stack as a stack of tasks, not vertices. There are two types of task you need to do. You need to expand vertexes, and you need to add finishing times.

When you go to expand a vertex, you first add the task of computing a finishing time, then add expanding every child vertex.

When you go to add a finishing time, you can do so knowing that expansion finished.

like image 112
btilly Avatar answered Sep 22 '25 15:09

btilly


Here is a working solution that uses two stacks during the iterative subroutine. The array traceBack holds the vertices that have been explored and is associated with complementary 2D-array, stack, that holds arrays of edges that have yet to be explored. These two arrays are linked; when we add an element to traceBack we also add to stack (same with popping elements).

count = 0
def outerLoop(hGraph, N):
    explored    = [False for iii in range(N+1)]
    finish      = {}

    for vertex in range(N, -1, -1):
        if explored[vertex] == False:
            dfs(vertex, hGraph, explored, finish)

    return finish


def dfs(vertex, graph, explored, finish):
    global count
    stack       = [[]]                              #stack contains the possible edges to explore
    traceBack   = []
    traceBack.append(vertex)

    while len(stack) > 0:
        explored[vertex] = True

        try:
            for n in graph[vertex]:
                if explored[n] == False:
                    if n not in stack[-1]:          #to prevent double adding (when we backtrack to previous vertex)
                        stack[-1].append(n)
                else:
                    if n in stack[-1]:              #make sure num exists in array before removing
                        stack[-1].remove(n)             
        except IndexError: pass

        if len(stack[-1]) == 0:                     #if current stack is empty then there are no outgoing edges:
            finish[count] = traceBack.pop()         #thus, we can add finishing times
            count += 1

            if len(traceBack) > 0:                  #to prevent popping empty array
                vertex = traceBack[-1]
            stack.pop()
        else:
            vertex = stack[-1][-1]                  #pick last element in top of stack to be new vertex

            stack.append([])
            traceBack.append(vertex)
like image 42
Eyeofpie Avatar answered Sep 22 '25 17:09

Eyeofpie