Скажімо, ви хотіли реалізувати пошук по ширині в бінарному дереві рекурсивно . Як би ти про це пішов?
Чи можливо використовувати лише стек виклику як допоміжне сховище?
Скажімо, ви хотіли реалізувати пошук по ширині в бінарному дереві рекурсивно . Як би ти про це пішов?
Чи можливо використовувати лише стек виклику як допоміжне сховище?
Відповіді:
(Я припускаю, що це лише якась думка вправа, або навіть хитрість домашнього завдання / питання інтерв'ю, але, мабуть, я міг би уявити якийсь химерний сценарій, коли з певних причин вам заборонено будь-який купі місця [якийсь справді поганий звичай диспетчер пам'яті? деякі химерні проблеми з робочим часом / ОС?], поки ви все ще маєте доступ до стеку ...)
Обхід першої ширини традиційно використовує чергу, а не стек. Характер черги та стека є майже протилежними, тому намагання використовувати стек викликів (який є стеком, звідси і назва) як допоміжного сховища (черги), в значній мірі приречений на збій, якщо ви цього не робите щось глупо смішне зі стеком дзвінків, яким ви не повинні бути.
З тієї ж точки зору, характер будь-якої безхвостової рекурсії, яку ви намагаєтеся реалізувати, по суті, додає стек до алгоритму. Це робить його більше не в першу чергу за пошуком бінарного дерева, і, таким чином, час роботи і те, що не для традиційних BFS, більше не застосовуються повністю. Звичайно, ви завжди можете тривіально перетворити будь-яку петлю на рекурсивний виклик, але це не якась змістовна рекурсія.
Однак існують способи, як це демонструють інші, реалізувати щось, що відповідає семантиці BFS за певну ціну. Якщо вартість порівняння дорога, але обхід вузла коштує дешево, то, як це робив @Simon Buchan , ви можете просто запустити повторний пошук по глибині, лише обробляючи листя. Це означатиме, що в купі не зберігається зростаюча черга, просто локальна змінна глибина, а стеки створюються знову і знову на стеку викликів, коли дерево перетинається знову і знову. І як відмічав @Patrick , бінарне дерево, підкріплене масивом, як правило, зберігається в порядку переходу в ширину, так що пошук по ширині в цьому випадку буде тривіальним, також не потребуючи додаткової черги.
Якщо ви використовуєте масив для резервного копіювання бінарного дерева, ви можете визначити наступний вузол алгебраїчно. якщо 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)
Я не зміг знайти спосіб зробити це повністю рекурсивно (без будь-якої допоміжної структури даних). Але якщо черга Q передається за посиланням, то ви можете мати таку рекурсивну функцію дурного хвоста:
BFS(Q)
{
if (|Q| > 0)
v <- Dequeue(Q)
Traverse(v)
foreach w in children(v)
Enqueue(Q, w)
BFS(Q)
}
Наступний метод використовував алгоритм 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;
}
}
level
нуля.
Проста рекурсія 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);
}
Я знайшов дуже красивий рекурсивний (навіть функціональний) алгоритм, пов'язаний з обходом «Хліб-Перший». Не моя ідея, але я думаю, що це слід згадати в цій темі.
Кріс Окасакі дуже чітко пояснює свій алгоритм нумерації першої широти з 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
}
Німий спосіб:
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;
}
Ось коротке рішення 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)
Ось реалізація 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)
Ось реалізація рекурсивної 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]
}
Наступне мені здається досить природним, використовуючи 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])
Ось рекурсивна реалізація 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 = []
Я хотів би додати свої центи до головної відповіді тим, що якщо мова підтримує щось на зразок генератора, bfs може бути виконано спільно рекурсивно.
Для початку відповідь @ Tanzelax звучить так:
Обхід першої ширини традиційно використовує чергу, а не стек. Характер черги та стека є майже протилежними, тому намагання використовувати стек викликів (який є стеком, звідси і назва) як допоміжний сховище (черга), в значній мірі приречений на провал
Справді, стек звичайного функціонального виклику не поводитиметься як звичайний стек. Але функція генератора призупинить виконання функції, так що це дає нам можливість отримати наступний рівень дітей вузлів, не заглиблюючись у більш глибокі нащадки вузла.
Наступний код - рекурсивний bfs в Python.
def bfs(root):
yield root
for n in bfs(root):
for c in n.children:
yield c
Інтуїція тут така:
Мені довелося реалізувати обхід купівлі, який виводиться в порядку 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;
}
Нехай 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)
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));
}
#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;
}
Ось реалізація 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
Далі йде мій код для повністю рекурсивної реалізації ширини першого пошуку двонаправленого графіка без використання циклу та черги.
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);
}
}
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();
});
}
Я створив програму, використовуючи 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;
}