web-dev-qa-db-fra.com

Comment implémenter la recherche en profondeur en premier pour le graphique avec une approche non récursive

Eh bien, j'ai passé beaucoup de temps sur cette question. Cependant, je ne peux trouver des solutions qu'avec des méthodes non récursives pour un arbre: Non récursif pour l'arbre , ou méthode récursive pour le graphique, Récursif pour le graphique .

Et beaucoup de tutoriels (je ne fournis pas ces liens ici) ne fournissent pas non plus les approches. Ou le tutoriel est totalement incorrect. Aidez-moi, s'il vous plaît.

Mise à jour:

C'est vraiment difficile à décrire:

Si j'ai un graphique non orienté:

               1
             / |  \
            4  |   2
               3 /

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

À l'étape: 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 éclaté les nœuds, nous avons obtenu 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?

31
Alston

Un DFS sans récursivité est fondamentalement le même que BFS - mais utilisez un stack au lieu d'une file d'attente comme structure de données.

Le thread DFS itératif vs DFS récursif et ordre des différents éléments gère les deux approches et la différence entre elles (et il y en a! Vous ne traverserez pas les nœuds dans le même ordre!)

L'algorithme pour l'approche itérative est fondamentalement:

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)
45
amit

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 nœud de départ et que 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}

Ainsi, l'application de l'algorithme poussant les voisins de 1 dans l'ordre 2, 4, 3 entraîne 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 car celui qui est visité le premier poussera l'autre, qui n'est pas encore visité, ainsi que 1 qui a été visité.

16
Patricia Shanahan

La logique DFS doit être:

1) si le nœud actuel n'est pas visité, visitez le nœud et marquez-le comme visité
2) pour tous ses voisins qui n'ont pas été visités, Poussez-les dans la pile

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

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

et voici le DFS sans récursivité:

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

Nous pouvons utiliser la même logique pour faire DFS récursivement, cloner un graphe, etc.

6
jeantimex

En fait, la pile n'est pas bien capable de gérer l'heure de découverte et l'heure de fin, si nous voulons implémenter DFS avec la pile et que nous voulons traiter l'heure de découverte et l'heure de fin, 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 graphique cas-1, cas-2 et cas-3.

case-1case-2case-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
K.Wanter

d'accord. si vous cherchez toujours un code Java code

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
The New One

Code Python. La complexité temporelle est [~ # ~] o [~ # ~] ( [~ # ~] v [ ~ # ~] + [~ # ~] e [~ # ~] ) où [~ # ~] v [~ # ~] et [~ # ~] e [~ # ~] sont le nombre de sommets et d'arêtes respectivement. La complexité de l'espace est O ( [~ # ~] v [~ # ~] ) en raison du pire des cas où il y a un chemin qui contient chaque sommet sans aucun retour en arrière (ie le chemin de recherche est un chaîne linéaire ).

La pile stocke des tuples de la forme (vertex, vertex_Edge_index) afin que le DFS puisse être repris à partir d'un sommet particulier sur l'Edge immédiatement après le dernier Edge qui a été traité à partir de ce sommet (tout comme la pile d'appel de fonction d'un DFS récursif).

L'exemple de code utilise un digraphe complet où chaque sommet est connecté à chaque autre sommet. Par conséquent, il n'est pas nécessaire de stocker une liste Edge explicite pour chaque nœud, car le graphique est une liste Edge (le graphique [~ # ~] 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

Notez que le temps mesure ici [~ # ~] v [~ # ~] opérations et non [~ # ~] e [~ # ~] . La valeur est numv * 2 car chaque sommet est considéré deux fois, une fois à la découverte et une fois à la fin.

2
bain

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

2
bobah

Je pense que vous devez utiliser un visited[n] tableau booléen pour vérifier si le nœud actuel est visité ou pas plus tôt.

1
Vikram Bhat

Beaucoup de gens diront que le DFS non récursif n'est qu'un BFS avec une pile plutôt qu'une file d'attente. Ce n'est pas exact, laissez-moi vous expliquer un peu plus.

DFS récursif

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

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

DFS non récursif

DFS n'est pas identique à 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 d'attente, vous utiliserez plus d'espace que DFS non récursif.

Pourquoi plus d'espace?

Considère 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 placez tous les nœuds adjacents dans la pile avant d'itérer vers le sommet adjacent suivant et cela a un coût d'espace. Si le graphique est grand, il peut faire une différence significative.

Que faire alors?

Si vous décidez de résoudre le problème d'espace en itérant à nouveau sur la liste de contiguïté après avoir éclaté la pile, cela va ajouter un coût de complexité en temps.

Une solution consiste à ajouter des éléments à la pile un par un, au fur et à mesure que vous les visitez. Pour ce faire, vous pouvez enregistrer un itérateur dans la pile pour reprendre l'itération après l'éclatement.

Chemin paresseux

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

1
arboreal84

Utilisation de la pile et implémentation comme le fait la pile des appels dans le processus de récursivité -

L'idée est de pousser un sommet dans la pile, puis de pousser son sommet adjacent à celui-ci qui est stocké dans une liste de contiguïté à l'index du sommet, puis de continuer ce processus jusqu'à ce que nous ne puissions plus avancer dans le graphique, maintenant si nous ne pouvons pas avancer dans le graphique puis nous supprimerons le sommet qui se trouve actuellement au sommet de la pile car il ne peut nous emmener sur aucun sommet non visité.

Maintenant, en utilisant la pile, nous prenons soin du point que le sommet n'est supprimé de la pile que lorsque tous les sommets qui peuvent être explorés à partir du sommet actuel ont été visités, ce qui était fait automatiquement par le processus de récursivité.

par ex -

Voir l'exemple de graphique ici.

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

Les parenthèses ci-dessus montrent l'ordre dans lequel le sommet est ajouté sur la pile et retiré de la pile, donc une parenthèse pour un sommet n'est fermée que lorsque tous les sommets qui peuvent être visités à partir de celui-ci ont été effectués.

(Ici, j'ai utilisé la représentation Adjacency List et implémenté comme vecteur de liste (vector> 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
Aman

Un algorithme récursif fonctionne très bien pour DFS car nous essayons de plonger aussi profondément que possible, c'est-à-dire. dès que nous trouverons un sommet non exploré, nous allons explorer tout de suite son PREMIER voisin non exploré. Vous devez interrompre 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
Huy Hoang-Nguyen

Le code Java Java sera utile: -

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
codeislife

Je pense que c'est un DFS optimisé concernant 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
Bill Cheng