二叉树节点:
public class Node
{
public int data = 0;
public Node lchild = null;
public Node rchild = null;
}
前序遍历:
List<Node> PreorderTraverse(Node root)
{
List<Node> nodes = new List<Node>();
if (root == null)
{
return nodes;
}
Stack<Node> stack = new Stack<Node>();
stack.Push(root);
while (stack.Count > 0)
{
Node node = stack.Pop();
nodes.Add(node);
if (node.rchild != null)
{
stack.Push(node.rchild);
}
if (node.lchild != null)
{
stack.Push(node.lchild);
}
}
return nodes;
}
前序遍历非常简单,先把根节点压入栈中,然后每从栈里取一个节点,就把它的两个子节点放入栈中,这样循环直到栈空即可。
List<Node> InorderTraverse(Node root)
{
List<Node> nodes = new List<Node>();
if (root == null)
{
return nodes;
}
Node node = root;
Stack<Node> stack = new Stack<Node>();
while (node != null || stack.Count > 0)
{
if (node != null) // 状况1
{
stack.Push(node);
node = node.lchild; // node变为状况1或3的节点
}
else // 状况3
{
node = stack.Pop(); // node变为状况2的节点
nodes.Add(node);
node = node1.rchild; // node变为状况1或3的节点
}
}
return nodes;
}
中序遍历稍微麻烦点,因为我们每次拿到手的节点有三种状况:
1. 它是一个尚未遍历的子树的根节点,其左子树还没处理,所以我们要把它入栈,然后处理它的左子节点,继续状况1或3。
2. 它是左子树遍历完成后的从栈取出的父节点,它的左子树已经处理完毕,只要把它输出然后就可以忽略它了,然后处理它的右子节点,回到状况1或3。
3. 它是空的,直接从栈中取新的节点,回到状况2。
后序遍历:
List<Node> PostorderTraverse(Node root)
{
List<Node> nodes = new List<Node>();
if (root == null)
{
return nodes;
}
Stack<Node> stack = new Stack<Node>();
stack.Push(root);
Node pnode = null;
while (stack.Count > 0)
{
Node node = stack.Peek();
// 如果pnode不是node的左/右子节点,表明node节点是以左/右子节点身份入栈的,那么以它为根的子树尚未遍历,需要把它的左右子节点入栈
if (pnode == null || (pnode != node.lchild && pnode != node.rchild))
{
if (node.rchild != null)
{
stack.Push(node.rchild);
}
if (node.lchild != null)
{
stack.Push(node.lchild);
}
}
// 如果它还是栈顶(没有子节点入栈),那么它一定是没有子节点或者是处理完子节点后退回到它
if (stack.Peek() == node)
{
nodes.Add(node);
stack.Pop();
pnode = node;
}
}
return nodes;
}
后续遍历是最复杂的,主要是从栈中取出节点node的时候,node有多种情况(前序和中序里的栈顶节点都是直接输出并抛弃,省心)
先看每个节点入栈时的状况:
1. 它是以子树的根节点的身份入栈的,一定同时还会把它的右子节点(状况2)和左子节点(状况3)入栈并压在它上面,当轮到它出栈时,它的左右子节点一定已先出栈并处理过了,此时我们直接输出它,代表以它为根的子树已经全部处理完毕
2. 它是以一个右子节点身份入栈的,那么轮到它出栈的时候,它是一颗尚未处理的子树的根节点,所以需要重新入栈进入状况1
3. 它是以一个左子节点身份入栈的,处理方法同2
问题是当我们如何才能得知从栈顶弹出的node是状况1还是状况23下入栈的呢?这得看我们最近一次输出的节点pnode跟它的关系了:
1 如果pnode是node的子节点,则node一定是以状况1入栈的,如果是状况2/3,它的子节点都没入过栈,更不可能输出过。所以按照状态1直接输出node就好了
2 如果pnode跟node无关,则按状况2/3处理
3 如果pnode跟node无关,且node本身是叶节点,我们也把按照状态1直接输出它