1、二叉树树的递归遍历,程序简洁,前、中、后遍历相差不大。
本例中用到的二叉树为:
前序遍历为:10 6 4 8 14 12 16
中序遍历为:4 6 8 10 12 14 16
后续遍历为:4 8 6 12 16 14 10
非递归代码:`
递归版的前序遍历:
private static void TraverseTree(BinaryTreeNode root,ArrayList<Integer> list) {
if(root==null)
return ;
list.add(root.val);
if(root.leftchild!=null)
TraverseTree(root.leftchild,list);
if(root.rightchild!=null)
TraverseTree(root.rightchild,list);
}
递归版的中序遍历:
private static void TraverseTree(BinaryTreeNode root,ArrayList<Integer> list) {
if(root==null)
return ;
if(root.leftchild!=null)
TraverseTree(root.leftchild,list);
list.add(root.val);
if(root.rightchild!=null)
TraverseTree(root.rightchild,list);
}
递归版的后序遍历
private static void TraverseTree(BinaryTreeNode root,ArrayList<Integer> list) {
if(root==null)
return ;
if(root.leftchild!=null)
TraverseTree(root.leftchild,list);
if(root.rightchild!=null)
TraverseTree(root.rightchild,list);
list.add(root.val);
}
从上面的代码块可以看出,递归版的前、中、后序遍历唯一的差别处就是什么时候将根节点保存到list中;
2、非递归版的二叉树遍历
很多的递归问题都可以用循环来解决,事实上递归也是基于栈来实现的,但是利用栈实现递归的时候,就得考虑栈的劣势可能引发的问题。
这儿顺便总结一下递归用栈的缺点:
- 递归由于是函数调用自身,而函数调用是有时间和空间消耗的:每一次函数调用都需要在内存栈中分配空间以保存参数、临时变量及返回地址,而且往栈里亚人和弹出数据都需要时间。
- 递归中有很多计算都是重复的,从而对性能能带来很大的负面影响。这一点在斐波那契数列的计算上体现尤其的明显。
- 除了效率之外,递归还有可能引起更严重的问题:调用栈溢出。每一调用函数都需要在内存栈中为其分配空间,而每个进程的栈容量是有限的,当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。
虽然栈的递归使用有很多的问题,但是在非递归计算过程中,栈的使用又是不可避免的,特别是在树的循环遍历中需要保存之前访问过的节点,那么这时也只能使用栈来中转啦!
非递归前序遍历代码:
public static void preOrderTraversal(BinaryTreeNode root,ArrayList<Integer> list){
if(root==null)
return ;
Stack<BinaryTreeNode> s=new Stack<BinaryTreeNode>();
BinaryTreeNode p=root;
while(p!=null||!s.isEmpty()){
while(p!=null){
list.add(p.val);
s.push(p);
p=p.leftchild;
}
if(!s.isEmpty()){
p=s.pop();
p=p.rightchild;
}
}
}
非递归中序遍历代码:
public static void InOrderTraversal(BinaryTreeNode root ,ArrayList<Integer> list){
Stack<BinaryTreeNode> stack=new Stack<BinaryTreeNode>();
if(root==null)
return ;
BinaryTreeNode p=root;
while(p!=null||!stack.isEmpty()){
while(p!=null){
stack.push(p);
p=p.leftchild;
}
if(!stack.isEmpty()){
p=stack.pop();
list.add(p.val);
p=p.rightchild;
}
}
}
非递归后续遍历代码:
public static void PostOrderTraversal(BinaryTreeNode root,ArrayList<Integer> list){
if(root==null)
return ;
Stack<BinaryTreeNode> stack=new Stack<BinaryTreeNode>();
Stack<Integer> s=new Stack<Integer>();
BinaryTreeNode p=root;
while(p!=null||!stack.isEmpty()){
while(p!=null){
stack.push(p);
s.push(p.val);
p=p.rightchild;
}
if(!stack.isEmpty()){
p=stack.pop();
p=p.leftchild;
}
}
while(!s.isEmpty()){
list.add(s.pop());
}
}
后续遍历代码较前、中序遍历代码有较大的改动因为根节点是最后被访问的。但是通过观察后续遍历的结果:4 8 6 12 16 14 10 ,其逆过来看为:10 14 16 12 6 8 4,再看前序遍历的结果:10 6 4 8 14 12 16,二者比较发现,逆后序遍历就是在前序遍历的基础上稍作改动:先访问根节点,再访问右孩子,再访问做孩子,结果就是逆后序遍历,得到逆后序遍历,再将其反转,就可以得到后续遍历的结果。
测试用的代码如下:
class BinaryTreeNode{
int val;
BinaryTreeNode leftchild=null;
BinaryTreeNode rightchild=null;
public BinaryTreeNode(int val){
this.val=val;
}
}
public class TreeTraverse {
public static void main(String[] args) {
BinaryTreeNode root=new BinaryTreeNode(10);
BinaryTreeNode node6=new BinaryTreeNode(6);
BinaryTreeNode node14=new BinaryTreeNode(14);
root.leftchild=node6;
root.rightchild=node14;
BinaryTreeNode node4=new BinaryTreeNode(4);
BinaryTreeNode node8=new BinaryTreeNode(8);
node6.leftchild=node4;
node6.rightchild=node8;
BinaryTreeNode node12=new BinaryTreeNode(12);
BinaryTreeNode node16=new BinaryTreeNode(16);
node14.leftchild=node12;
node14.rightchild=node16;
ArrayList<Integer> list= new ArrayList<Integer>();
TraverseTree(root,list);
preOrderTraversal(root,list);
InOrderTraversal(root,list);
PostOrderTraversal(root,list);
System.out.println(list);
}
}
前、中、后序遍历结果如下:
前序遍历结果为:
[10, 6, 4, 8, 14, 12, 16]
中序遍历结果为:
[4, 6, 8, 10, 12, 14, 16]
后序遍历结果为:
[4, 8, 6, 12, 16, 14, 10]