使用堆栈的非递归深度优先搜索(DFS)

时间:2012-04-26 22:29:27

标签: algorithm graph graph-algorithm depth-first-search

好的,这是我关于Stack Overflow的第一篇文章我已经阅读了一段时间,真的很佩服网站。我希望这是可以接受的问题。所以我一直在阅读Intro to Algorithms(Cormen。麻省理工学院出版社),我完全依赖于图算法。我一直在研究用于广度和深度优先搜索的正式算法。

这是深度优先搜索的伪代码:

DFS(G)
-----------------------------------------------------------------------------------
1  for each vertex u ∈ G.V
2      u.color ← WHITE       // paint all vertices white; undiscovered
3      u.π ← NIL
4      time ← 0              // global variable, timestamps
5  for each vertex u ∈ G.V
6      if u.color = WHITE
7          DFS-VISIT(G,u)

DFS-VISIT(G, u)
-----------------------------------------------------------------------------------
1  u.color ← GRAY          // grey u; it is discovered
2  time ← time + 1 
3  u.d ← time
4  for each v ∈ G.Adj[u]   // explore edge (u,v)
5      if v.color == WHITE
6          v.π ← u
7          DFS-VISIT(G,v) 
8  u.color ← BLACK         // blacken u; it is finished
9  time ← time + 1
10 u.f ← time

该算法是递归的,它从多个源开始(将在未连接的图中发现每个顶点)。它有几个属性,大多数语言特定的实现可能会遗漏。它区分了3种不同的“颜色”顶点。它最初将所有这些都描绘成白色,然后当它们被“发现”(在DFS-VISIT中访问)时,它们将被涂成灰色。 DFS-VISIT算法在当前顶点的邻接列表上递归调用自身循环,并且当它没有任何边缘到任何白色节点时,只绘制一个顶点黑色。

每个顶点的另外两个属性保持为u.d和u.f是发现顶点(涂成灰色)或顶点完成(涂成黑色)时的时间戳。第一次绘制节点时,它的时间戳为1,并且每次绘制另一个时,它都会递增到下一个整数值(无论是灰色还是黑色)。 u.π也被维护,它只是指向发现你的节点的指针。

Algorithm Non-Recursive-DFS(G)
-----------------------------------------------------------------------------------
1   for each vertex u ∈ G.V
2       u.color ← WHITE
3       u.π ← NIL
4   time = 0
5   for each vertex u ∈ G.V
6       if u.color = WHITE
7           u.color ← GRAY
8           time ← time + 1
9           u.d ← time
7           push(u, S)
8           while stack S not empty
9               u ← pop(S)
10              for each vertex v ∈ G.Adj[u]
11                  if v.color = WHITE
12                      v.color = GRAY
13                      time ← time + 1
14                      v.d ← time
15                      v.π ← u
16                      push(v, S)
17              u.color ← BLACK 
18              time ← time + 1
19              u.f ← time

*编辑10/31/12 * 令人尴尬的是,我的算法已经错误了很长时间,它在大多数情况下会起作用,但不是全部。我刚刚得到了一个问题的流行问题徽章,我看到Irfy在下面的答案中发现问题的地方,所以这就是信用额度。我只是在这里修复它,以备将来需要它的人使用。

有没有人看到上述算法的缺陷?到目前为止,我还不是图论的专家,但我认为我对递归和迭代有很好的把握,我相信这应该是一样的。我希望它在功能上等同于递归算法。它应该保留第一个算法的所有属性,即使它们不是必需的。

当我开始写它时,我不认为我会有一个三重循环,但结果就是这样。当我环顾谷歌时,我已经看到了其他迭代算法,它只有一个双嵌套循环,但是,它们似乎没有从多个来源继续。 (即他们不会发现未连接图的每个顶点)

8 个答案:

答案 0 :(得分:5)

两种算法都很好。第二个是从递归到基于堆栈的直接转换。所有变异状态都存储在堆栈中。 G在执行算法期间永远不会改变。

算法将根据算法访问每个节点的顺序为每个断开连接的区域生成生成树。树将通过引用父节点(u.π)和段树(u.du.f)来表示。

子节点将具有对其父节点的引用(如果它是根节点,则为NULL),并且具有包含在其父节点范围内的范围(child.d .. child.f)。

parent.d < child.d < child.f < parent.f

child.π = parent

编辑:我在翻译中发现了一个错误。您实际上并没有将当前状态推入堆栈,而是将来的方法参数。此外,您不会为弹出的节点GRAY着色并设置f字段。

这是对原始第一个算法的重写:

algorithm Stack-DFS(G)
    for each vertex u ∈ G.V
        u.color ← WHITE
        u.π ← NIL
    time ← 0
    S ← empty stack
    for each vertex u ∈ G.V
        if u.color = WHITE
            # Start of DFS-VISIT
            step ← 1
            index ← 0
            loop unconditionally
                if step = 1
                    # Before the loop
                    u.color ← GRAY
                    time ← time + 1
                    u.d ← time
                    step ← 2
                if step = 2
                    # Start/continue looping
                    for each vertex v ∈ G.Adj[u]
                        i ← index of v
                        if i ≥ index and v.color = WHITE
                            v.π ← u
                            # Push current state
                            push((u, 2, i + 1), S)
                            # Update variables for new call
                            u = v
                            step ← 1
                            index ← 0
                            # Make the call
                            jump to start of unconditional loop
                    # No more adjacent white nodes
                    step ← 3
                if step = 3
                    # After the loop
                    u.color ← BLACK
                    time ← time + 1
                    u.right ← time
                    # Return
                    if S is empty
                        break unconditional loop
                    else
                        u, step, index ← pop(S)

可能有一些地方可以优化,但至少应该现在可以使用。

结果:

Name   d    f   π
q      1   16   NULL
s      2    7   q
v      3    6   s
w      4    5   v
t      8   15   q
x      9   12   t
z     10   11   x
y     13   14   t
r     17   20   NULL
u     18   19   r

答案 1 :(得分:1)

int stackk[100];
int top=-1;
void graph::dfs(int v){
 stackk[++top]=v;
// visited[v]=1;
 while(top!=-1){
   int x=stackk[top--];
   if(!visited[x])
    {visited[x]=1;
     cout<<x<<endl;
    }
   for(int i=V-1;i>=0;i--)
   {
        if(!visited[i]&&adj[x][i])
        {   //visited[i]=1;
            stackk[++top]=i;
        }
   }
 }
}
void graph::Dfs_Traversal(){
 for(int i=0;i<V;i++)
  visited[i]=0;
 for(int i=0;i<V;i++)
  if(!visited[i])
    g.dfs(i);

答案 2 :(得分:1)

我想我设法编写了一个更简单的伪代码。

但首先要说明一些事情:

  1. v.pDescendant - 指向其邻接列表给出的顶点后代的指针。
  2. 在邻接列表中,我假设每个元素都有一个字段&#34; pNext&#34;指向链表上的下一个元素。
  3. 我使用了一些C ++语法,主要是&#34; - &gt;&#34;和&#34;&amp;&#34;强调什么是指针,什么不是。
  4. Stack.top()返回指向堆栈第一个元素的指针。但与pop()不同的是,它不会删除它。
  5. 该算法基于以下观察: 访问时,顶点被推入堆栈。只有在我们检查(变黑)所有后代时才会删除(弹出)。

    DFS(G)
    1. for all vertices v in G.V do
    2.   v.color = WHITE; v.parent = NIL; v.d = NIL; v.f = NIL; v.pDescendant = adj[v].head
    3. time = 0 
    4. Initialize Stack
    5. for all vertices v in G.V s.t. v.color == WHITE do
    6.   time++
    7.   Stack.push(&v)
    8.   v.color = GRAY 
    9.   v.d = time 
    10.   DFS-ITERATIVE(G,v)
    
    DFS-ITERATIVE(G,s) 
    1. while Stack.Empty() == FALSE do
    2.   u = Stack.top();
    3.   if u.pDescendant == NIL // no Descendants to u || no more vertices to explore
    4.      u.color = BLACK
    5.      time++
    6.      u.f = time
    7.      Stack.pop()
    8.   else if (u.pDescendant)->color == WHITE
    9.      Stack.push(u.pDescendant)
    10.     time++
    10.     (u.pDescendant)->d = time
    11.     (u.pDescendant)->color = GRAY
    12.     (u.pDescendant)->parent = &u
    12.     u.pDescendant= (u.pDescendant)->pNext // point to next descendant on the adj list      
    13.  else
    14.     u.pDescendant= (u.pDescendant)->pNext // not sure about the necessity of this line      
    

答案 3 :(得分:0)

这是C ++中的代码。

class Graph
{
    int V;                          // No. of vertices
    list<int> *adj;                 // Pointer to an array containing adjacency lists
public:
    Graph(int V);                    // Constructor
    void addEdge(int v, int w);             // function to add an edge to graph
    void BFS(int s);                    // prints BFS traversal from a given source s
    void DFS(int s);
};

Graph::Graph(int V)
{
    this->V = V;
    adj = new list<int>[V]; //list of V list
}

void Graph::addEdge(int v, int w)
{
    adj[v].push_back(w); // Add w to v’s list.
}


  void Graph::DFS(int s)
        {
                 //mark unvisited to each node
        bool *visited  = new bool[V];
        for(int i = 0; i<V; i++)
            visited[i] =false;
        int *d = new int[V];  //discovery
        int *f = new int[V]; //finish time

        int t = 0;       //time

        //now mark current node to visited
        visited[s] =true;
        d[s] = t;            //recored the discover time

        list<int> stack;

        list<int>::iterator i;

        stack.push_front(s);
        cout << s << " ";

        while(!(stack.empty()))
        {
            s= stack.front();
            i = adj[s].begin();

            while ( (visited[*i]) && (i != --adj[s].end()) )
            {
                ++i;
            }
            while ( (!visited[*i])  )
            {

                visited[*i] =true;
                t++;
                d[*i] =t;
                if (i != adj[s].end())
                    stack.push_front(*i);

                cout << *i << " ";
                i = adj[*i].begin();

            }

            s = stack.front();
            stack.pop_front();
            t++;
            f[s] =t;

        }
        cout<<endl<<"discovery time of the nodes"<<endl;

        for(int i = 0; i<V; i++)
        {
            cout<< i <<" ->"<< d[i] <<"    ";
        }
        cout<<endl<<"finish time of the nodes"<<endl;

        for(int i = 0; i<V; i++)
        {
            cout<< i <<" ->"<< f[i] <<"   ";
        }

    }

         int main()
         {
        // Create a graph given in the above diagram
        Graph g(5);
        g.addEdge(0, 1);
        g.addEdge(0, 4);
        g.addEdge(1, 4);
        g.addEdge(1, 2);
        g.addEdge(1, 3);
        g.addEdge(3, 4);
        g.addEdge(2, 3);


        cout << endl<<"Following is Depth First Traversal (starting from vertex 0) \n"<<endl;
        g.DFS(0);

        return 0;
    }

简单的迭代DFS。您还可以查看发现时间和结束时间。有任何疑问请评论。我已经包含了足够的注释来理解代码。

答案 4 :(得分:-1)

您的非递归代码确实存在严重缺陷。

在您检查v是否为WHITE后,您在推送之前从未将其标记为GRAY,因此可能会从其他未访问的节点一次又一次地将其视为WHITE,导致对推送到堆栈的v节点的多个引用。这可能是一个灾难性的缺陷(可能导致无限循环等)。

此外,除根节点外,您不设置.d。这意味着Nested set model属性.d.f s将不正确。 (如果您不知道.d.f是什么,请阅读该文章,这对我来说非常有启发性。文章的left是你的.dright是您的.f。)

您的内部if基本上需要与外部if减去循环以及父引用相同。那就是:

11                  if v.color = WHITE
++                      v.color ← GRAY
++                      time ← time + 1
++                      v.d ← time
12                      v.π ← u
13                      push(v, S)

纠正这一点,它应该是一个真正的等价物。

答案 5 :(得分:-1)

在非递归版本中,我们需要另一种颜色来反映递归堆栈中的状态。因此,我们将添加一个color = RED来指示节点的所有子节点都被推送到堆栈。我还假设堆栈有一个peek()方法(否则可以用pop和立即推送模拟)

因此,通过添加,原始帖子的更新版本应如下所示:

for each vertex u ∈ G.V
      u.color ← WHITE
      u.π ← NIL
  time = 0
  for each vertex u ∈ G.V
      if u.color = WHITE
          u.color ← GRAY
          time ← time + 1
          u.d ← time
          push(u, S)
          while stack S not empty
              u ← peek(S)
              if u.color = RED
                  //means seeing this again, time to finish
                  u.color ← BLACK
                  time ← time + 1
                  u.f ← time
                  pop(S) //discard the result
              else
                  for each vertex v ∈ G.Adj[u]
                     if v.color = WHITE
                         v.color = GRAY
                         time ← time + 1
                         v.d ← time
                         v.π ← u
                         push(v, S)
                   u.color = RED

答案 6 :(得分:-1)

I used Adjacency Matrix:    

void DFS(int current){
        for(int i=1; i<N; i++) visit_table[i]=false;
        myStack.push(current);
        cout << current << "  ";
        while(!myStack.empty()){
            current = myStack.top();
            for(int i=0; i<N; i++){
                if(AdjMatrix[current][i] == 1){
                    if(visit_table[i] == false){ 
                        myStack.push(i);
                        visit_table[i] = true;
                        cout << i << "  ";
                    }
                    break;
                }
                else if(!myStack.empty())
                    myStack.pop();
            }
        }
    }

答案 7 :(得分:-2)

我认为至少有一种情况是递归和堆栈版本在功能上不相同。考虑三角形的情况 - 顶点A,B和C相互连接。现在,利用递归DFS,人们将用源A获得的前趋图将是A-> B-> C或A-> C-> B(A-> B意味着A是B的父母深入第一棵树)。

但是,如果使用DFS的堆栈版本,则B和C的父项将始终记录为A. B的父级可能永远不会是C,反之亦然(总是如此)用于递归DFS)。这是因为,当探索任何顶点的邻接列表(此处为A)时,我们将邻接列表的所有成员(此处为B和C)推送到一起

如果您尝试使用DFS在图表中查找关节点,这可能会变得相关[1]。 一个例子是,只有当我们使用DFS的递归版本时,以下陈述才成立。

  

根顶点是一个关节点,当且仅当它至少有   两个孩子在深度第一棵树。

在一个三角形中,显然没有关节点,但是堆栈-DFS仍然为深度优先树中的任何源顶点提供了两个子节点(A有子节点B和C)。只有当我们使用递归DFS创建深度优先树时,上述陈述才成立。

[1]算法简介,CLRS - 问题22-2(第二版和第三版)