Comment mettre en œuvre la profondeur première recherche de graphe avec aprroach non récursif

eh Bien, j'ai passé beaucoup de temps sur cette question. Cependant, je ne peux trouver des solutions avec des méthodes non-récursives pour un arbre: Non récursive pour l'arbre , ou méthode récursive pour le graphe, récursive pour le graphe .

et beaucoup de tutoriels(Je ne fournis pas ces liens ici) ne fournissent pas les approches aussi bien. Ou le tutoriel est totalement erronée. S'il vous plaît aider moi.

mise à Jour:

il est vraiment difficile de décrire:

si j'ai un graphique non corrigé:

               1
             / |  
            4  |   2
               3 /

1-- 2-- 3 --1 c'est un cycle.

au pas: push the neighbors of the popped vertex into the stack

WHAT'S THE ORDER OF THE VERTEXES SHOULD BE PUSHED?

si l'ordre poussé est 2 4 3, le sommet de la pile est:

| |
|3|
|4|
|2|    
 _

après avoir fait sauter les noeuds, nous avons eu le résultat: 1 - > 3 -> 4 -> 2 au lieu de 1--> 3 --> 2 -->4.

C'EST INCORRECT. QUELLE CONDITION DOIS-JE AJOUTER POUR ARRÊTER CE SCÉNARIO?

24
demandé sur Community 2014-02-02 12:58:36

13 réponses

A DFS sans récursion est fondamentalement la même que BFS - mais utiliser une stack au lieu d'une file d'attente que la structure de données.

Le fil Itératif DFS vs Récursive DFS et les différents éléments de commande les poignées avec les deux approches et la différence entre eux (et il y en a! vous ne traverserez pas les noeuds dans le même ordre!)

l'algorithme pour l'approche itérative est essentiellement:

DFS(source):
  s <- new stack
  visited <- {} // empty set
  s.push(source)
  while (s is not empty):
    current <- s.pop()
    if (current is in visited):
        continue
    visited.add(current)
    // do something with current
    for each node v such that (current,v) is an edge:
        s.push(v)
41
répondu amit 2017-05-23 11:47:28

ce n'est pas une réponse, mais un commentaire étendu, montrant l'application de l'algorithme dans la réponse de @amit au graphique dans la version actuelle de la question, en supposant que 1 est le noeud de départ et ses voisins sont poussés dans l'ordre 2, 4, 3:

               1
             / |  \
            4  |   2
               3 /

Actions            Stack             Visited
=======            =====             =======
push 1             [1]               {}
pop and visit 1    []                {1}
 push 2, 4, 3      [2, 4, 3]         {1}
pop and visit 3    [2, 4]            {1, 3}
 push 1, 2         [2, 4, 1, 2]      {1, 3}
pop and visit 2    [2, 4, 1]         {1, 3, 2}
 push 1, 3         [2, 4, 1, 1, 3]   {1, 3, 2}
pop 3 (visited)    [2, 4, 1, 1]      {1, 3, 2}
pop 1 (visited)    [2, 4, 1]         {1, 3, 2}
pop 1 (visited)    [2, 4]            {1, 3, 2}
pop and visit 4    [2]               {1, 3, 2, 4}
  push 1           [2, 1]            {1, 3, 2, 4}
pop 1 (visited)    [2]               {1, 3, 2, 4}
pop 2 (visited)    []                {1, 3, 2, 4}

appliquant ainsi l'algorithme poussant les voisins de 1 dans l'ordre 2, 4, 3 résultats dans l'ordre de visite 1, 3, 2, 4. Indépendamment de l'ordre de poussée pour les voisins de 1, 2 et 3 seront adjacents dans l'ordre de visite parce que celui qui est visité le premier poussera l'autre, qui n'est pas encore visité, ainsi que 1 qui a été visité.

16
répondu Patricia Shanahan 2014-02-02 19:03:39

la logique DFS devrait être:

1) si le noeud courant n'est pas visité, visitez le noeud et marquez-le comme visité
151940920" 2) pour tous ses voisins qui n'ont pas été visités, les pousser à la pile

par exemple, définissons une classe GraphNode en Java:

class GraphNode {
    int index;
    ArrayList<GraphNode> neighbors;
}

et voici la DFS sans récursion:

void dfs(GraphNode node) {
    // sanity check
    if (node == null) {
        return;
    }

    // use a hash set to mark visited nodes
    Set<GraphNode> set = new HashSet<GraphNode>();

    // use a stack to help depth-first traversal
    Stack<GraphNode> stack = new Stack<GraphNode>();
    stack.push(node);

    while (!stack.isEmpty()) {
        GraphNode curr = stack.pop();

        // current node has not been visited yet
        if (!set.contains(curr)) {
            // visit the node
            // ...

            // mark it as visited
            set.add(curr);
        }

        for (int i = 0; i < curr.neighbors.size(); i++) {
            GraphNode neighbor = curr.neighbors.get(i);

            // this neighbor has not been visited yet
            if (!set.contains(neighbor)) {
                stack.push(neighbor);
            }
        }
    }
}

on peut utiliser la même logique faire DFS de manière récursive, clone graphique etc.

6
répondu jean.timex 2014-10-26 21:01:05

la récursion est un moyen d'utiliser la pile d'appels pour stocker l'état de traversée du graphe. Vous pouvez utiliser la pile explicitement, par exemple en ayant une variable locale de type std::stack , alors vous n'aurez pas besoin de la récursion pour implémenter la DFS, mais juste une boucle.

2
répondu bobah 2014-02-02 09:04:15

d'accord. si vous êtes toujours à la recherche d'un code java

dfs(Vertex start){
    Stack<Vertex> stack = new Stack<>(); // initialize a stack
    List<Vertex> visited = new ArrayList<>();//maintains order of visited nodes
    stack.push(start); // push the start
    while(!stack.isEmpty()){ //check if stack is empty
        Vertex popped = stack.pop(); // pop the top of the stack
        if(!visited.contains(popped)){ //backtrack if the vertex is already visited
            visited.add(popped); //mark it as visited as it is not yet visited
            for(Vertex adjacent: popped.getAdjacents()){ //get the adjacents of the vertex as add them to the stack
                    stack.add(adjacent);
            }
        }
    }

    for(Vertex v1 : visited){
        System.out.println(v1.getId());
    }
}
2
répondu The New One 2015-07-12 08:11:09

code Python. La complexité temporelle est O ( V + E ) où V et E sont respectivement le nombre de sommets et d'arêtes. La complexité de l'espace est O ( V ) en raison du pire des cas où il ya un chemin qui contient chaque vertex sans aucun retour en arrière (c.-à-d. le chemin de recherche est un chaîne linéaire ).

la pile stocke les tuples de la forme (vertex, vertex_edge_index) afin que la DFS puisse être reprise à partir d'un vertex particulier au bord immédiatement après le dernier bord qui a été traité à partir de ce vertex (tout comme la pile d'appels de fonction d'un DFS récursif).

le code de l'exemple utilise un digraphe complet où chaque vertex est connecté à chaque autre vertex. Il n'est donc pas nécessaire de stocker une liste de bord explicite pour chaque noeud, car le graphe est une liste de bord (le graphe g contient chaque sommet).

numv = 1000
print('vertices =', numv)
G = [Vertex(i) for i in range(numv)]

def dfs(source):
  s = []
  visited = set()
  s.append((source,None))
  time = 1
  space = 0
  while s:
    time += 1
    current, index = s.pop()
    if index is None:
      visited.add(current)
      index = 0
    # vertex has all edges possible: G is a complete graph
    while index < len(G) and G[index] in visited:
      index += 1
    if index < len(G):
      s.append((current,index+1))
      s.append((G[index], None))
    space = max(space, len(s))
  print('time =', time, '\nspace =', space)

dfs(G[0])

sortie:

time = 2000 
space = 1000

noter que time ici mesure V opérations et non E . La valeur est numv *2 parce que chaque sommet est considéré deux fois, une fois à la découverte et une fois à la finition.

2
répondu bain 2017-04-13 12:19:15

en fait, stack n'est pas en mesure de gérer le temps de découverte et le temps de finition, si nous voulons implémenter DFS avec stack, et si nous voulons gérer le temps de découverte et le temps de finition, nous aurions besoin de recourir à une autre pile d'enregistreurs, mon implémentation est montrée ci-dessous, avoir un test correct, ci-dessous est pour le cas-1, le cas-2 et le cas-3 graph.

case-1 case-2 case-3

from collections import defaultdict

class Graph(object):

    adj_list = defaultdict(list)

    def __init__(self, V):
        self.V = V

    def add_edge(self,u,v):
        self.adj_list[u].append(v)

    def DFS(self):
        visited = []
        instack = []
        disc = []
        fini = []
        for t in range(self.V):
            visited.append(0)
            disc.append(0)
            fini.append(0)
            instack.append(0)

        time = 0
        for u_ in range(self.V):
            if (visited[u_] != 1):
                stack = []
                stack_recorder = []
                stack.append(u_)
                while stack:
                    u = stack.pop()
                    visited[u] = 1
                    time+=1
                    disc[u] = time
                    print(u)
                    stack_recorder.append(u)
                    flag = 0
                    for v in self.adj_list[u]:
                        if (visited[v] != 1):
                            flag = 1
                            if instack[v]==0:
                                stack.append(v)
                            instack[v]= 1



                    if flag == 0:
                        time+=1
                        temp = stack_recorder.pop()
                        fini[temp] = time
                while stack_recorder:
                    temp = stack_recorder.pop()
                    time+=1
                    fini[temp] = time
        print(disc)
        print(fini)

if __name__ == '__main__':

    V = 6
    G = Graph(V)

#==============================================================================
# #for case 1
#     G.add_edge(0,1)
#     G.add_edge(0,2)
#     G.add_edge(1,3)
#     G.add_edge(2,1)
#     G.add_edge(3,2) 
#==============================================================================

#==============================================================================
# #for case 2
#     G.add_edge(0,1)
#     G.add_edge(0,2)
#     G.add_edge(1,3)
#     G.add_edge(3,2)  
#==============================================================================

#for case 3
    G.add_edge(0,3)    
    G.add_edge(0,1)

    G.add_edge(1,4)
    G.add_edge(2,4)
    G.add_edge(2,5)
    G.add_edge(3,1)
    G.add_edge(4,3)
    G.add_edge(5,5)    


    G.DFS()   
2
répondu K.Wanter 2017-05-11 06:06:13

je pense que vous devez utiliser un tableau booléen visited[n] pour vérifier si le noeud courant est visité ou pas plus tôt.

1
répondu Vikram Bhat 2014-02-03 08:21:04

un algorithme récursif fonctionne très bien pour DFS alors que nous essayons de plonger aussi profondément que nous le pouvons, c'est à dire. dès qu'on trouve un vertex Non exploré, on va explorer son premier voisin non exploré tout de suite. Vous devez sortir de la boucle for dès que vous trouvez le premier voisin non exploré.

for each neighbor w of v
   if w is not explored
       mark w as explored
       push w onto the stack
       BREAK out of the for loop
0
répondu Huy Hoang-Nguyen 2016-02-22 05:39:27

je pense que c'est une DFS optimisée en ce qui concerne l'espace-corrigez-moi si je me trompe.

s = stack
s.push(initial node)
add initial node to visited
while s is not empty:
    v = s.peek()
    if for all E(v,u) there is one unvisited u:
        mark u as visited
        s.push(u)
    else 
        s.pop
0
répondu Bill Cheng 2016-05-28 22:57:37

utilisant la pile et implémentant comme fait par la pile d'appel dans le processus de récursion -

l'idée est de pousser un vertex dans la pile, puis de pousser son vertex adjacent qui est stocké dans une liste de contiguïté à l'index du vertex et puis de continuer ce processus jusqu'à ce que nous ne puissions pas aller plus loin dans le graphe, maintenant si nous ne pouvons pas aller de l'avant dans le graphe, alors nous allons supprimer le vertex qui est actuellement sur le dessus de la pile car il est incapable de nous emmener sur un sommet quelconque qui est inconnu.

maintenant, en utilisant stack nous nous occupons du point que le vertex n'est retiré de la pile que lorsque tous les vertices qui peuvent être explorés à partir du vertex courant ont été visités, ce qui était fait par le processus de récursion automatiquement.

pour ex -

voir l'exemple du graphique ici.

( 0 ( 1 ( 2 ( 4 4 ) 2 ) ( 3 3 ) 1 ) 0 ) ( 6 ( 5 5 ) ( 7 7 ) 6 )

ci-dessus entre parenthèses indiquent l'ordre dans lequel le sommet est ajouté sur la pile et retiré de la pile, de sorte qu'une parenthèse pour un sommet est fermé uniquement lorsque tous les sommets peuvent être visités ont été fait.

(ici j'ai utilisé la représentation de la liste de contiguïté et implémenté comme un vecteur de liste (vecteur > AdjList) en utilisant C++ STL)

void DFSUsingStack() {
    /// we keep a check of the vertices visited, the vector is set to false for all vertices initially.
    vector<bool> visited(AdjList.size(), false);

    stack<int> st;

    for(int i=0 ; i<AdjList.size() ; i++){
        if(visited[i] == true){
            continue;
        }
        st.push(i);
        cout << i << '\n';
        visited[i] = true;
        while(!st.empty()){
            int curr = st.top();
            for(list<int> :: iterator it = AdjList[curr].begin() ; it != AdjList[curr].end() ; it++){
                if(visited[*it] == false){
                    st.push(*it);
                    cout << (*it) << '\n';
                    visited[*it] = true;
                    break;
                }
            }
            /// We can move ahead from current only if a new vertex has been added on the top of the stack.
            if(st.top() != curr){
                continue;
            }
            st.pop();
        }
    }
}
0
répondu Aman 2016-07-19 15:23:39

le Code Java suivant sera pratique: -

private void DFS(int v,boolean[] visited){
    visited[v]=true;
    Stack<Integer> S = new Stack<Integer>();
    S.push(v);
    while(!S.isEmpty()){
        int v1=S.pop();     
        System.out.println(adjLists.get(v1).name);
        for(Neighbor nbr=adjLists.get(v1).adjList; nbr != null; nbr=nbr.next){
             if (!visited[nbr.VertexNum]){
                 visited[nbr.VertexNum]=true;
                 S.push(nbr.VertexNum);
             }
        }
    }
}
public void dfs() {
    boolean[] visited = new boolean[adjLists.size()];
    for (int v=0; v < visited.length; v++) {
        if (!visited[v])/*This condition is for Unconnected Vertices*/ {

            System.out.println("\nSTARTING AT " + adjLists.get(v).name);
            DFS(v, visited);
        }
    }
}
0
répondu codeislife 2016-12-29 06:14:51

beaucoup de gens diront que la DFS non récursive est juste BFS avec une pile plutôt qu'une queue. Ce n'est pas exact, laisse-moi t'expliquer un peu plus.

DFS récursifs

DFS récursif utilise la pile d'appels pour maintenir l'état, ce qui signifie que vous ne gérez pas une pile séparée vous-même.

Cependant, pour un grand graphe, DFS récursive (ou toute fonction récursive qui est) peut entraîner une profonde récursion, qui peut écraser votre problème avec un un débordement de pile (pas ce site, la vraie chose ).

DFS Non récursifs

DFS n'est pas la même que la BFS. Il a une utilisation d'espace différente, mais si vous l'implémentez comme BFS, mais en utilisant une pile plutôt qu'une file, vous utiliserez plus d'espace que DFS non récursive.

pourquoi plus d'espace?

considérez ceci:

// From non-recursive "DFS"
for (auto i&: adjacent) {
    if (!visited(i)) {
        stack.push(i);
    }
}

et comparez - le avec ceci:

// From recursive DFS
for (auto i&: adjacent) {
    if (!visited(i)) {
        dfs(i);
    }
}

dans le premier morceau de code vous mettez tous les noeuds adjacents dans la pile avant itération au prochain sommet adjacent et qui a un coût d'espace. Si le graphique est importante, cela peut faire une différence significative.

Que faire alors?

si vous décidez de résoudre le problème d'espace en itérant au-dessus de la liste adjacente encore une fois après avoir popping la pile, cela va ajouter le coût de complexité de temps.

une solution est d'ajouter des articles à la pile un par un, comme vous le visitez. Pour ce faire, vous pouvez enregistrer un itérateur dans la pile pour reprendre l'itération après popping.

manière Paresseuse

en C/C++, une approche paresseuse est de compiler votre programme avec une plus grande taille de pile et d'augmenter la taille de pile via ulimit , mais c'est vraiment nul. En Java, vous pouvez définir la taille de la pile comme paramètre JVM.

0
répondu arboreal84 2017-04-30 04:02:15