Выполнение поиска в ширину рекурсивно


152

Допустим, вы хотели рекурсивно реализовать поиск в двоичном дереве в ширину . Как бы вы пошли об этом?

Возможно ли использовать только стек вызовов в качестве вспомогательного хранилища?


14
очень хороший вопрос это не так просто. в основном вы просите реализовать BFS, используя только стек.
Сисис

4
рекурсивно только со стеком? это ранит мою голову
Кевин Фридхайм

11
Я обычно использую стек для удаления рекурсивного поведения
Newtopian

Если я использую BFS в куче Max, мне интересно, работают ли приведенные ниже решения как надо? Есть предположения ?
Jay D

Ответы:


123

(Я предполагаю, что это просто какое-то упражнение на размышление или даже домашнее задание / вопрос с подвохом, но я полагаю, я мог бы представить какой-то причудливый сценарий, когда вам по какой-то причине не хватает места в куче [какой-то действительно плохой обычай диспетчер памяти - какие-то странные проблемы со временем выполнения / ОС?], пока у вас все еще есть доступ к стеку ...)

Обход в ширину традиционно использует очередь, а не стек. Природа очереди и стека в значительной степени противоположна, поэтому попытка использовать стек вызовов (который является стеком, отсюда и название) в качестве вспомогательного хранилища (очереди) в значительной степени обречен на неудачу, если вы не делаете что-то глупо смешное со стеком вызовов, которым ты не должен быть.

С другой стороны, природа любой нехвостовой рекурсии, которую вы пытаетесь реализовать, заключается в добавлении стека к алгоритму. Это больше не позволяет выполнять поиск в двоичном дереве в ширину, и, таким образом, время выполнения и все такое для традиционной BFS больше не применяется полностью. Конечно, вы всегда можете легко превратить любой цикл в рекурсивный вызов, но это не какая-либо значимая рекурсия.

Однако, как продемонстрировали другие, есть способы реализовать что-то, что следует семантике BFS, за определенную плату. Если стоимость сравнения стоит дорого, но обход узла обходится дешево, то как @Simon Buchan сделал , вы можете просто запустить итеративный поиск в глубину, только обработав листья. Это будет означать отсутствие растущей очереди, хранящейся в куче, только локальную переменную глубины, и стеки, накапливающиеся снова и снова в стеке вызовов, когда дерево перебирается снова и снова. И, как заметил @Patrick , двоичное дерево, поддерживаемое массивом, в любом случае обычно хранится в порядке обхода в ширину, поэтому поиск в ширину в этом случае будет тривиальным, также без необходимости во вспомогательной очереди.


10
Это действительно просто мысленное упражнение. Я не могу себе представить ситуацию, в которой вы бы действительно хотели это сделать. Спасибо за хорошо продуманный ответ!
Нат

2
" но я полагаю, что мог бы представить какой-то причудливый сценарий, когда вам по какой-то причине не разрешено пространство в куче »: не знаю, я могу представить себе встроенную среду, в которой доступен только стек (вместе с любым пространством памяти только для чтения) (это на самом деле довольно просто и эффективно писать программное обеспечение без использования кучи, если вы точно знаете, что собирается делать ваша программа, что обычно имеет место во встроенном программном обеспечении). Так что это не так "странно" для меня. Может быть, необычно, но не странно.
Томас

Я думаю, что ваш ответ может содержать ссылку на эту статью ( ibm.com/developerworks/aix/library/au-aix-stack-tree-traversal ). Он показывает реализацию того, что вы написали во второй части вашего ответа
incud

25

Если вы используете массив для поддержки двоичного дерева, вы можете определить следующий узел алгебраически. Если iэто узел, то его дочерние элементы могут быть найдены в 2i + 1(для левого узла) и 2i + 2(для правого узла). Следующий сосед узла задается как i + 1, если не iявляется степенью2

Вот псевдокод для очень наивной реализации поиска в ширину в двоичном дереве поиска на основе массива. Это предполагает массив фиксированного размера и, следовательно, дерево фиксированной глубины. Он будет смотреть на узлы без родителей и может создать неуправляемо большой стек.

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)        

1
Ницца. Я упустил из виду тот факт, что мы имеем дело с бинарным деревом. Индексы могут быть назначены с использованием DFS. Кстати, вы забыли возврат false в первом случае.
Сисис

Я думаю, что я предпочитаю метод очереди; P. Добавлен возврат false.
Патрик МакМерчи

1
Умная. Идея хранить узлы в массиве и ссылаться на них алгебраически не пришла мне в голову.
Нат

19

Я не мог найти способ сделать это полностью рекурсивным (без какой-либо вспомогательной структуры данных). Но если очередь Q передается по ссылке, то у вас может быть следующая рекурсивная функция глупого хвоста:

BFS(Q)
{
  if (|Q| > 0)
     v <- Dequeue(Q)
     Traverse(v)
     foreach w in children(v)
        Enqueue(Q, w)    

     BFS(Q)
}

6
Это неестественный способ добавить рекурсив в чистую и правильную функцию.
Мистерион

Совершенно не согласен - я считаю это более естественным - и также более полезным; Вы можете расширить этот метод для передачи рабочего состояния при прохождении слоев
Том Голден

15

Следующий метод использовал алгоритм DFS, чтобы получить все узлы на определенной глубине - что аналогично выполнению BFS для этого уровня. Если вы выясните глубину дерева и сделаете это для всех уровней, результаты будут такими же, как у 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);
}

Найти глубину дерева - это кусок пирога:

public int MaxDepth(Tree root) {
    if (root == null) {
        return 0;
    } else {
        return Math.Max(MaxDepth(root.Left), MaxDepth(root.Right)) + 1;
    }
}

Пожалуйста, уделите немного больше внимания форматированию вашего кода. Я сделал некоторые изменения.
Миха

Но, подождите ... это DFS, а не BFS? Потому что PrintLevelNodes не возвращает, пока не levelстанет ноль.
Херрингтон Даркхолм

1
@HerringtonDarkholme, верно. Он выполняет поиск DFS, но выводит значения, как если бы он выполнял BFS для уровня. Спасибо за указание на это.
Сандж

1
@ Санджай, это действительно хорошая демонстрация того, как можно выполнить какое-то действие с узлами в DFS-порядке. Это не обязательно то, как можно было бы «касаться» узлов в DFS-порядке, но, безусловно, позволит рекурсивно «действовать» на узлы в DFS-порядке, в этом случае печатая их значения.
bunkerdive

8

Простая рекурсия BFS и DFS в Java:
просто нажмите / предложите корневой узел дерева в стеке / очереди и вызовите эти функции.

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

4
Немного странно передавать стек в качестве параметра для DFS, потому что у вас там уже есть неявный стек. Также вопрос состоял в том, чтобы использовать только стек вызовов в качестве структуры данных.
владич

4

Я нашел очень красивый рекурсивный (даже функциональный) алгоритм обхода Breadth-First. Не моя идея, но я думаю, что это должно быть упомянуто в этой теме.

Крис Окасаки объясняет свой алгоритм нумерации в ширину из ICFP 2000 по адресу http://okasaki.blogspot.de/2008/07/breadth-first-numbering-algorithm-in.html, используя только 3 изображения.

Scala-реализация Debasish Ghosh, которую я нашел по адресу http://debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html :

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
}

+1 за красивый алгоритм. Тем не менее, я обнаружил, что все еще использую очередь. Левая сторона самого «Правила 3» фактически является операциями удаления очереди и постановки в очередь.
Люк Ли

3

Тупой путь:

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

3

Вот краткое решение Scala :

  def bfs(nodes: List[Node]): List[Node] = {
    if (nodes.nonEmpty) {
      nodes ++ bfs(nodes.flatMap(_.children))
    } else {
      List.empty
    }
  }

Идея использования возвращаемого значения в качестве аккумулятора хорошо подходит. Может быть реализован на других языках аналогичным образом, просто убедитесь, что ваша рекурсивная функция обрабатывает список узлов .

Список тестовых кодов (с использованием тестового дерева @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())))
  }
}

Вывод:

List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

2

Вот реализация 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

Вот Scala 2.11.4 реализация рекурсивной BFS. Я пожертвовал оптимизацией хвостового вызова для краткости, но версия TCOd очень похожа. Смотрите также сообщение @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

Следующее мне кажется довольно естественным, используя Haskell. Выполните рекурсивную итерацию по уровням дерева (здесь я собираю имена в большую упорядоченную строку, чтобы показать путь через дерево):

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

Вот реализация Python для рекурсивного обхода BFS, работающая для графа без цикла.

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

2

Я хотел бы добавить свои центы к верхнему ответу в том, что если язык поддерживает что-то вроде генератора, bfs можно делать ко-рекурсивно.

Для начала ответ @ Tanzelax гласит:

Обход в ширину традиционно использует очередь, а не стек. Природа очереди и стека в значительной степени противоположна, поэтому попытка использовать стек вызовов (который является стеком, отсюда и название) в качестве вспомогательного хранилища (очереди) в значительной степени обречена на провал

Действительно, стек обычных вызовов функций не будет вести себя как обычный стек. Но функция генератора приостановит выполнение функции, поэтому она дает нам возможность получить следующий уровень дочерних узлов, не углубляясь в более глубоких потомков узла.

Следующий код является рекурсивным BFS в Python.

def bfs(root):
  yield root
  for n in bfs(root):
    for c in n.children:
      yield c

Интуиция здесь такова:

  1. BFS первый вернет рут в качестве первого результата
  2. предположим, что у нас уже есть последовательность bfs, следующий уровень элементов в bfs - это непосредственные потомки предыдущего узла в последовательности
  3. повторите две вышеуказанные процедуры

Я не знаю Python, но я думаю, что ваш код переводится в этот код C # . Он выполняет обход BFS, но завершается с исключением с переполнением стека. Я так и не понял, почему. Однако я изменил алгоритм так, чтобы он останавливался (и, вероятно, работал лучше). Вы найдете мой рабочий образец здесь .
Адам Саймон

1

Мне пришлось реализовать обход кучи, который выводит в порядке BFS. На самом деле это не BFS, но выполняет ту же задачу.

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

2
Для других зрителей: это пример реализации полного дерева в массиве; В частности, @Justin выполняет обход предварительного заказа, во время которого он сохраняет значения узлов (в порядке BFS) в массиве с соответствующим индексом BFS. Это позволяет вызывающей функции выполнять линейную итерацию по массиву, печатая значения в порядке BFS. См. Это общее описание. Примечание: вызывающая функция должна обрабатывать случай неполных деревьев.
бункерное погружение

1

Пусть v будет начальной вершиной

Пусть G - рассматриваемый граф

Ниже приведен псевдокод без использования очереди.

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)

Я думаю, что это может застрять в бесконечном цикле - вершины помечаются как посещенные, но они никогда не проверяются на посещаемость перед повторным повторением.

Этот фрагмент похож на DFS, а не на BFS
Dení

1

BFS для двоичного (или n-арного) дерева может быть выполнена рекурсивно без очередей следующим образом (здесь, в 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;
    }
}

Пример обхода печати номерами 1-12 в порядке возрастания:

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

0
#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

Вот реализация JavaScript, которая имитирует обход в ширину и рекурсию в глубину. Я храню значения узлов на каждой глубине внутри массива, внутри хеша. Если уровень уже существует (у нас есть коллизия), поэтому мы просто перемещаемся к массиву на этом уровне. Вы также можете использовать массив вместо объекта JavaScript, поскольку наши уровни являются числовыми и могут служить индексами массива. Вы можете вернуть узлы, значения, преобразовать в связанный список или что угодно. Я просто возвращаю значения ради простоты.

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 ] } */

Вот пример фактического обхода ширины с использованием итеративного подхода.

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

Ниже приведен мой код для полностью рекурсивной реализации поиска в ширину двунаправленного графа без использования цикла и очереди.

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

C # реализация рекурсивного алгоритма поиска в ширину для двоичного дерева.

Визуализация данных двоичного дерева

IDictionary<string, string[]> graph = new Dictionary<string, string[]> {
    {"A", new [] {"B", "C"}},
    {"B", new [] {"D", "E"}},
    {"C", new [] {"F", "G"}},
    {"E", new [] {"H"}}
};

void Main()
{
    var pathFound = BreadthFirstSearch("A", "H", new string[0]);
    Console.WriteLine(pathFound); // [A, B, E, H]

    var pathNotFound = BreadthFirstSearch("A", "Z", new string[0]);
    Console.WriteLine(pathNotFound); // []
}

IEnumerable<string> BreadthFirstSearch(string start, string end, IEnumerable<string> path)
{
    if (start == end)
    {
        return path.Concat(new[] { end });
    }

    if (!graph.ContainsKey(start)) { return new string[0]; }    

    return graph[start].SelectMany(letter => BreadthFirstSearch(letter, end, path.Concat(new[] { start })));
}

Если вы хотите, чтобы алгоритм работал не только с двоичным деревом, но и с графиками, которые могут иметь два и более узлов, которые указывают на один и тот же другой узел, вы должны избегать самоциклирования, храня список уже посещенных узлов. Реализация может выглядеть следующим образом.

Визуализация графических данных

IDictionary<string, string[]> graph = new Dictionary<string, string[]> {
    {"A", new [] {"B", "C"}},
    {"B", new [] {"D", "E"}},
    {"C", new [] {"F", "G", "E"}},
    {"E", new [] {"H"}}
};

void Main()
{
    var pathFound = BreadthFirstSearch("A", "H", new string[0], new List<string>());
    Console.WriteLine(pathFound); // [A, B, E, H]

    var pathNotFound = BreadthFirstSearch("A", "Z", new string[0], new List<string>());
    Console.WriteLine(pathNotFound); // []
}

IEnumerable<string> BreadthFirstSearch(string start, string end, IEnumerable<string> path, IList<string> visited)
{
    if (start == end)
    {
        return path.Concat(new[] { end });
    }

    if (!graph.ContainsKey(start)) { return new string[0]; }


    return graph[start].Aggregate(new string[0], (acc, letter) =>
    {
        if (visited.Contains(letter))
        {
            return acc;
        }

        visited.Add(letter);

        var result = BreadthFirstSearch(letter, end, path.Concat(new[] { start }), visited);
        return acc.Concat(result).ToArray();
    });
}

0

Я сделал программу, использующую c ++, которая также работает в объединенном и непересекающемся графе.

    #include <queue>
#include "iostream"
#include "vector"
#include "queue"

using namespace std;

struct Edge {
    int source,destination;
};

class Graph{
    int V;
    vector<vector<int>> adjList;
public:

    Graph(vector<Edge> edges,int V){
        this->V = V;
        adjList.resize(V);
        for(auto i : edges){
            adjList[i.source].push_back(i.destination);
            //     adjList[i.destination].push_back(i.source);
        }
    }
    void BFSRecursivelyJoinandDisjointtGraphUtil(vector<bool> &discovered, queue<int> &q);
    void BFSRecursivelyJointandDisjointGraph(int s);
    void printGraph();


};

void Graph :: printGraph()
{
    for (int i = 0; i < this->adjList.size(); i++)
    {
        cout << i << " -- ";
        for (int v : this->adjList[i])
            cout <<"->"<< v << " ";
        cout << endl;
    }
}


void Graph ::BFSRecursivelyJoinandDisjointtGraphUtil(vector<bool> &discovered, queue<int> &q) {
    if (q.empty())
        return;
    int v = q.front();
    q.pop();
    cout << v <<" ";
    for (int u : this->adjList[v])
    {
        if (!discovered[u])
        {
            discovered[u] = true;
            q.push(u);
        }
    }
    BFSRecursivelyJoinandDisjointtGraphUtil(discovered, q);

}

void Graph ::BFSRecursivelyJointandDisjointGraph(int s) {
    vector<bool> discovered(V, false);
    queue<int> q;

    for (int i = s; i < V; i++) {
        if (discovered[i] == false)
        {
            discovered[i] = true;
            q.push(i);
            BFSRecursivelyJoinandDisjointtGraphUtil(discovered, q);
        }
    }
}

int main()
{

    vector<Edge> edges =
            {
                    {0, 1}, {0, 2}, {1, 2}, {2, 0}, {2,3},{3,3}
            };

    int V = 4;
    Graph graph(edges, V);
 //   graph.printGraph();
    graph.BFSRecursivelyJointandDisjointGraph(2);
    cout << "\n";




    edges = {
            {0,4},{1,2},{1,3},{1,4},{2,3},{3,4}
    };

    Graph graph2(edges,5);

    graph2.BFSRecursivelyJointandDisjointGraph(0);
    return 0;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.