使用DFS进行拓扑排序而不进行递归

时间:2013-11-22 20:01:46

标签: c++ algorithm stack depth-first-search topological-sort

我知道进行拓扑排序的常用方法是使用DFS进行递归。但是你如何使用stack<int>而不是递归呢?我需要获得逆转后的订单,但我有点卡住了:

该图是vector<vector<int> >邻接列表

以下是我想用于拓扑排序的DFS

bool visited[MAX]={0};
stack<int> dfs, postOrder;
vector<int> newVec;
vector<int>::iterator it;

for(int i=0;i<MAX;i++){
    if(visited[i]==false){
        dfs.push(i);
    }   
    while(!dfs.empty()){
        int node=dfs.top();
        dfs.pop();
        visited[node]=true;
        newVec=graph[node]; //vector of neighboors
        for(it=newVec.begin();it!=newVec.end();it++){
            int son=*it;
            if(visited[son]==false){
                dfs.push(son);
            }
        }
    }
}

7 个答案:

答案 0 :(得分:21)

为了构建postOrder列表,您需要知道算法处理完节点k的最后一个子节点的时间。

找出何时将最后一个子项弹出堆栈的一种方法是在堆栈上放置特殊标记,以指示特定节点的子节点正在启动的位置。您可以将dfs堆栈的类型更改为vector<pair<bool,int> >。当bool设置为true时,它表示父项; false表示孩子。

当您从堆栈中弹出“子对”(即将该对中的第一个成员设置为false)时,您运行您当前拥有的代码,即将所有子项都推入堆栈使用for循环。但是,在进入for循环之前,您应该将make_pair(true, node)推入堆栈,以标记此node的所有子项的开头。

当您从堆栈中弹出“父对”时,将父索引推送到postOrder,然后继续:

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.

答案 1 :(得分:4)

我认为你的代码是一个很好的非递归DFS。拓扑排序的关键点是:

当您选择要推入堆栈的节点时。此节点必须没有前导(具有度数为0)。这意味着您正在以in-degree为基础进行DFS,而不是将所有相邻节点推入堆栈,您总是选择0度为度的

所以你推动堆栈中没有前导的每个节点。弹出一个,打印并将其从所有相邻节点的前导列表中删除(或将其相邻节点的度数减少1)。这需要您编辑相邻列表。然后将具有0度现在度的所有相邻节点推入堆栈(此阶段可能会失败但没有问题,您仍然在堆栈中有一些节点)。然后弹出下一个。重复直到堆栈为空。您打印的节点序列是图的拓扑排序结果。

答案 2 :(得分:1)

下面是我对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;
}

进行测试:ideone

希望有所帮助!

答案 3 :(得分:0)

该节点是第一次访问并且仍在进行中,它被添加到堆栈中为false。然后,这些节点从堆栈处理为LIFO,并更改为true(已处理的子节点是访问过的子节点)。处理完所有子节点后,在追踪路径时,此节点从堆栈中删除。

对于那些尝试实现此代码的人,应将visited[node.second]=true;移动到第一个添加到第一个节点的地方为false。这样,不会重新访问导致已经跟踪的顶点的后边缘。

答案 4 :(得分:0)

这是Java中非递归方式进行拓扑排序的代码。它或多或少类似于DFS方法,带有一些额外的代码来实现目标。

if (csv.is_open())
{
    string line;

    while (getline(..))
    {   
        array.push_back(stof(val));
    }

    csv.close();
}

答案 5 :(得分:0)

图的结构如下

N:顶点数 adj[]:输入图

vector<int> topoSort(int V, vector<int> adj[]) {
    stack<int> s;
    vector<int> f(V,0);
    
    stack<int> out; 
    int i,j,x;
    for(i=0;i<V;i++){
        if(f[i]==0){
            s.push(i);
            
            while(!s.empty()){
                x = s.top();
                s.pop();
                if(f[x]==1){
                    out.push(x);
                    continue;
                }
                f[x] = 1;
                s.push(x);
                for(j=0;j<adj[x].size();j++){
                    if(f[adj[x][j]]==0){
                        s.push(adj[x][j]);
                    }
                }
            }
        }
    }
    
    vector<int> output;

    while(!out.empty()){
        x=out.top();
        out.pop();
        //cout << x << " ";
        output.push_back(x);
    }
    //cout << endl;
    
    return output;
}

答案 6 :(得分:-3)

我们再来一次。 :-)我正在提交答案,因为我没有足够的积分来发表评论。 : - (

让我说我喜欢这个算法很多。如果以正确的方式定义图形,则没有错误。但是请看这个图:

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

这将显示: 2 1 2 0

为了保护自己免受以这种方式定义的图形或进入的边缘是随机的,你可以这样做:

#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;
}

或者您可以预先订购图表,也许有人可以展示该解决方案。如何预先给定边缘预先排序,无需第二次检查。 : - )

我虽然对Atif Hussain的评论做了,但这是错误的。那永远不会奏效。您总是希望尽可能晚地向下推送一个节点,以便它尽可能首先弹出。