web-dev-qa-db-ja.com

再帰を使用せずにツリーをトラバースするにはどうすればよいですか?

メモリノードツリーが非常に大きく、ツリーをトラバースする必要があります。各子ノードの戻り値を親ノードに渡します。これは、すべてのノードがルートノードまでデータをバブルするまで行う必要があります。

トラバーサルはこのように機能します。

private Data Execute(Node pNode)
{
    Data[] values = new Data[pNode.Children.Count];
    for(int i=0; i < pNode.Children.Count; i++)
    {
        values[i] = Execute(pNode.Children[i]);  // recursive
    }
    return pNode.Process(values);
}

public void Start(Node pRoot)
{
    Data result = Execute(pRoot);
}

これは正常に機能しますが、コールスタックがノードツリーのサイズを制限するのではないかと心配しています。

Executeへの再帰呼び出しが行われないようにコードを書き換えるにはどうすればよいですか?

20
Reactgular

次に、再帰を使用しない汎用ツリートラバーサルの実装を示します。

public static IEnumerable<T> Traverse<T>(T item, Func<T, IEnumerable<T>> childSelector)
{
    var stack = new Stack<T>();
    stack.Push(item);
    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in childSelector(next))
            stack.Push(child);
    }
}

あなたの場合、それを次のように呼び出すことができます:

IEnumerable<Node> allNodes = Traverse(pRoot, node => node.Children);

深さを優先するのではなく、呼吸を優先して検索する場合は、QueueではなくStackを使用してください。 PriorityQueueを使用して、最適な最初の検索を行います。

29
Servy

事前にツリーの深さの見積もりがある場合、スタックサイズを調整するだけで十分でしょうか?バージョン2.0以降のC#では、新しいスレッドを開始するたびにこれが可能です。以下を参照してください。

http://www.atalasoft.com/cs/blogs/rickm/archive/2008/04/22/increasing-the-size-of-your-stack-net-memory-management-part-3。 aspx

そうすれば、より複雑なものを実装する必要なく、再帰的なコードを保持できます。もちろん、独自のスタックを使用して非再帰的なソリューションを作成すると、時間とメモリの効率が向上する可能性がありますが、コードは今ほど単純ではないはずです。

4
Doc Brown