与其尝试专门解决表达式树的问题,不如让我为您描述一些处理不良行为树的通用技术。
您可能想先阅读我关于解决您提出的问题的系列文章:如何在不使用递归的情况下确定树的深度?
http://blogs.msdn.com/b/ericlippert/archive/2005/07/27/recursion-part-one-recursive-data-structures-and-functions.aspx
那些文章是我在做 JScript 的时候写的,所以例子都是用 JScript 写的。不过,如何将这些概念应用到 C# 中并不难。
让我给你一个 C# 中的小玩具示例,说明如何在不进行完全递归的情况下对递归数据结构进行操作。假设我们有以下二叉树:(假设 WOLOG 的二叉树节点是零个或两个子节点,从不完全是一个。)
class Node
{
public Node Left { get; private set; }
public Node Right { get; private set; }
public string Value { get; private set; }
public Node(string value) : this(null, null, value) {}
public Node(Node left, Node right, string value)
{
this.Left = left;
this.Right = right;
this.Value = value;
}
}
...
Node n1 = new Node("1");
Node n2 = new Node("2");
Node n3 = new Node("3");
Node n3 = new Node("4");
Node n5 = new Node("5");
Node p1 = new Node(n1, n2, "+");
Node p2 = new Node(p1, n3, "*");
Node p3 = new Node(n4, n5, "+");
Node p4 = new Node(p2, p3, "-");
所以我们有树 p4:
-
/ \
* +
/ \ / \
+ 3 4 5
/ \
1 2
我们希望将 p4 打印为带括号的表达式
(((1+2)*3)-(4+5))
递归解决方案很简单:
static void RecursiveToString(Node node, StringBuilder sb)
{
// Again, assuming either zero or two children.
if (node.Left != null)
sb.Append(node.Value);
else
{
sb.Append("(");
RecursiveToString(node.Left, sb);
sb.Append(node.Value);
RecursiveToString(node.Right, sb);
sb.Append(")");
}
}
现在假设我们知道这棵树可能在左侧“深”,而在右侧“浅”。我们可以消除左边的递归吗?
static void RightRecursiveToString(Node node, StringBuilder sb)
{
// Again, assuming either zero or two children.
var stack = new Stack<Node>();
stack.Push(node);
while(stack.Peek().Left != null)
{
sb.Append("(");
stack.Push(stack.Peek().Left);
}
while(stack.Count != 0)
{
Node current = stack.Pop();
sb.Append(current.Value);
if (current.Right != null)
RightRecursiveToString(current.Right, sb);
sb.Append(")");
}
}
}
仅右递归版本当然更难阅读,也更难推理,但它不会破坏堆栈。
让我们来看看我们的例子。
push p4
push p2
output (
push p1
output (
push n1
output (
loop condition is met
pop n1
output 1
go back to the top of the loop
pop p1
output +
recurse on n2 -- this outputs 2
output )
go back to the top of the loop
pop p2
output *
recurse on n3 -- this outputs 3
output )
go back to the top of the loop
pop p4
output -
recurse on p3
push p3
push n4
output (
loop condition is met
pop n4
output 4
go back to the top of the loop
pop p3
output +
recurse on n5 -- this outputs 5
output )
loop condition is not met; return.
output )
loop condition is not met, return.
我们输出什么? (((1+2)*3)-(4+5)),根据需要。
所以你在这里看到我可以从两个递归减少到一个。我们可以使用类似的技术从一个递归到没有递归。让这个算法完全迭代——这样它既不会在左边也不会在右边递归——留作练习。
(顺便说一句:我问这个问题的一个变体作为一个面试问题,所以如果你被我面试过,你现在有一个不公平的优势!)