web-dev-qa-db-ja.com

次のノード評価手順を非再帰的ソリューションに変換するにはどうすればよいですか?

次の再帰的な方法があります。

これは、深い最初の検索トラバーサルを使用して、ノード(論理式を表す)を評価します。

EvaluateNode(Node node)
{
    bool result;
    switch(node.Type)
    {
        case AND_OPERATOR:
            result = true;
            foreach(Node nodeChild in node.Childrens)
            {
                childresult = EvaluateNode(nodeChild);
                result = result && childresult;
            }
        case OR_OPERATOR:
            result = false;                
            foreach(Node nodeChild in node.Childrens)
            {
                childresult = EvaluateNode(nodeChild);
                result = result || childresult;
            }
        case VALUE:
            result = node.Value;
    }

    return result;
}

これをスタック/キューベースの非再帰的ソリューションに変換したいと思います。これが私がこれまでに試したことです(不完全です):

bool result;
stack.Push(node);
while(!stack.Empty())
{
    Node node = stack.Pop();
    switch(node.Type)
    {
        case AND_OPERATOR:
            foreach(Node nodeChild in node.Childrens)
            {
                stack.Push(nodeChild);
            }   
        case OR_OPERATOR:
            foreach(Node nodeChild in node.Childrens)
            {
                stack.Push(nodeChild);
            }           
        case VALUE:
            result = node.Value;
    }
}

結果を保存するために別のスタックが必要になると思いますが、これを理解することはできませんでした。

6
tigrou

幅優先探索を行っています。これにアプローチする典型的な2つの方法があります:再帰的および非再帰的です。非再帰的アルゴリズムの擬似コードは次の場所にあります。

http://en.wikipedia.org/wiki/Breadth-first_search#Algorithm

もちろん、自分のニーズに合わせて調整する必要がありますが、実際にはプリンシパルは変更されません。

[〜#〜]更新[〜#〜]

Ben Aaronson は正しいです(以下のコメント)。これは幅優先ではありません。深さ優先です。

http://en.wikipedia.org/wiki/Depth-first_search#Pseudocode

混乱させて申し訳ありません。

UPDATE 2

これは実際にはOPの問題にも対処していないと思います。つまり、各ノードをどうするかはその親に依存するため、個々のノードをスタックにプッシュするだけでは不十分です Ben Aaronson

同意しません。ツリーのトラバーサルと評価は、互いに分離できます。これがあなたのやり方です:

IEnumerable<Node> PostOrderDFS(Node root)
{
  ...
}

ポストオーダーDFSでは、子が親の直前にあることに注意してください。可能な実装はここにあります:

http://blogs.msdn.com/b/daveremy/archive/2010/03/16/non-recursive-post-order-depth-first-traversal.aspx

ここで変更する必要があるのは戻り値の型だけであり、yield returnではなくConsole.Writeを作成します。

評価は次のようになります。

bool Evaluate(Node root)
{
  List<Tuple<Node, bool>> traverseList = PostOrderDFS(root).Select(n => Tuple.Create(n, false)).ToList();
  for(int i = 0; i < traverseList.Count; ++i)
  {
    Tuple<Node, bool> Tuple = traverseList[i];
    Node node = Tuple.Item1;
    switch(node.Type)
    {
    case AND_OPERATOR:
        Tuple.Item2 = true;
        foreach(int j = 1; j <= node.Childrens.Count; ++j)
        {
            bool childresult = traverseList[i-j].Item2;
            Tuple.Item2 = Tuple.Item2 && childresult;
        }
    case OR_OPERATOR:
        Tuple.Item2 = false;
        foreach(int j = 1; j <= node.Childrens.Count; ++j)
        {
            bool childresult = traverseList[i-j].Item2;
            Tuple.Item2 = Tuple.Item2 || childresult;
        }
    case VALUE:
        Tuple.Item2 = node.Value;
    }
  }

  return traverseList.Last().Item2;
}
3
Gábor Angyal

一般的な答えを出してみたいと思います。再帰is実際にはスタック(コールスタック)を使用しているため、任意の再帰関数は、スタックを使用して簡単な方法で反復関数に変換できます。コールスタックをシミュレートするだけです。

コールスタックには次のものが含まれます。

  • 引数
  • リターンアドレス
  • とローカル変数。

これらの構造体を作成します。

struct Context {
    Arguments args;
    LocalVars lvars;
    ExecutionState state;
}

Argumentsは簡単です:引数は再帰呼び出しの引数の値に設定されます。

トリッキーな部分は「リターンアドレス」、または私がここでExecutionStateと呼び、LocalVarsです。

ExecutionStateは、「再帰呼び出し」が戻った後に実行を再開するために、呼び出しが行われた場所を追跡する必要があります。あなたの例では、3つの可能な状態があります:

  • 評価する
  • 最初の呼び出しから戻った
  • 2回目の通話から戻った

LocalVarsには、resultChildrenループイテレータが含まれます。呼び出しはループ内で行われるため、中断して後で再開する必要があるため、ループ反復子もスタック上になければなりません。

更新:ここでは、テストされていないC++ /#擬似コードバージョンを作成しました。 OP(@tigrou)はいくつかのエラーを修正し、動作するC#バージョンを作成しました。

enum ExecutionState
{
    S_EVAL,
    S_CALL1,
    S_CALL2 
};

enum NodeType
{
    AND_OPERATOR,
    OR_OPERATOR,
    VALUE   
};

class LocalVars 
{
    public IEnumerator<Node> Enumerator;
    public bool Result;
}

class Context
{
    public Node Node;       
    public LocalVars LocalVariables;        
    public ExecutionState State;

    public Context(Node node, ExecutionState state)
    {
        this.Node = node;
        this.State = state;
        this.LocalVariables = new LocalVars();
    }
};

class Node
{
    public bool Value;          
    public NodeType Type;
    public List<Node> Childrens;
};

static bool EvaluateNode(Node node)
{
    Stack<Context> stack = new Stack<Context>();
    Context context = new Context(node, ExecutionState.S_EVAL);            
    stack.Push(context);

    bool returnresult = false;
    while(stack.Any())
    {
        context = stack.Pop();
        switch(context.State)
        {
            case ExecutionState.S_EVAL:
                switch(context.Node.Type)
                {
                    case NodeType.AND_OPERATOR:
                        if(context.LocalVariables.Enumerator == null) 
                        {
                            context.LocalVariables.Enumerator = context.Node.Childrens.GetEnumerator();
                            context.LocalVariables.Result = true;
                        }

                        if(context.LocalVariables.Enumerator.MoveNext())
                        {
                            context.State = ExecutionState.S_CALL1;
                            stack.Push(context);        // Push resume with S_CALL1

                            Context call = new Context(context.LocalVariables.Enumerator.Current, ExecutionState.S_EVAL);                                                  
                            stack.Push(call);           // Push call
                        } 
                        else
                        {
                            returnresult = context.LocalVariables.Result;    // no Push -> return
                        }
                        break;

                    case NodeType.OR_OPERATOR:
                        if(context.LocalVariables.Enumerator == null) 
                        {
                            context.LocalVariables.Enumerator = context.Node.Childrens.GetEnumerator();
                            context.LocalVariables.Result = false;
                        }

                        if(context.LocalVariables.Enumerator.MoveNext())
                        {
                            context.State = ExecutionState.S_CALL2;
                            stack.Push(context);        // Push resume with S_CALL2

                            Context call = new Context(context.LocalVariables.Enumerator.Current, ExecutionState.S_EVAL);                                                  
                            stack.Push(call);           // Push call
                        } 
                        else
                        {
                            returnresult = context.LocalVariables.Result;    // no Push -> return
                        }
                        break;

                    case NodeType.VALUE:
                        returnresult = context.Node.Value;  // no Push -> return
                        break;
                }
                break;

            case ExecutionState.S_CALL1:
                context.LocalVariables.Result = context.LocalVariables.Result && returnresult;
                context.State = ExecutionState.S_EVAL;
                stack.Push(context);                        // continue with S_EVAL
                break;

            case ExecutionState.S_CALL2:
                context.LocalVariables.Result = context.LocalVariables.Result || returnresult;
                context.State = ExecutionState.S_EVAL;
                stack.Push(context);                        // continue with S_EVAL
                break;
        }
    }

    return returnresult;
}
4
alain

非再帰的な方法で算術式を評価する問題は解決され、50年以上前に初めて公開されました。あなたが探しているのは ダイクストラの 操車場アルゴリズムです。 ウィキペディアの記事 にはアルゴリズムの優れた説明があり、Googleには何万ものヒットがあります。 リテラシープログラムの記事 は説明と実装。

3
Ross Patterson

これが私がこれまでに持ってきた最良の解決策です。それはGáborAngyalの答えに基づいています。

これは彼の実装とは少し異なるので、誰かに役立つと思われるので、ここに投稿します。 GetNodesDFS()が実装されているため、再帰性を使用していることに注意してください。 DFSトラバーサルのスタックベースの実装は、レビュー(メンテナンス)の記述とコード化が簡単ではないため、私はそのようにしました。

結局、再帰的な部分がノード評価を処理するメインループから分離されているので、OPの最初の再帰的な例からの解決策を好みます。実際のコードはこれらの例よりもはるかに複雑であり、OPのように再帰性を使用すると、再帰呼び出しごとに多くの変数を渡す必要がありました(ここでは問題ではありません)。

nodesDFS = GetNodesDFS(nodes, x => x.Childrens.Reverse());
foreach(Node node in nodesDFS)
{
    switch(node.Type)
    {
        case AND_OPERATOR:
            result = true;
            for(i = 0 ; i < node.Childrens.Count ; i++)
            {
                result = result && results.Pop();
            }   
            results.Push(result);
        case OR_OPERATOR:
            result = false;
            for(i = 0 ; i < node.Childrens.Count ; i++)
            {
                result = result || results.Pop();
            }    
            results.Push(result);       
        case VALUE:
            results.Push(node.Value); 
    }        
}
finalresult = evaluatedNodes.Pop();

GetNodesDFS()はこのように実装されます(上記の注釈を参照):

GetNodesDFS(source, descendby)
{
   foreach (value in source)
   {
       foreach (child in descendby(value).GetNodesDFS(descendBy))
       {
           yield return child;
       }
       yield return value;
   }
}
0
tigrou