Першочерговий пошук ширини


152

Скажімо, ви хотіли реалізувати пошук по ширині в бінарному дереві рекурсивно . Як би ти про це пішов?

Чи можливо використовувати лише стек виклику як допоміжне сховище?


14
дуже гарне запитання. це зовсім не просто. в основному ви просите реалізувати BFS, використовуючи лише стек.
Sisis

4
рекурсивно з просто стеком? це болить мені в голові.
Кевін Фрідхайм

11
Зазвичай я використовую стек для видалення рекурсивної поведінки
ньютопієць,

Якщо я використовую BFS на купі Max, мені цікаво, чи рішення, наведені нижче, працюють належним чином? Будь-які думки?
Jay D

Відповіді:


123

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

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

З тієї ж точки зору, характер будь-якої безхвостової рекурсії, яку ви намагаєтеся реалізувати, по суті, додає стек до алгоритму. Це робить його більше не в першу чергу за пошуком бінарного дерева, і, таким чином, час роботи і те, що не для традиційних BFS, більше не застосовуються повністю. Звичайно, ви завжди можете тривіально перетворити будь-яку петлю на рекурсивний виклик, але це не якась змістовна рекурсія.

Однак існують способи, як це демонструють інші, реалізувати щось, що відповідає семантиці BFS за певну ціну. Якщо вартість порівняння дорога, але обхід вузла коштує дешево, то, як це робив @Simon Buchan , ви можете просто запустити повторний пошук по глибині, лише обробляючи листя. Це означатиме, що в купі не зберігається зростаюча черга, просто локальна змінна глибина, а стеки створюються знову і знову на стеку викликів, коли дерево перетинається знову і знову. І як відмічав @Patrick , бінарне дерево, підкріплене масивом, як правило, зберігається в порядку переходу в ширину, так що пошук по ширині в цьому випадку буде тривіальним, також не потребуючи додаткової черги.


10
Це справді лише вправа на думку. Я не можу реально уявити ситуацію, в якій ви насправді хотіли б це зробити. Дякуємо за добре продуману відповідь!
Нейт

2
" але я думаю, я міг би уявити якийсь химерний сценарій, коли тобі з якихось причин не дозволено будь-який купольний простір ": dunno, я можу уявити собі вбудоване середовище, де доступний лише стек (разом із будь-яким простором пам'яті для читання) (це насправді досить просто та ефективно писати програмне забезпечення, не використовуючи купу взагалі, якщо ви точно знаєте, що збирається робити ваша програма, що зазвичай відбувається у вбудованому програмному забезпеченні). Тож для мене це не так "химерно". Незвичайне, можливо, але не химерне.
Томас

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

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. До речі, ви забули помилку повернення у першому випадку.
сизіс

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

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

Зверніть трохи більше уваги на формування вашого коду. Я вніс деякі зміни.
Міха 8

Але, тримайте ... це ДФС, а не БФС? Оскільки PrintLevelNodes не повертається до levelнуля.
Херрінгтон Даркхолм

1
@HerringtonDarkholme, Правильно. Він виконує пошук DFS, але виводить значення так, як ніби він робив BFS для рівня. Дякуємо, що вказали на це.
Сань

1
@Sanjay, це справді хороша демонстрація того, як можна виконати якусь дію над вузлами в порядку 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

Я знайшов дуже красивий рекурсивний (навіть функціональний) алгоритм, пов'язаний з обходом «Хліб-Перший». Не моя ідея, але я думаю, що це слід згадати в цій темі.

Кріс Окасакі дуже чітко пояснює свій алгоритм нумерації першої широти з ICFP 2000 за адресою http://okasaki.blogspot.de/2008/07/breadth-first-numeing-algorithm-in.html лише трьома малюнками.

Реалізація Debaish Ghosh Scala, яку я знайшов на веб- сайті 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

Ось реалізація рекурсивної BFS Scala 2.11.4. Я пожертвував оптимізацією хвостового виклику заради стислості, але версія 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, але виходить з ладу за винятком stackoverflow. Я до цього часу не зрозумів, чому. Однак я змінив алгоритм так, щоб він зупинявся (і мабуть краще). Ви можете знайти мій робочий зразок тут .
Адам Симон

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. Дивіться цей загальний опис Примітка: функція виклику повинна обробляти випадок неповних дерев.
bunkerdive

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.