Topological sort using DFS without recursion

喜你入骨 提交于 2019-11-30 02:22:18

In order to construct the postOrder list you need to know the time when your algorithm has finished processing the last child of node k.

One way to figure out when you have popped the last child off the stack is to put special marks on the stack to indicate spots where the children of a particular node are starting. You could change the type of your dfs stack to vector<pair<bool,int> >. When the bool is set to true, it indicates a parent; false indicates a child.

When you pop a "child pair" (i.e. one with the first member of the pair set to false) off the stack, you run the code that you currently have, i.e. push all their children onto the stack with your for loop. Before entering the for loop, however, you should push make_pair(true, node) onto the stack to mark the beginning of all children of this node.

When you pop a "parent pair" off the stack, you push the parent index onto the postOrder, and move on:

vector<bool> visited(MAX);
stack<pair<bool,int> > dfs;
stack<int> postOrder;
vector<int> newVec;
vector<int>::iterator it;
vector<vector<int> > graph;
for(int i=0;i<MAX;i++){
    if(visited[i]==false){
        dfs.push(make_pair(false,i));
    }   
    while(!dfs.empty()){
        pair<bool,int> node=dfs.top();
        dfs.pop();
        if (node.first) {
            postOrder.push(node.second);
            continue;
        }
        visited[node.second]=true;
        dfs.push(make_pair(true, node.second));
        newVec=graph[node.second]; //vector of neighboors
        for(it=newVec.begin();it!=newVec.end();it++){
            int son=*it;
            if(visited[son]==false){
                dfs.push(make_pair(false, son));
            }
        }
    }
}

Demo on ideone.

I think your code is a good non-recursive DFS . The key point of topological sort is that:

When you pick a node to push into the stack. This node must have no precessor( has a in-degree of 0). This means you are doing a DFS base on in-degree in stead of push all the adjacent nodes into the stack, you always pick the one with 0 in-degree

So you push every node that have no precessor in the stack. Pop one, print it and remove it from all its adjacent nodes' precessor list ( or decrease its adjacent nodes' in-degree by 1). This need you to edit your adjacent list. Than push all its adjacent nodes that has in-degree of 0 now in to the stack( this phase may fail but no problem, you still got some nodes in the stack). Then pop the next one. Repeat until the stack is empty. The node sequence that you printed is the topological sort result of the graph.

Below is my iterative code to topological sorting of DAG.

#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <stack>
using namespace std;

unordered_map<int, unordered_set<int>> g;  // this is the simplest graph representation I was able to implement. Map the vertices to their set of children

void addEdge(int x, int y) { // Add edges to the graph
    g[x].insert(y);
}

void topSort() {
    unordered_set<int> visited; // Keep track of visited nodes
    stack<int> mainStack; // The stack that will have the resulting vertices in topologically sorted order

    for(auto it = g.begin(); it != g.end(); it++) {
        if(visited.count(it->first) == 0) { // If the vertex was not already visited do the following
            visited.insert(it->first); // visit the vertex
            stack<int> locStack;
            locStack.push(it->first); // push it to local stack
            while(!locStack.empty()) { // This part is similar to basic DFS algorithm
                int curr = locStack.top();
                bool unVisCh = false; // Keep a boolean to check whether there is any unvisited child
                for(auto it2 = g[curr].begin(); it2 != g[curr].end(); it2++) {
                    int child = *it2;
                    if(visited.count(child) == 0) {
                        unVisCh = true;
                        visited.insert(child);
                        locStack.push(child);
                    }
                }
                if(!unVisCh) { // if there is no unvisited child then we can push the vertex to the resulting stack
                    locStack.pop();
                    mainStack.push(curr);
                }
            }
        }
    }

    while(!mainStack.empty()) {
        cout<<mainStack.top()<<" ";
        mainStack.pop(); // print them in order
    }
    cout<<endl;
}

int main() {
    addEdge(1,2);
    addEdge(4,5);
    addEdge(5,6);
    addEdge(3,2);
    addEdge(2,6);
    addEdge(1,3);
    addEdge(4,3); // add adges to the graph

    topSort();

    return 0;
}

For testing: ideone

Hope that helps!

The node is 1st visited and is still in process, it's added to stack as false. These nodes are then processed from stack as LIFO, and changed to true (processed means children visited). After all children processed, while tracing path back, this node dropped from stack.

For those trying to implement this code, visited[node.second]=true; should be moved to the 2 places where node is 1st added to stack as false. This, so that back edges leading to already traced vertices not revisited.

Here we go again. :-) I am submitting an answer, because I do not have enough points to make comments. :-(

Well let me say that I like this algorithm a lot. If the graph is defined in the right way, then there is no error. But take this graph:

vector<vector<int>> graph
{
     { 2, 1 }
    ,{ 2 }
    ,{ }
};

This will display: 2 1 2 0

To protect yourself from graphs defined in that way or where the edges that come in are random you can do this:

#include <iostream>
#include <stack>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
    stack<pair<bool, int>> dfs;
    stack<int> postorder;
    vector<int> newvector;
    vector<int>::iterator it;
    vector<vector<int>> graph
    {
         { 2, 1 }
        ,{ 2 }
        ,{ }
    };

    vector<bool> visited(graph.size());
    vector<bool> finallyvisited(graph.size());

    for (int i = 0;i < graph.size();i++)
    {
        if (!visited[i])
        {
            dfs.push(make_pair(false, i));
        }

        while (!dfs.empty())
        {
            pair<bool, int> node = dfs.top();
            dfs.pop();
            if (node.first)
            {
                if (!finallyvisited[node.second])
                {
                    finallyvisited[node.second] = true;
                    postorder.push(node.second);
                    cout << node.second << endl;
                }
                continue;
            }

            visited[node.second] = true;
            dfs.push(make_pair(true, node.second));
            newvector = graph[node.second];
            for (it = newvector.begin();it != newvector.end();it++)
            {
                int son = *it;
                if (!visited[son])
                {
                    dfs.push(make_pair(false, son));
                }
            }
        }
    }
    return 0;
}

Or you could preorder the graph, maybe someone could show that solution. How to preorder randomly given edges that there is no need for second check. :-)

And I did though over the Atif Hussain comment and it is erroneous. That would never work. You always want to push down the stack a node as late as possible so it pops as first as possible.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!