web-dev-qa-db-fra.com

Effectuer une première recherche de manière récursive

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?

132
Nate

(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.

98
Tanzelax

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)        
22
Patrick McMurchie

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)
}
17
sisis

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;
    }
}
13
Sanj

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);
}
8
A-patel-guy

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;
}
4
Simon Buchan

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
}
4
snv

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]
}
2
Joffer

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)
2
rookie

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])
2
user4861515

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

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)
1
Ilya Bystrov

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)
1
Ashok

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;
}
1
Justin
#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;
}
0
Kaidul

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

0
Pushkal

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 = []
0
Jbeat

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
0
Alex Hawkins