Supposons que vous souhaitiez implémenter une recherche approfondie d'un arbre binaire de manière récursive . Comment vous y prendriez-vous?
Est-il possible d'utiliser uniquement la pile d'appels comme stockage auxiliaire?
(J'imagine qu'il s'agit simplement d'un exercice de réflexion ou même d'un tour de passe-passe ou d'une question d'entrevue, mais je suppose que je pourrais imaginer un scénario étrange dans lequel vous ne disposez d'aucun espace de mémoire pour une raison quelconque [une très mauvaise coutume gestionnaire de mémoire? Des problèmes bizarres d’exécution/système d’exploitation?] alors que vous avez toujours accès à la pile ...)
La traversée en largeur d'abord utilise traditionnellement une file d'attente, pas une pile. La nature d'une file d'attente et d'une pile étant assez opposée, essayer d'utiliser la pile d'appels (qui est une pile, d'où son nom) comme stockage auxiliaire (une file d'attente) est pratiquement voué à l'échec, à moins que vous ne le fassiez Quelque chose de ridicule avec la pile d'appels que vous ne devriez pas être.
De la même manière, la nature de toute récursion que vous essayez d'implémenter consiste essentiellement à ajouter une pile à l'algorithme. Ainsi, la première recherche sur une arborescence binaire n’est plus très étendue et, par conséquent, le temps d’exécution et ainsi de suite pour BFS traditionnel ne s’appliquent plus complètement. Bien sûr, vous pouvez toujours transformer n'importe quelle boucle en appel récursif, mais ce n'est pas une récursion significative.
Cependant, il existe des moyens, comme d'autres l'ont démontré, d'implémenter quelque chose qui suit la sémantique de BFS à un certain coût. Si le coût de la comparaison coûte cher mais que la traversée des noeuds est peu coûteuse, alors, comme @Simon Buchan did, vous pouvez simplement exécuter une recherche itérative en profondeur d'abord, en ne traitant que les feuilles. Cela signifierait qu'aucune file d'attente croissante ne serait stockée dans le segment de mémoire, mais simplement une variable de profondeur locale, et que des piles seraient construites à maintes reprises sur la pile d'appels au fur et à mesure que l'arbre serait traversé. Et comme @Patrick remarque, une arborescence binaire adossée à un tableau est généralement stockée dans l'ordre de traversée par ordre de priorité largeur d'abord; par conséquent, une recherche en largeur par dessus serait triviale, sans nécessiter de file d'attente auxiliaire.
Si vous utilisez un tableau pour sauvegarder l'arborescence binaire, vous pouvez déterminer le prochain nœud de manière algébrique. Si i
est un nœud, ses enfants peuvent être trouvés à 2i + 1
(pour le nœud de gauche) et 2i + 2
(pour le nœud de droite). Le prochain voisin d'un nœud est donné par i + 1
, sauf si i
est une puissance de 2
Voici le pseudocode pour une implémentation très naïve de la recherche en largeur d'abord sur un arbre de recherche binaire basé sur un tableau. Cela suppose un tableau de taille fixe et donc une arborescence de profondeur fixe. Il examinera les nœuds sans parent et pourrait créer une pile trop lourde.
bintree-bfs(bintree, elt, i)
if (i == LENGTH)
return false
else if (bintree[i] == elt)
return true
else
return bintree-bfs(bintree, elt, i+1)
Je ne pouvais pas trouver un moyen de le faire complètement récursif (sans structure de données auxiliaire). Mais si la file d'attente Q est passée par référence, vous pouvez alors avoir la fonction récursive suivante idiote:
BFS(Q)
{
if (|Q| > 0)
v <- Dequeue(Q)
Traverse(v)
foreach w in children(v)
Enqueue(Q, w)
BFS(Q)
}
La méthode suivante a utilisé un algorithme DFS pour obtenir tous les nœuds dans une profondeur particulière - ce qui revient à faire BFS pour ce niveau. Si vous trouvez la profondeur de l’arbre et que vous le faites pour tous les niveaux, les résultats seront les mêmes que pour un BFS.
public void PrintLevelNodes(Tree root, int level) {
if (root != null) {
if (level == 0) {
Console.Write(root.Data);
return;
}
PrintLevelNodes(root.Left, level - 1);
PrintLevelNodes(root.Right, level - 1);
}
}
for (int i = 0; i < depth; i++) {
PrintLevelNodes(root, i);
}
Trouver la profondeur d'un arbre est un jeu d'enfant:
public int MaxDepth(Tree root) {
if (root == null) {
return 0;
} else {
return Math.Max(MaxDepth(root.Left), MaxDepth(root.Right)) + 1;
}
}
Une récursivité simple BFS et DFS en Java:
Appuyez/proposez le nœud racine de l’arbre dans la pile/file d’attente et appelez ces fonctions.
public static void breadthFirstSearch(Queue queue) {
if (queue.isEmpty())
return;
Node node = (Node) queue.poll();
System.out.println(node + " ");
if (node.right != null)
queue.offer(node.right);
if (node.left != null)
queue.offer(node.left);
breadthFirstSearch(queue);
}
public static void depthFirstSearch(Stack stack) {
if (stack.isEmpty())
return;
Node node = (Node) stack.pop();
System.out.println(node + " ");
if (node.right != null)
stack.Push(node.right);
if (node.left != null)
stack.Push(node.left);
depthFirstSearch(stack);
}
La manière idiote:
template<typename T>
struct Node { Node* left; Node* right; T value; };
template<typename T, typename P>
bool searchNodeDepth(Node<T>* node, Node<T>** result, int depth, P pred) {
if (!node) return false;
if (!depth) {
if (pred(node->value)) {
*result = node;
}
return true;
}
--depth;
searchNodeDepth(node->left, result, depth, pred);
if (!*result)
searchNodeDepth(node->right, result, depth, pred);
return true;
}
template<typename T, typename P>
Node<T>* searchNode(Node<T>* node, P pred) {
Node<T>* result = NULL;
int depth = 0;
while (searchNodeDepth(node, &result, depth, pred) && !result)
++depth;
return result;
}
int main()
{
// a c f
// b e
// d
Node<char*>
a = { NULL, NULL, "A" },
c = { NULL, NULL, "C" },
b = { &a, &c, "B" },
f = { NULL, NULL, "F" },
e = { NULL, &f, "E" },
d = { &b, &e, "D" };
Node<char*>* found = searchNode(&d, [](char* value) -> bool {
printf("%s\n", value);
return !strcmp((char*)value, "F");
});
printf("found: %s\n", found->value);
return 0;
}
J'ai trouvé un très bel algorithme récursif (même fonctionnel) lié au parcours en largeur. Ce n'est pas mon idée, mais je pense que cela devrait être mentionné dans ce sujet.
Chris Okasaki explique son algorithme de numérotation premier à partir d'ICFP 2000 à l'adresse http://okasaki.blogspot.de/2008/07/breadth-first-numbering-algorithm-in.html très clairement avec seulement 3 images.
L'implémentation Scala de Debasish Ghosh, que j'ai trouvée à l'adresse http://debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html , est la suivante:
trait Tree[+T]
case class Node[+T](data: T, left: Tree[T], right: Tree[T]) extends Tree[T]
case object E extends Tree[Nothing]
def bfsNumForest[T](i: Int, trees: Queue[Tree[T]]): Queue[Tree[Int]] = {
if (trees.isEmpty) Queue.Empty
else {
trees.dequeue match {
case (E, ts) =>
bfsNumForest(i, ts).enqueue[Tree[Int]](E)
case (Node(d, l, r), ts) =>
val q = ts.enqueue(l, r)
val qq = bfsNumForest(i+1, q)
val (bb, qqq) = qq.dequeue
val (aa, tss) = qqq.dequeue
tss.enqueue[org.dg.collection.BFSNumber.Tree[Int]](Node(i, aa, bb))
}
}
}
def bfsNumTree[T](t: Tree[T]): Tree[Int] = {
val q = Queue.Empty.enqueue[Tree[T]](t)
val qq = bfsNumForest(1, q)
qq.dequeue._1
}
Voici une implémentation Scala 2.11.4 de BFS récursive. J'ai sacrifié l'optimisation de l'appel final pour des raisons de brièveté, mais la version TCOd est très similaire. Voir aussi le message de @snv .
import scala.collection.immutable.Queue
object RecursiveBfs {
def bfs[A](tree: Tree[A], target: A): Boolean = {
bfs(Queue(tree), target)
}
private def bfs[A](forest: Queue[Tree[A]], target: A): Boolean = {
forest.dequeueOption exists {
case (E, tail) => bfs(tail, target)
case (Node(value, _, _), _) if value == target => true
case (Node(_, l, r), tail) => bfs(tail.enqueue(List(l, r)), target)
}
}
sealed trait Tree[+A]
case class Node[+A](data: A, left: Tree[A], right: Tree[A]) extends Tree[A]
case object E extends Tree[Nothing]
}
Voici une implémentation en python:
graph = {'A': ['B', 'C'],
'B': ['C', 'D'],
'C': ['D'],
'D': ['C'],
'E': ['F'],
'F': ['C']}
def bfs(paths, goal):
if not paths:
raise StopIteration
new_paths = []
for path in paths:
if path[-1] == goal:
yield path
last = path[-1]
for neighbor in graph[last]:
if neighbor not in path:
new_paths.append(path + [neighbor])
yield from bfs(new_paths, goal)
for path in bfs([['A']], 'D'):
print(path)
Ce qui suit semble assez naturel pour moi, en utilisant Haskell. Itérer récursivement sur les niveaux de l'arbre (ici, je rassemble les noms dans une grosse chaîne ordonnée pour montrer le chemin à travers l'arbre):
data Node = Node {name :: String, children :: [Node]}
aTree = Node "r" [Node "c1" [Node "gc1" [Node "ggc1" []], Node "gc2" []] , Node "c2" [Node "gc3" []], Node "c3" [] ]
breadthFirstOrder x = levelRecurser [x]
where levelRecurser level = if length level == 0
then ""
else concat [name node ++ " " | node <- level] ++ levelRecurser (concat [children node | node <- level])
BFS pour un arbre binaire (ou n-aire) peut être effectué de manière récursive sans files d'attente comme suit (ici en Java):
public class BreathFirst {
static class Node {
Node(int value) {
this(value, 0);
}
Node(int value, int nChildren) {
this.value = value;
this.children = new Node[nChildren];
}
int value;
Node[] children;
}
static void breathFirst(Node root, Consumer<? super Node> printer) {
boolean keepGoing = true;
for (int level = 0; keepGoing; level++) {
keepGoing = breathFirst(root, printer, level);
}
}
static boolean breathFirst(Node node, Consumer<? super Node> printer, int depth) {
if (depth < 0 || node == null) return false;
if (depth == 0) {
printer.accept(node);
return true;
}
boolean any = false;
for (final Node child : node.children) {
any |= breathFirst(child, printer, depth - 1);
}
return any;
}
}
Un exemple de traversée imprimant les numéros 1 à 12 dans l'ordre croissant:
public static void main(String... args) {
// 1
// / | \
// 2 3 4
// / | | \
// 5 6 7 8
// / | | \
// 9 10 11 12
Node root = new Node(1, 3);
root.children[0] = new Node(2, 2);
root.children[1] = new Node(3);
root.children[2] = new Node(4, 2);
root.children[0].children[0] = new Node(5, 2);
root.children[0].children[1] = new Node(6);
root.children[2].children[0] = new Node(7, 2);
root.children[2].children[1] = new Node(8);
root.children[0].children[0].children[0] = new Node(9);
root.children[0].children[0].children[1] = new Node(10);
root.children[2].children[0].children[0] = new Node(11);
root.children[2].children[0].children[1] = new Node(12);
breathFirst(root, n -> System.out.println(n.value));
}
Voici court Scala solution:
def bfs(nodes: List[Node]): List[Node] = {
if (nodes.nonEmpty) {
nodes ++ bfs(nodes.flatMap(_.children))
} else {
List.empty
}
}
L'idée de utiliser la valeur de retour en tant qu'accumulateur convient bien . Peut être implémentée dans d'autres langages de manière similaire, assurez-vous simplement que votre fonction récursive processus liste de nœuds .
Liste des codes de test (en utilisant l’arborescence de test @marco):
import org.scalatest.FlatSpec
import scala.collection.mutable
class Node(val value: Int) {
private val _children: mutable.ArrayBuffer[Node] = mutable.ArrayBuffer.empty
def add(child: Node): Unit = _children += child
def children = _children.toList
override def toString: String = s"$value"
}
class BfsTestScala extends FlatSpec {
// 1
// / | \
// 2 3 4
// / | | \
// 5 6 7 8
// / | | \
// 9 10 11 12
def tree(): Node = {
val root = new Node(1)
root.add(new Node(2))
root.add(new Node(3))
root.add(new Node(4))
root.children(0).add(new Node(5))
root.children(0).add(new Node(6))
root.children(2).add(new Node(7))
root.children(2).add(new Node(8))
root.children(0).children(0).add(new Node(9))
root.children(0).children(0).add(new Node(10))
root.children(2).children(0).add(new Node(11))
root.children(2).children(0).add(new Node(12))
root
}
def bfs(nodes: List[Node]): List[Node] = {
if (nodes.nonEmpty) {
nodes ++ bfs(nodes.flatMap(_.children))
} else {
List.empty
}
}
"BFS" should "work" in {
println(bfs(List(tree())))
}
}
Sortie:
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
Soit v le sommet de départ
Soit G le graphe en question
Ce qui suit est le pseudo-code sans utiliser la file d'attente
Initially label v as visited as you start from v
BFS(G,v)
for all adjacent vertices w of v in G:
if vertex w is not visited:
label w as visited
for all adjacent vertices w of v in G:
recursively call BFS(G,w)
J'ai dû implémenter une traversée de tas qui sort dans un ordre BFS. Ce n'est pas réellement BFS mais accomplit la même tâche.
private void getNodeValue(Node node, int index, int[] array) {
array[index] = node.value;
index = (index*2)+1;
Node left = node.leftNode;
if (left!=null) getNodeValue(left,index,array);
Node right = node.rightNode;
if (right!=null) getNodeValue(right,index+1,array);
}
public int[] getHeap() {
int[] nodes = new int[size];
getNodeValue(root,0,nodes);
return nodes;
}
#include <bits/stdc++.h>
using namespace std;
#define Max 1000
vector <int> adj[Max];
bool visited[Max];
void bfs_recursion_utils(queue<int>& Q) {
while(!Q.empty()) {
int u = Q.front();
visited[u] = true;
cout << u << endl;
Q.pop();
for(int i = 0; i < (int)adj[u].size(); ++i) {
int v = adj[u][i];
if(!visited[v])
Q.Push(v), visited[v] = true;
}
bfs_recursion_utils(Q);
}
}
void bfs_recursion(int source, queue <int>& Q) {
memset(visited, false, sizeof visited);
Q.Push(source);
bfs_recursion_utils(Q);
}
int main(void) {
queue <int> Q;
adj[1].Push_back(2);
adj[1].Push_back(3);
adj[1].Push_back(4);
adj[2].Push_back(5);
adj[2].Push_back(6);
adj[3].Push_back(7);
bfs_recursion(1, Q);
return 0;
}
Voici mon code pour l'implémentation complètement récursive de la recherche approfondie d'un graphe bidirectionnel sans utiliser boucle et file d'attente.
public class Graph
{
public int V;
public LinkedList<Integer> adj[];
Graph(int v)
{
V = v;
adj = new LinkedList[v];
for (int i=0; i<v; ++i)
adj[i] = new LinkedList<>();
}
void addEdge(int v,int w)
{
adj[v].add(w);
adj[w].add(v);
}
public LinkedList<Integer> getAdjVerted(int vertex)
{
return adj[vertex];
}
public String toString()
{
String s = "";
for (int i=0;i<adj.length;i++)
{
s = s +"\n"+i +"-->"+ adj[i] ;
}
return s;
}
}
//BFS IMPLEMENTATION
public static void recursiveBFS(Graph graph, int vertex,boolean visited[], boolean isAdjPrinted[])
{
if (!visited[vertex])
{
System.out.print(vertex +" ");
visited[vertex] = true;
}
if(!isAdjPrinted[vertex])
{
isAdjPrinted[vertex] = true;
List<Integer> adjList = graph.getAdjVerted(vertex);
printAdjecent(graph, adjList, visited, 0,isAdjPrinted);
}
}
public static void recursiveBFS(Graph graph, List<Integer> vertexList, boolean visited[], int i, boolean isAdjPrinted[])
{
if (i < vertexList.size())
{
recursiveBFS(graph, vertexList.get(i), visited, isAdjPrinted);
recursiveBFS(graph, vertexList, visited, i+1, isAdjPrinted);
}
}
public static void printAdjecent(Graph graph, List<Integer> list, boolean visited[], int i, boolean isAdjPrinted[])
{
if (i < list.size())
{
if (!visited[list.get(i)])
{
System.out.print(list.get(i)+" ");
visited[list.get(i)] = true;
}
printAdjecent(graph, list, visited, i+1, isAdjPrinted);
}
else
{
recursiveBFS(graph, list, visited, 0, isAdjPrinted);
}
}
Voici une implémentation Python à parcours récursif BFS, fonctionnant pour un graphe sans cycle.
def bfs_recursive(level):
'''
@params level: List<Node> containing the node for a specific level.
'''
next_level = []
for node in level:
print(node.value)
for child_node in node.adjency_list:
next_level.append(child_node)
if len(next_level) != 0:
bfs_recursive(next_level)
class Node:
def __init__(self, value):
self.value = value
self.adjency_list = []
Voici une implémentation JavaScript qui simule la récurrence largeur première première avec la première profondeur. Je stocke les valeurs de nœud à chaque profondeur dans un tableau, à l'intérieur d'un hachage. Si un niveau existe déjà (nous avons une collision), nous allons simplement pousser vers le tableau à ce niveau. Vous pouvez également utiliser un tableau au lieu d’un objet JavaScript, car nos niveaux sont numériques et peuvent servir d’indices de tableau. Vous pouvez renvoyer des noeuds, des valeurs, convertir en une liste liée ou ce que vous voulez. Je ne fais que renvoyer des valeurs dans un souci de simplicité.
BinarySearchTree.prototype.breadthFirstRec = function() {
var levels = {};
var traverse = function(current, depth) {
if (!current) return null;
if (!levels[depth]) levels[depth] = [current.value];
else levels[depth].Push(current.value);
traverse(current.left, depth + 1);
traverse(current.right, depth + 1);
};
traverse(this.root, 0);
return levels;
};
var bst = new BinarySearchTree();
bst.add(20, 22, 8, 4, 12, 10, 14, 24);
console.log('Recursive Breadth First: ', bst.breadthFirstRec());
/*Recursive Breadth First:
{ '0': [ 20 ],
'1': [ 8, 22 ],
'2': [ 4, 12, 24 ],
'3': [ 10, 14 ] } */
Voici un exemple de Traversée Largeur Première réelle utilisant une approche itérative.
BinarySearchTree.prototype.breadthFirst = function() {
var result = '',
queue = [],
current = this.root;
if (!current) return null;
queue.Push(current);
while (current = queue.shift()) {
result += current.value + ' ';
current.left && queue.Push(current.left);
current.right && queue.Push(current.right);
}
return result;
};
console.log('Breadth First: ', bst.breadthFirst());
//Breadth First: 20 8 22 4 12 24 10 14