再帰を使わないで深さ優先探索と幅優先探索は、 データ構造がスタックかキューかだけの違い、と昔習った
しかし、ふと、そうやって深さ優先探索を行うと、 帰りがけ順に処理を行うことができないことに気がついた
例えば、再帰を使わない深さ優先探索は以下のように実装できるだろう
% cat preorder.rb Node = Struct.new(:value, :left, :right) def n(v, l=nil, r=nil) Node.new(v, l, r) end tree = n(1, n(2, n(3), n(4)), n(5, n(6), n(7))) stack = [tree] until stack.empty? n = stack.pop p n.value stack.push n.right if n.right stack.push n.left if n.left end % ruby preorder.rb 1 2 3 4 5 6 7
ここで、この実装で p n.value というのは行きがけ順に実行される
では、帰りがけ順に処理を行いたいときにはどうしたらいいだろうか
再帰を使う深さ優先探索ならこれは簡単で、子ノードを再帰的に処理した後に 処理を行えばよい (行きがけ順に行う処理は子ノードを再帰的に処理する前に行う)
しかし、再帰を使わない場合は、そういう帰りがけ順の処理になるちょうどいい場所がない
考えてみると、子ノードを再帰的に処理した後に行う、というのは、 最後の子ノードを処理した後の継続 (帰りがけ順の処理をした後に上のノードに return する) が マシンスタックとして表現されているわけで、 データとしてスタックを表現する場合でもその継続と同じ意味のデータが必要なのだろう
というわけで実装してみると、以下のようにすれば、再帰を使わずに、深さ優先探索で帰りがけ順の処理を行える
% cat postorder.rb Node = Struct.new(:value, :left, :right) def n(v, l=nil, r=nil) Node.new(v, l, r) end tree = n(1, n(2, n(3), n(4)), n(5, n(6), n(7))) stack = [[:pre, tree]] until stack.empty? tag, n = stack.pop case tag when :pre stack.push [:post, n] stack.push [:pre, n.right] if n.right stack.push [:pre, n.left] if n.left when :post p n.value end end % ruby postorder.rb 3 4 2 6 7 5 1
[latest]