次の再帰的な方法があります。
これは、深い最初の検索トラバーサルを使用して、ノード(論理式を表す)を評価します。
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;
}
}
結果を保存するために別のスタックが必要になると思いますが、これを理解することはできませんでした。
幅優先探索を行っています。これにアプローチする典型的な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では、子が親の直前にあることに注意してください。可能な実装はここにあります:
ここで変更する必要があるのは戻り値の型だけであり、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;
}
一般的な答えを出してみたいと思います。再帰is実際にはスタック(コールスタック)を使用しているため、任意の再帰関数は、スタックを使用して簡単な方法で反復関数に変換できます。コールスタックをシミュレートするだけです。
コールスタックには次のものが含まれます。
これらの構造体を作成します。
struct Context {
Arguments args;
LocalVars lvars;
ExecutionState state;
}
Arguments
は簡単です:引数は再帰呼び出しの引数の値に設定されます。
トリッキーな部分は「リターンアドレス」、または私がここでExecutionState
と呼び、LocalVars
です。
ExecutionState
は、「再帰呼び出し」が戻った後に実行を再開するために、呼び出しが行われた場所を追跡する必要があります。あなたの例では、3つの可能な状態があります:
LocalVars
には、result
とChildren
ループイテレータが含まれます。呼び出しはループ内で行われるため、中断して後で再開する必要があるため、ループ反復子もスタック上になければなりません。
更新:ここでは、テストされていない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;
}
非再帰的な方法で算術式を評価する問題は解決され、50年以上前に初めて公開されました。あなたが探しているのは ダイクストラの 操車場アルゴリズムです。 ウィキペディアの記事 にはアルゴリズムの優れた説明があり、Googleには何万ものヒットがあります。 リテラシープログラムの記事 は説明と実装。
これが私がこれまでに持ってきた最良の解決策です。それは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;
}
}