@Bart Kiers Un arbre en général, à en juger par le tag.
biziclop
13
La première recherche en profondeur est un algorithme récursif. Les réponses ci-dessous explorent les nœuds de manière récursive, ils n'utilisent tout simplement pas la pile d'appels du système pour faire leur récursivité, et utilisent plutôt une pile explicite.
Null Set
8
@Null Set Non, c'est juste une boucle. Selon votre définition, chaque programme informatique est récursif. (Ce qui, dans un certain sens du mot, ils sont.)
biziclop
1
@Null Set: Un arbre est également une structure de données récursive.
Gumbo
2
@MuhammadUmer le principal avantage des approches itératives par rapport aux approches récursives lorsque l'itération est considérée comme moins lisible est que vous pouvez éviter les contraintes de taille maximale de pile / profondeur de récursivité que la plupart des systèmes / langages de programmation implémentent pour protéger la pile. Avec une pile en mémoire, votre pile n'est limitée que par la quantité de mémoire que votre programme est autorisé à consommer, ce qui permet généralement une pile beaucoup plus grande que la taille maximale de la pile d'appels.
+1 pour avoir noté à quel point les deux sont similaires lorsqu'ils sont effectués de manière non récursive (comme s'ils étaient radicalement différents lorsqu'ils sont récursifs, mais quand même ...)
corsiKa
3
Et puis pour ajouter à la symétrie, si vous utilisez une file d'attente de priorité minimale comme frange à la place, vous avez un chercheur de chemin le plus court à source unique.
Mark Peters
10
BTW, la .first()fonction supprime également l'élément de la liste. Comme shift()dans de nombreuses langues. pop()fonctionne également et renvoie les nœuds enfants dans l'ordre de droite à gauche au lieu de gauche à droite.
Ariel
5
OMI, l'algo DFS est légèrement incorrect. Imaginez 3 sommets tous connectés les uns aux autres. Les progrès doivent être: gray(1st)->gray(2nd)->gray(3rd)->blacken(3rd)->blacken(2nd)->blacken(1st). Mais votre code produit: gray(1st)->gray(2nd)->gray(3rd)->blacken(2nd)->blacken(3rd)->blacken(1st).
batman
3
@learner Je comprends peut-être mal votre exemple, mais s'ils sont tous connectés les uns aux autres, ce n'est pas vraiment un arbre.
biziclop
40
Vous utiliseriez une pile contenant les nœuds qui n'ont pas encore été visités:
stack.push(root)
while !stack.isEmpty() do
node = stack.pop()
for each node.childNodes do
stack.push(stack)
endfor
// …
endwhile
@Gumbo Je me demande si c'est un graphique avec des cycyles. Cela peut-il fonctionner? Je pense que je peux simplement éviter d'ajouter un nœud dulpliqué à la pile et cela peut fonctionner. Ce que je vais faire est de marquer tous les voisins du nœud qui sont sortis et d'ajouter un if (nodes are not marked)pour juger s'il est approprié d'être poussé vers la pile. Cela peut-il fonctionner?
Alston
1
@Stallman Vous pouvez vous souvenir des nœuds que vous avez déjà visités. Si vous visitez uniquement les nœuds que vous n'avez pas encore visités, vous ne ferez aucun cycle.
Gumbo
@Gumbo Que voulez-vous dire par doing cycles? Je pense que je veux juste l'ordre de DFS. Est-ce vrai ou pas, merci.
Alston
Je voulais juste souligner que l'utilisation d'une pile (LIFO) signifie une première traversée en profondeur. Si vous voulez utiliser la largeur d'abord, optez plutôt pour une file d'attente (FIFO).
Per Lundberg
3
Il convient de noter que pour avoir un code équivalent à la réponse @biziclop la plus populaire, vous devez pousser les notes enfants dans l'ordre inverse ( for each node.childNodes.reverse() do stack.push(stack) endfor). C'est aussi probablement ce que vous voulez. Belle explication pourquoi c'est comme ça dans cette vidéo: youtube.com/watch?v=cZPXfl_tUkA endfor
Mariusz Pawelski
32
Si vous avez des pointeurs vers des nœuds parents, vous pouvez le faire sans mémoire supplémentaire.
def dfs(root):
node = root
while True:
visit(node)
if node.first_child:
node = node.first_child # walk down
else:
while not node.next_sibling:
if node is root:
return
node = node.parent # walk up ...
node = node.next_sibling # ... and right
Notez que si les nœuds enfants sont stockés sous forme de tableau plutôt que via des pointeurs frères, le frère suivant peut être trouvé sous la forme:
C'est une bonne solution car elle n'utilise pas de mémoire supplémentaire ni de manipulation d'une liste ou d'une pile (quelques bonnes raisons d'éviter la récursivité). Cependant, cela n'est possible que si les nœuds de l'arborescence ont des liens vers leurs parents.
joeytwiddle
Je vous remercie. Cet algorithme est génial. Mais dans cette version, vous ne pouvez pas supprimer la mémoire du nœud dans la fonction de visite. Cet algorithme peut convertir l'arborescence en liste à liaison unique en utilisant le pointeur "first_child". Ensuite, vous pouvez le parcourir et libérer la mémoire du nœud sans récursivité.
puchu
6
"Si vous avez des pointeurs vers les nœuds parents, vous pouvez le faire sans mémoire supplémentaire": le stockage du pointeur vers les nœuds parents utilise de la "mémoire supplémentaire" ...
rptr
1
@ rptr87 si ce n'était pas clair, sans mémoire supplémentaire en dehors de ces pointeurs.
Abhinav Gauniyal
Cela échouerait pour les arbres partiels où le nœud n'est pas la racine absolue, mais peut être facilement corrigé par while not node.next_sibling or node is root:.
Basel Shishani
5
Utilisez une pile pour suivre vos nœuds
Stack<Node> s;
s.prepend(tree.head);
while(!s.empty) {
Node n = s.poll_front // gets first node
// do something with q?
for each child of n: s.prepend(child)
}
@Dave O. Non, car vous repoussez les enfants du nœud visité devant tout ce qui est déjà là.
biziclop
J'ai dû mal interpréter la sémantique de push_back alors.
Dave O.
@Dave vous avez un très bon point. Je pensais qu'il fallait "repousser le reste de la file d'attente" et non "pousser vers l'arrière". Je vais éditer de manière appropriée.
corsiKa
Si vous poussez vers l'avant, cela devrait être une pile.
vol
@Timmy ouais, je ne sais pas trop à quoi je pensais. @quasiverse Nous considérons normalement une file d'attente comme une file d'attente FIFO. Une pile est définie comme une file d'attente LIFO.
corsiKa
4
Bien que «utiliser une pile» puisse fonctionner comme la réponse à une question d'interview artificielle, en réalité, il s'agit simplement de faire explicitement ce qu'un programme récursif fait dans les coulisses.
La récursivité utilise la pile intégrée des programmes. Lorsque vous appelez une fonction, il pousse les arguments de la fonction sur la pile et lorsque la fonction retourne, il le fait en sautant la pile de programmes.
Avec la différence importante que la pile de threads est sévèrement limitée et que l'algorithme non récursif utiliserait le tas beaucoup plus évolutif.
Yam Marcovic
1
Ce n'est pas simplement une situation artificielle. J'ai utilisé des techniques comme celle-ci à quelques reprises en C # et JavaScript pour gagner des performances significatives par rapport aux équivalents d'appel récursifs existants. Il arrive souvent que la gestion de la récursion avec une pile au lieu d'utiliser la pile d'appels soit beaucoup plus rapide et moins gourmande en ressources. Il y a beaucoup de frais généraux impliqués dans le placement d'un contexte d'appel sur une pile par rapport au programmeur étant capable de prendre des décisions pratiques sur ce qu'il faut placer sur une pile personnalisée.
Jason Jackson
4
Une implémentation ES6 basée sur une excellente réponse biziclops:
PreOrderTraversal is same as DFS in binary tree. You can do the same recursion
taking care of Stack as below.
public void IterativePreOrder(Tree root)
{
if (root == null)
return;
Stack s<Tree> = new Stack<Tree>();
s.Push(root);
while (s.Count != 0)
{
Tree b = s.Pop();
Console.Write(b.Data + " ");
if (b.Right != null)
s.Push(b.Right);
if (b.Left != null)
s.Push(b.Left);
}
}
La logique générale est de pousser un nœud (à partir de la racine) dans la valeur Stack, Pop () it et Print (). Ensuite, s'il a des enfants (gauche et droite), poussez-les dans la pile - appuyez d'abord sur Droite pour que vous visitiez d'abord Left child (après avoir visité le nœud lui-même). Lorsque la pile est vide (), vous aurez visité tous les nœuds en pré-commande.
classNode{constructor(name, childNodes){this.name = name;this.childNodes = childNodes;this.visited =false;}}function*dfs(s){let stack =[];
stack.push(s);
stackLoop:while(stack.length){let u = stack[stack.length -1];// peekif(!u.visited){
u.visited =true;// grey - visitedyield u;}for(let v of u.childNodes){if(!v.visited){
stack.push(v);continue stackLoop;}}
stack.pop();// black - all reachable descendants were processed }}
Il s'écarte du DFS non récursif typique pour détecter facilement quand tous les descendants joignables d'un nœud donné ont été traités et pour maintenir le chemin actuel dans la liste / pile.
Supposons que vous souhaitiez exécuter une notification lorsque chaque nœud d'un graphique est visité. L'implémentation récursive simple est:
void DFSRecursive(Node n, Set<Node> visited) {
visited.add(n);
for (Node x : neighbors_of(n)) { // iterate over all neighbors
if (!visited.contains(x)) {
DFSRecursive(x, visited);
}
}
OnVisit(n); // callback to say node is finally visited, after all its non-visited neighbors
}
Ok, maintenant vous voulez une implémentation basée sur la pile car votre exemple ne fonctionne pas. Des graphes complexes peuvent par exemple faire exploser la pile de votre programme et vous devez implémenter une version non récursive. Le plus gros problème est de savoir quand émettre une notification.
Le pseudo-code suivant fonctionne (mélange de Java et C ++ pour la lisibilité):
void DFS(Node root) {
Set<Node> visited;
Set<Node> toNotify; // nodes we want to notify
Stack<Node> stack;
stack.add(root);
toNotify.add(root); // we won't pop nodes from this until DFS is done
while (!stack.empty()) {
Node current = stack.pop();
visited.add(current);
for (Node x : neighbors_of(current)) {
if (!visited.contains(x)) {
stack.add(x);
toNotify.add(x);
}
}
}
// Now issue notifications. toNotifyStack might contain duplicates (will never
// happen in a tree but easily happens in a graph)
Set<Node> notified;
while (!toNotify.empty()) {
Node n = toNotify.pop();
if (!toNotify.contains(n)) {
OnVisit(n); // issue callback
toNotify.add(n);
}
}
Cela semble compliqué mais la logique supplémentaire nécessaire pour émettre des notifications existe car vous devez notifier dans l'ordre inverse de la visite - DFS commence à la racine mais le notifie en dernier, contrairement à BFS qui est très simple à implémenter.
Pour les coups de pied, essayez le graphique suivant: les nœuds sont s, t, v et w. les arêtes dirigées sont: s-> t, s-> v, t-> w, v-> w et v-> t. Exécutez votre propre implémentation de DFS et l'ordre dans lequel les nœuds doivent être visités doit être: w, t, v, s Une implémentation maladroite de DFS pourrait peut-être notifier t d'abord et cela indique un bogue. Une implémentation récursive de DFS atteindrait toujours w en dernier.
import java.util.*;
class Graph {
private List<List<Integer>> adj;
Graph(int numOfVertices) {
this.adj = new ArrayList<>();
for (int i = 0; i < numOfVertices; ++i)
adj.add(i, new ArrayList<>());
}
void addEdge(int v, int w) {
adj.get(v).add(w); // Add w to v's list.
}
void DFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(nodesToVisitIndex, s);// add the node to the HEAD of the unvisited nodes list.
}
}
System.out.println(nextChild);
}
}
void BFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(s);// add the node to the END of the unvisited node list.
}
}
System.out.println(nextChild);
}
}
public static void main(String args[]) {
Graph g = new Graph(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.addEdge(3, 1);
g.addEdge(3, 4);
System.out.println("Breadth First Traversal- starting from vertex 2:");
g.BFS(2);
System.out.println("Depth First Traversal- starting from vertex 2:");
g.DFS(2);
}}
sortie: Largeur première traversée - à partir du sommet 2: 2 0 3 1 4 Profondeur première traversée - à partir du sommet 2: 2 3 4 1 0
En utilisant uniquement des constructions de base: variables, tableaux, if, while et for
Fonctions getNode(id)etgetChildren(id)
En supposant un nombre connu de nœuds N
REMARQUE: j'utilise l'indexation de tableau à partir de 1, pas de 0.
Largeur d'abord
S = Array(N)
S[1] = 1; // root id
cur = 1;
last = 1
while cur <= last
id = S[cur]
node = getNode(id)
children = getChildren(id)
n = length(children)
for i = 1..n
S[ last+i ] = children[i]
end
last = last+n
cur = cur+1
visit(node)
end
La profondeur d'abord
S = Array(N)
S[1] = 1; // root id
cur = 1;
while cur > 0
id = S[cur]
node = getNode(id)
children = getChildren(id)
n = length(children)
for i = 1..n
// assuming children are given left-to-right
S[ cur+i-1 ] = children[ n-i+1 ]
// otherwise
// S[ cur+i-1 ] = children[i]
end
cur = cur+n-1
visit(node)
end
Voici un lien vers un programme java montrant DFS suivant à la fois des méthodes récursives et non récursives et calculant également l' heure de découverte et de fin , mais pas de laleling des bords.
public void DFSIterative() {
Reset();
Stack<Vertex> s = new Stack<>();
for (Vertex v : vertices.values()) {
if (!v.visited) {
v.d = ++time;
v.visited = true;
s.push(v);
while (!s.isEmpty()) {
Vertex u = s.peek();
s.pop();
boolean bFinished = true;
for (Vertex w : u.adj) {
if (!w.visited) {
w.visited = true;
w.d = ++time;
w.p = u;
s.push(w);
bFinished = false;
break;
}
}
if (bFinished) {
u.f = ++time;
if (u.p != null)
s.push(u.p);
}
}
}
}
}
Je voulais juste ajouter mon implémentation python à la longue liste de solutions. Cet algorithme non récursif a des événements de découverte et terminés.
worklist =[root_node]
visited = set()while worklist:
node = worklist[-1]if node in visited:# Node is finished
worklist.pop()else:# Node is discovered
visited.add(node)for child in node.children:
worklist.append(child)
Réponses:
DFS:
BFS:
La symétrie des deux est assez cool.
Mise à jour: comme indiqué,
take_first()
supprime et renvoie le premier élément de la liste.la source
.first()
fonction supprime également l'élément de la liste. Commeshift()
dans de nombreuses langues.pop()
fonctionne également et renvoie les nœuds enfants dans l'ordre de droite à gauche au lieu de gauche à droite.gray(1st)->gray(2nd)->gray(3rd)->blacken(3rd)->blacken(2nd)->blacken(1st)
. Mais votre code produit:gray(1st)->gray(2nd)->gray(3rd)->blacken(2nd)->blacken(3rd)->blacken(1st)
.Vous utiliseriez une pile contenant les nœuds qui n'ont pas encore été visités:
la source
if (nodes are not marked)
pour juger s'il est approprié d'être poussé vers la pile. Cela peut-il fonctionner?doing cycles
? Je pense que je veux juste l'ordre de DFS. Est-ce vrai ou pas, merci.for each node.childNodes.reverse() do stack.push(stack) endfor
). C'est aussi probablement ce que vous voulez. Belle explication pourquoi c'est comme ça dans cette vidéo: youtube.com/watch?v=cZPXfl_tUkA endforSi vous avez des pointeurs vers des nœuds parents, vous pouvez le faire sans mémoire supplémentaire.
Notez que si les nœuds enfants sont stockés sous forme de tableau plutôt que via des pointeurs frères, le frère suivant peut être trouvé sous la forme:
la source
while not node.next_sibling or node is root:
.Utilisez une pile pour suivre vos nœuds
la source
Bien que «utiliser une pile» puisse fonctionner comme la réponse à une question d'interview artificielle, en réalité, il s'agit simplement de faire explicitement ce qu'un programme récursif fait dans les coulisses.
La récursivité utilise la pile intégrée des programmes. Lorsque vous appelez une fonction, il pousse les arguments de la fonction sur la pile et lorsque la fonction retourne, il le fait en sautant la pile de programmes.
la source
Une implémentation ES6 basée sur une excellente réponse biziclops:
la source
La logique générale est de pousser un nœud (à partir de la racine) dans la valeur Stack, Pop () it et Print (). Ensuite, s'il a des enfants (gauche et droite), poussez-les dans la pile - appuyez d'abord sur Droite pour que vous visitiez d'abord Left child (après avoir visité le nœud lui-même). Lorsque la pile est vide (), vous aurez visité tous les nœuds en pré-commande.
la source
DFS non récursif utilisant des générateurs ES6
Il s'écarte du DFS non récursif typique pour détecter facilement quand tous les descendants joignables d'un nœud donné ont été traités et pour maintenir le chemin actuel dans la liste / pile.
la source
Supposons que vous souhaitiez exécuter une notification lorsque chaque nœud d'un graphique est visité. L'implémentation récursive simple est:
Ok, maintenant vous voulez une implémentation basée sur la pile car votre exemple ne fonctionne pas. Des graphes complexes peuvent par exemple faire exploser la pile de votre programme et vous devez implémenter une version non récursive. Le plus gros problème est de savoir quand émettre une notification.
Le pseudo-code suivant fonctionne (mélange de Java et C ++ pour la lisibilité):
Cela semble compliqué mais la logique supplémentaire nécessaire pour émettre des notifications existe car vous devez notifier dans l'ordre inverse de la visite - DFS commence à la racine mais le notifie en dernier, contrairement à BFS qui est très simple à implémenter.
Pour les coups de pied, essayez le graphique suivant: les nœuds sont s, t, v et w. les arêtes dirigées sont: s-> t, s-> v, t-> w, v-> w et v-> t. Exécutez votre propre implémentation de DFS et l'ordre dans lequel les nœuds doivent être visités doit être: w, t, v, s Une implémentation maladroite de DFS pourrait peut-être notifier t d'abord et cela indique un bogue. Une implémentation récursive de DFS atteindrait toujours w en dernier.
la source
Exemple complet de code de travail, sans pile:
sortie: Largeur première traversée - à partir du sommet 2: 2 0 3 1 4 Profondeur première traversée - à partir du sommet 2: 2 3 4 1 0
la source
Vous pouvez utiliser une pile. J'ai implémenté des graphiques avec Adjacency Matrix:
la source
Itératif DFS en Java:
la source
http://www.youtube.com/watch?v=zLZhSSXAwxI
Je viens de regarder cette vidéo et est sorti avec la mise en œuvre. Cela me semble facile à comprendre. Veuillez critiquer cela.
la source
En utilisant
Stack
, voici les étapes à suivre: Poussez le premier sommet de la pile puis,Voici le programme Java en suivant les étapes ci-dessus:
la source
la source
Pseudo-code basé sur la réponse de @ biziclop:
getNode(id)
etgetChildren(id)
N
Largeur d'abord
La profondeur d'abord
la source
Voici un lien vers un programme java montrant DFS suivant à la fois des méthodes récursives et non récursives et calculant également l' heure de découverte et de fin , mais pas de laleling des bords.
Source complète ici .
la source
Je voulais juste ajouter mon implémentation python à la longue liste de solutions. Cet algorithme non récursif a des événements de découverte et terminés.
la source