2
$\begingroup$

Problem

I am currently digging deep into some optimizations on the classical iterative approaches to both DFS and BFS algorithms. The material I'm currently using at my University presents both iterative approaches as follows.

Definitions:

G(V,E): Connected graph on which the algorithm will be run, with a set V of vertices and a set E of edges.

A[1...n]: Array of adjacency lists for each vertex in graph (A[k] is the linked list containing the adjacent vertices of vertex k)

v[1...n]: Array to track visited vertices

u[1...n]: Array to track active vertices (vertices which have entered the data structure in use)

Iterative Methods Presented:

These are the proposed algorithms in the material of my Universitie's course:

BFS

BFS-Iterative(A,v,u,i): // on vertex i
queue <- new Queue()
markInUse(u,i) // sets u[k] = 1
queue.enqueue(i)
while not queue.empty():
    k <- queue.dequeue()
    markAsVisited(v,i)
    for j in A[k]:
        if isNotInUse(u,j) and isNotVisited(v,j): // checks if u[k] = 1
            markInUse(u,j)
            queue.enqueue(j)

The space complexity here is O(|V|), thanks to the use of the "in-use" array u, which ensures that no vertex (index) is pushed more than once into the queue.

DFS

DFS-Iterative(A,v,i): // on vertex i
stack <- new Stack()
stack.push(i)
while not stack.empty():
    k <- stack.pop()
    if isNotVisited(v,k): // checks if v[k] = 1
        markVisited(v,k) // sets v[k] = 1
        for j in A[k]:
            if isNotVisited(v,j):
                stack.push(j)

According to the materials, the space complexity here is O(|E|), and it can be improved by emulating the same behaviour as the recursive algorithm by pushing iterators into the stack (or pointers to the lists) instead of all neighbours of a specific vertex, which I also implemented successfully (not shown here).

Questions

Question 1 (BFS symmetric to DFS):

If I take exactly the same structure of the original DFS-Iterative algorithm and change the stack to a queue (without the "in-use" array u) then I would arrive at a correct BFS-Iterative that only has the downside of having a space of O(|E|):

BFS-Iterative-Symmetric(A,v,i):
queue <- new Queue()
queue.enqueue(i)
while not queue.empty():
    k <- queue.dequeue()
    if isNotVisited(v,k):
        markVisited(v,k)
        for j in A[k]:
            if isNotVisited(v,j):
                queue.enqueue(j)

Would it make sense to use iterators to draft a new version of the BFS algorithm to reduce it to O(|V|), without having to use the "in-use" array?

Question 2 (BFS modified)

For didactical purposes and being able to understand better both approaches I modified the BFS approach to resemble more the structure DFS one.

Would the following algorithm - despite a possible redundancy in the if-checks - still be correct?

BFS-Iterative-Modified(A,v,u,i):
queue <- new Queue()
markInUse(u,i)
queue.enqueue(i)
while not queue.empty():
    k <- queue.dequeue()
    if isNotVisited(v,i): // (1) added this check 
        markAsVisited(v,i)
        for j in A[k]:
            if isNotInUse(u,j): // (2) removed isNotVisited(v,j)
                markInUse(u,j)
                queue.enqueue(j)

Question 3 (DFS modified):

The materials state that the use of the "trick" of using an array to keep track of the verteces "in-use" in the vector u does NOT work for the DFS, and it is left to the reader to find out why.

Why would the following modified algorithm using the "in-use" array method not work? This is my take on the implementation, since the material does not show this algorithm, it only mentions it. I ran it on a couple of simple examples and it worked well. What am I missing here?

DFS-Iterative-Modified(A,v,u,i): // same structure as BFS-Iterative-Modified
stack <- new Stack() // same as above, using now stack instead of queue
markInUse(u,i)
stack.push(i)
while not stack.empty():
    k <- stack.pop()
    if isNotVisited(v,i):
        markAsVisited(v,i)
        for j in A[k]:
            if isNotInUse(u,j):
                markInUse(u,j)
                stack.push(j)

It does seem that this could also improve the DFS algorithm from O(|E|) to O(|V|), but maybe I just need a counter example for me to understand why it's wrong.

$\endgroup$
1

1 Answer 1

1
$\begingroup$

Answer to question 1:

I am not sure how the version of BFS with iterators would work. But it's also not clear why you would want to do that -- if you are after $O(V)$ space, the original algorithm already does that.

You say you want to get rid of one of two arrays u and v. You can simply get rid of the v array:

BFS-Iterative(A,v,u,i): // on vertex i
queue <- new Queue()
u[i] = 1
queue.enqueue(i)
while not queue.empty():
    k <- queue.dequeue()
    for j in A[k]:
        if not u[j]:
            u[j] = 1
            queue.enqueue(j)

Answer to question 2:

Yes that code works, but the check (1) is redundant: it is always true.

Answer to question 3:

That code doesn't visit vertices in DFS order. Consider for example a graph with edges 1->{2, 3, 4}, 2->4. The DFS order would be 1, 2, 4, 3 but that code will visit in order 1, 2, 3, 4.

$\endgroup$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.