Thực hiện tìm kiếm đầu tiên theo chiều rộng đệ quy


151

Hãy nói rằng bạn muốn thực hiện một tìm kiếm theo chiều rộng của một cây nhị phân đệ quy . Làm thế nào bạn sẽ đi về nó?

Có thể chỉ sử dụng ngăn xếp cuộc gọi làm bộ nhớ phụ?


14
câu hỏi rất hay Điều này không đơn giản chút nào. về cơ bản, bạn đang yêu cầu triển khai BFS chỉ bằng một ngăn xếp.
sisis

4
đệ quy chỉ với một ngăn xếp? Điều này làm tổn thương đầu tôi.
Kevin Friedheim

11
Tôi thường sử dụng một ngăn xếp để loại bỏ hành vi đệ quy
Newtopian

Nếu tôi sử dụng BFS trên heap Max, tôi tự hỏi liệu các giải pháp được đưa ra dưới đây có hoạt động tốt không? Có suy nghĩ gì không?
Jay D

Câu trả lời:


122

(Tôi cho rằng đây chỉ là một dạng bài tập suy nghĩ, hoặc thậm chí là một câu hỏi phỏng vấn / bài tập về nhà, nhưng tôi cho rằng tôi có thể tưởng tượng ra một kịch bản kỳ quái khi bạn không cho phép bất kỳ không gian heap nào vì một số lý do trình quản lý bộ nhớ? một số vấn đề thời gian chạy / hệ điều hành kỳ quái?] trong khi bạn vẫn có quyền truy cập vào ngăn xếp ...)

Theo truyền thống, chiều rộng đầu tiên sử dụng một hàng đợi, không phải là một ngăn xếp. Bản chất của hàng đợi và ngăn xếp hoàn toàn trái ngược nhau, vì vậy, cố gắng sử dụng ngăn xếp cuộc gọi (là ngăn xếp, do đó tên) làm bộ lưu trữ phụ (hàng đợi) sẽ thất bại rất nhiều, trừ khi bạn đang làm một cái gì đó ngu ngốc vô lý với ngăn xếp cuộc gọi mà bạn không nên.

Trên cùng một mã thông báo, bản chất của bất kỳ đệ quy không đuôi nào bạn cố gắng thực hiện về cơ bản là thêm một ngăn xếp vào thuật toán. Điều này làm cho nó không còn tìm kiếm lần đầu tiên trên cây nhị phân, và do đó thời gian chạy và không có gì cho BFS truyền thống không còn hoàn toàn áp dụng. Tất nhiên, bạn luôn có thể biến bất kỳ vòng lặp nào thành một cuộc gọi đệ quy, nhưng đó không phải là bất kỳ loại đệ quy có ý nghĩa nào.

Tuy nhiên, có nhiều cách, như được chứng minh bởi những người khác, để thực hiện một cái gì đó tuân theo ngữ nghĩa của BFS với một số chi phí. Nếu chi phí so sánh đắt nhưng truyền tải nút thì rẻ, như @Simon Buchan đã làm, bạn chỉ cần chạy một tìm kiếm theo chiều sâu lặp đi lặp lại, chỉ xử lý các lá. Điều này có nghĩa là không có hàng đợi tăng trưởng được lưu trữ trong heap, chỉ là một biến số độ sâu cục bộ và các ngăn xếp được xây dựng lặp đi lặp lại trên ngăn xếp cuộc gọi khi cây được lặp đi lặp lại. Và như @Patrick đã lưu ý, một cây nhị phân được hỗ trợ bởi một mảng thường được lưu trữ theo thứ tự truyền tải theo chiều rộng đầu tiên, do đó, một tìm kiếm đầu tiên trên đó sẽ là tầm thường, mà không cần hàng đợi phụ.


10
Đây thực sự chỉ là một bài tập suy nghĩ. Tôi thực sự không thể tưởng tượng được một tình huống mà bạn thực sự muốn làm điều này. Cảm ơn câu trả lời cũng nghĩ ra!
Nate

2
" nhưng tôi cho rằng tôi có thể tưởng tượng một số tình huống kỳ quái khi bạn không cho phép bất kỳ không gian heap nào vì lý do nào đó ": dunno, tôi có thể tưởng tượng một môi trường nhúng trong đó chỉ có ngăn xếp (cùng với bất kỳ không gian bộ nhớ chỉ đọc nào) thực sự khá dễ dàng và hiệu quả để viết phần mềm mà không cần sử dụng heap nếu bạn biết chính xác chương trình của bạn sẽ làm gì, thường là trong phần mềm nhúng). Vì vậy, nó không "kỳ quái" với tôi. Không bình thường, có thể, nhưng không kỳ quái.
Thomas

Tôi nghĩ rằng câu trả lời của bạn có thể chứa một tài liệu tham khảo cho bài viết này ( ibm.com/developerworks/aix/l Library / au-aix-stack-trip-training ). Nó cho thấy một triển khai về những gì bạn đã viết trong phần thứ hai của câu trả lời của bạn
bao gồm

25

Nếu bạn sử dụng một mảng để sao lưu cây nhị phân, bạn có thể xác định nút tiếp theo theo đại số. if ilà một nút, thì con của nó có thể được tìm thấy tại 2i + 1(đối với nút bên trái) và 2i + 2(đối với nút bên phải). Hàng xóm tiếp theo của một nút được đưa ra bởi i + 1, trừ khi icó sức mạnh của2

Đây là mã giả cho một triển khai rất ngây thơ của tìm kiếm đầu tiên trên một cây tìm kiếm nhị phân được hỗ trợ mảng. Điều này giả định một mảng kích thước cố định và do đó một cây có chiều sâu cố định. Nó sẽ xem xét các nút không có cha mẹ và có thể tạo ra một ngăn xếp lớn không thể kiểm soát được.

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
Đẹp. Tôi đã bỏ qua thực tế rằng chúng ta đang đối phó với một cây nhị phân. Các chỉ mục có thể được chỉ định bằng DFS. BTW, bạn đã quên trả lại sai trong trường hợp đầu tiên.
sisis

Tôi nghĩ rằng tôi thích phương pháp xếp hàng; P. Đã thêm trả lại sai.
Patrick McMurchie

1
Tài giỏi. Ý tưởng lưu trữ các nút trong một mảng và tham chiếu chúng theo đại số đã không xảy ra với tôi.
Nate

19

Tôi không thể tìm ra cách để thực hiện đệ quy hoàn toàn (không có bất kỳ cấu trúc dữ liệu phụ trợ nào). Nhưng nếu hàng đợi Q được truyền bằng tham chiếu, thì bạn có thể có hàm đệ quy đuôi ngớ ngẩn sau đây:

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

     BFS(Q)
}

6
Đây là cách không tự nhiên, để thêm đệ quy để làm sạch và đúng chức năng.
Mysterion

Hoàn toàn không đồng ý - tôi thấy nó tự nhiên hơn - và cũng hữu ích hơn; bạn có thể mở rộng phương pháp này để chuyển trạng thái làm việc khi bạn đi qua các lớp
Tom Golden

15

Phương pháp sau đây đã sử dụng thuật toán DFS để có được tất cả các nút ở độ sâu cụ thể - tương tự như thực hiện BFS cho mức đó. Nếu bạn tìm ra độ sâu của cây và làm điều này cho tất cả các cấp, kết quả sẽ giống như 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);
}

Tìm chiều sâu của cây là một miếng bánh:

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

Xin hãy chú ý hơn một chút đến việc hình thành mã của bạn. Tôi đã làm một số thay đổi.
Micha

Nhưng, chờ đã ... đây có phải là DFS chứ không phải BFS không? Bởi vì PrintLevelNodes không trả về cho đến khi levelbằng không.
Herrington Darkholme

1
@HerringtonDarkholme, Đúng. Nó thực hiện tìm kiếm DFS nhưng các giá trị đầu ra như thể nó đã thực hiện BFS cho một cấp độ. Cảm ơn đã chỉ ra rằng.
Sanj

1
@Sanjay, đây thực sự là một minh chứng tốt về cách người ta có thể thực hiện một số hành động trên các nút theo thứ tự DFS. Không nhất thiết là cách người ta thực sự "chạm" vào các nút theo thứ tự DFS, nhưng chắc chắn sẽ cho phép "hành động" đệ quy trên các nút theo thứ tự DFS, trong trường hợp này là in các giá trị của chúng.
bunkerdive

8

Một đệ quy BFS và DFS đơn giản trong Java:
Chỉ cần đẩy / cung cấp nút gốc của cây trong ngăn xếp / hàng đợi và gọi các hàm này.

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
Có một chút kỳ lạ khi truyền stack như một tham số cho DFS, bởi vì bạn đã có stack stack ở đó. Ngoài ra câu hỏi là chỉ sử dụng ngăn xếp cuộc gọi như một cấu trúc dữ liệu.
vladich

4

Tôi tìm thấy một thuật toán đệ quy liên quan đến Breadth-First traverse rất đẹp (thậm chí là chức năng). Không phải ý tưởng của tôi, nhưng tôi nghĩ nó nên được đề cập trong chủ đề này.

Chris Okasaki giải thích thuật toán đánh số đầu tiên của mình từ ICFP 2000 tại http://okasaki.blogspot.de/2008/07/breadth-first-numbering-alerskym-in.html rất rõ ràng chỉ với 3 hình ảnh.

Việc triển khai Scala của Debasish Ghosh, mà tôi tìm thấy tại http://debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html , là:

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 cho thuật toán đẹp. Tuy nhiên, tôi thấy nó vẫn sử dụng hàng đợi. Phía bên trái của "Quy tắc 3" thực sự là các hoạt động dequeue và enqueue.
Luke Lee

3

Cách ngu ngốc:

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

Đây là giải pháp Scala ngắn :

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

Ý tưởng sử dụng giá trị trả về làm bộ tích lũy rất phù hợp. Có thể được thực hiện bằng các ngôn ngữ khác theo cách tương tự, chỉ cần đảm bảo rằng danh sách quy trình hàm đệ quy của bạn .

Danh sách mã kiểm tra (sử dụng cây thử @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())))
  }
}

Đầu ra:

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

2

Đây là một triển khai 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

Đây là một triển khai Scala 2.11.4 của BFS đệ quy. Tôi đã hy sinh tối ưu hóa cuộc gọi đuôi để ngắn gọn, nhưng phiên bản TCOd rất giống nhau. Xem thêm bài viết của @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

Những điều sau đây có vẻ khá tự nhiên đối với tôi, sử dụng Haskell. Lặp lại đệ quy qua các cấp của cây (ở đây tôi thu thập các tên thành một chuỗi có thứ tự lớn để hiển thị đường dẫn qua cây):

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

Dưới đây là một triển khai Python duyệt qua đệ quy BFS, làm việc cho một biểu đồ không có chu kỳ.

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

Tôi muốn thêm xu của mình vào câu trả lời hàng đầu ở chỗ nếu ngôn ngữ hỗ trợ một cái gì đó như trình tạo, bfs có thể được thực hiện đồng quy.

Để bắt đầu, câu trả lời của @ Tanzelax đọc:

Theo truyền thống, chiều rộng đầu tiên sử dụng một hàng đợi, không phải là một ngăn xếp. Bản chất của một hàng đợi và một ngăn xếp hoàn toàn trái ngược nhau, vì vậy cố gắng sử dụng ngăn xếp cuộc gọi (là một ngăn xếp, do đó tên) làm bộ lưu trữ phụ (một hàng đợi) sẽ thất bại khá nhiều

Thật vậy, ngăn xếp của hàm gọi thông thường sẽ không hoạt động như một ngăn xếp thông thường. Nhưng hàm tạo sẽ đình chỉ việc thực thi hàm để nó cho chúng ta cơ hội sinh ra các cấp con tiếp theo mà không đào sâu vào hậu duệ của nút.

Đoạn mã sau là bfs đệ quy trong Python.

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

Trực giác ở đây là:

  1. bfs đầu tiên sẽ trả về root như kết quả đầu tiên
  2. giả sử chúng ta đã có chuỗi bfs, cấp độ phần tử tiếp theo trong bfs là con ngay lập tức của nút trước đó trong chuỗi
  3. lặp lại hai thủ tục trên

Tôi không biết Python nhưng tôi nghĩ mã của bạn chuyển sang mã C # này . Nó thực hiện BFS traversal nhưng gặp sự cố với ngoại lệ stackoverflow. Tôi đã không tìm ra lý do tại sao cho đến nay. Tuy nhiên, tôi đã sửa đổi thuật toán để nó dừng lại (và có thể hoạt động tốt hơn). Bạn tìm mẫu làm việc của tôi ở đây .
Adam Simon

1

Tôi đã phải thực hiện một giao dịch heap mà đầu ra theo thứ tự BFS. Nó không thực sự là BFS nhưng hoàn thành nhiệm vụ tương tự.

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
Đối với những người xem khác: đây là một ví dụ về việc thực hiện một cây hoàn chỉnh trong một mảng; Cụ thể, @Justin đang thực hiện một giao dịch đặt hàng trước, trong đó anh ta lưu các giá trị nút (theo thứ tự BFS) trong một mảng tại chỉ mục BFS thích hợp. Điều này cho phép hàm gọi lặp lại tuyến tính thông qua mảng, in các giá trị theo thứ tự BFS. Xem mô tả chung này Lưu ý: chức năng gọi phải xử lý trường hợp cây không hoàn chỉnh.
bunkerdive

1

Đặt v là đỉnh bắt đầu

Đặt G là đồ thị trong câu hỏi

Sau đây là mã giả mà không sử dụng hàng đợi

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)

Tôi nghĩ rằng điều này có thể bị mắc kẹt trong một vòng lặp vô hạn - các đỉnh đang được đánh dấu là đã truy cập, nhưng chúng không bao giờ được kiểm tra cho lượt truy cập trước khi đệ quy lại.

Đoạn mã này tương tự như DFS chứ không phải BFS
Dení

1

BFS cho cây nhị phân (hoặc n-ary) có thể được thực hiện đệ quy mà không cần hàng đợi như sau (ở đây trong 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;
    }
}

Một ví dụ về in số ngang 1-12 theo thứ tự tăng dần:

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

Dưới đây là một triển khai JavaScript thực hiện đệ quy đầu tiên theo chiều rộng với đệ quy sâu đầu tiên. Tôi đang lưu trữ các giá trị nút ở mỗi độ sâu bên trong một mảng, bên trong hàm băm. Nếu một mức đã tồn tại (chúng ta có xung đột), vì vậy chúng ta chỉ cần đẩy đến mảng ở cấp đó. Bạn cũng có thể sử dụng một mảng thay vì một đối tượng JavaScript vì các cấp của chúng tôi là số và có thể đóng vai trò là các chỉ số mảng. Bạn có thể trả về các nút, giá trị, chuyển đổi thành Danh sách được liên kết hoặc bất cứ điều gì bạn muốn. Tôi chỉ trả lại các giá trị vì đơn giản.

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

Dưới đây là một ví dụ về Breadth First Traversal thực tế bằng cách sử dụng phương pháp lặp.

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

Sau đây là mã của tôi để thực hiện đệ quy hoàn toàn lần đầu tiên tìm kiếm đồ thị hai chiều mà không sử dụng vòng lặp và hàng đợi.

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 # thực hiện thuật toán tìm kiếm theo chiều rộng đệ quy cho cây nhị phân.

Trực quan hóa dữ liệu cây nhị phân

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

Nếu bạn muốn thuật toán hoạt động không chỉ với cây nhị phân mà với đồ thị, có thể có hai và nhiều nút trỏ đến cùng một nút khác, bạn phải tránh tự quay vòng bằng cách giữ danh sách các nút đã truy cập. Việc thực hiện có thể giống như thế này.

Trực quan hóa dữ liệu đồ thị

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

Tôi đã thực hiện một chương trình sử dụng c ++ đang hoạt động trong biểu đồ khớp và tách rời.

    #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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.