二分木の幅優先探索を再帰的に実装したいとしましょう。あなたならどうしますか?
コールスタックのみを補助記憶域として使用することは可能ですか?
二分木の幅優先探索を再帰的に実装したいとしましょう。あなたならどうしますか?
コールスタックのみを補助記憶域として使用することは可能ですか?
(これはある種の思考練習、またはトリックの宿題/面接の質問でさえあると想定していますが、何らかの理由でヒープ領域が許可されないという奇妙なシナリオを想像できると思います [いくつかの本当に悪い習慣メモリ マネージャー? いくつかの奇妙なランタイム/OS の問題?] スタックにまだアクセスできます...)
幅優先トラバーサルでは、伝統的にスタックではなくキューを使用します。キューとスタックの性質はほとんど正反対です。そのため、コール スタック (スタックなので名前の由来) を補助ストレージ (キュー) として使用しようとすると、失敗する運命にあります。あなたがすべきではないコールスタックでばかげた何か。
同様に、実装しようとする非末尾再帰の性質は、本質的にアルゴリズムにスタックを追加することです。これにより、二分木で幅優先検索が行われなくなり、従来の 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)
}
Java での単純な BFS および DFS 再帰:
スタック/キュー内のツリーのルート ノードをプッシュ/提供し、これらの関数を呼び出すだけです。
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);
}
愚かな方法:
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;
}
これは、再帰的 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]
}
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)
これは、 QUEUE を使用せずにポインターを使用して実行できると思います。
基本的に、任意の時点で 2 つのポインターを維持しています。1 つは親を指し、もう 1 つは処理される子を指しています (処理されたすべてのリンクリスト)。
これで、子のポインターを割り当てるだけで、親の処理が終了したら、次のレベルを処理するために子を親にするだけです
以下は私のコードです:
//Tree Node
struct Node {
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
//アルゴリズム:
void LevelTraverse(Node* parent,Node* chidstart,Node* childend ){
if(!parent && !chidstart) return; // we processed everything
if(!parent && chidstart){ //finished processing last level
parent=chidstart;chidstart=childend=NULL; // assgin child to parent for processing next level
LevelTraverse(parent,chidstart,childend);
}else if(parent && !chidstart){ // This is new level first node tobe processed
Node* temp=parent; parent=parent->next;
if(temp->left) { childend=chidstart=temp->left; }
if(chidstart){
if(temp->right) { childend->next=temp->right; childend=temp->right; }
}else{
if(temp->right) { childend=chidstart=temp->right; }
}
LevelTraverse(parent,chidstart,childend);
}else if(parent && chidstart){ //we are in mid of some level processing
Node* temp=parent; parent=parent->next;
if(temp->left) { childend->next=temp->left; childend=temp->left; }
if(temp->right) { childend->next=temp->right; childend=temp->right; }
LevelTraverse(parent,chidstart,childend);
}
}
//ドライバーコード:
Node* connect(Node* root) {
if(!root) return NULL;
Node* parent; Node* childs, *childe; parent=childs=childe=NULL;
parent=root;
LevelTraverse(parent, childs, childe);
return root;
}
#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