树的遍历(基础)

树的遍历
之前的工作都没有接触到树,也就很少研究它。幸运地的是,在目前的工作中多次遇到树型结构的数据,那么访问树节点中的数据就是必然的了,而且还需要按照指定规则对节点中的数据进行额外处理。经过学习之后,对与树相关的基本算法有了一些认知,就计划写几篇小文。其实这样的文章早已是汗牛充栋,而我只是把它当作我的学习总结罢了,以加深记忆与理解,如能对其他朋友有所助益,则更感愉悦了 :-) (2009.04.03最后更新)

这次先从最基础的开始--树的遍历。本文使用了两种极常用的方法来遍历树中的所有节点--递归;迭代,但它们实现的都是深度优先(Depth-First)算法。

1. 树节点与数据
先定义树节点及数据(用户对象),并创建测试用的数据。
TreeNode是树节点的定义。
/**
 * 树节点的定义。
 
*/
public   interface  TreeNode {

    
/**
     * 获取指定下标处的子节点。
     * 
     * 
@param  index
     *            下标。
     * 
@return  子节点。
     
*/
    
public  TreeNode getChildAt( int  index);

    
/**
     * 返回指定子节点的下标。
     * 
     * 
@param  index
     *            下标。
     * 
@return  子节点。
     
*/
    
public   int  getChildIndex(TreeNode index);

    
/**
     * 获取子节点的数量。
     * 
     * 
@return  子节点的数量。
     
*/
    
public   int  getChildCount();

    
/**
     * 返回父节点。
     * 
     * 
@return  父节点。
     
*/
    
public  TreeNode getParent();

    
/**
     * 设置父节点。注:此处不需要改变父节点中的子节点元素。
     * 
     * 
@param  parent
     *            父节点。
     
*/
    
public   void  setParent(TreeNode parent);

    
/**
     * 获取所有的子节点。
     * 
     * 
@return  子节点的集合。
     
*/
    
public  List <?>  getChildren();

    
/**
     * 是否为叶节点。
     * 
     * 
@return  是叶节点,返回true;否则,返回false。
     
*/
    
public   boolean  isLeaf();
}

GenericTreeNode是一个通用的树节点实现。
public   class  GenericTreeNode < T >   implements  TreeNode {

    
private  T userObject  =   null ;

    
private  TreeNode parent  =   null ;

    
private  List < GenericTreeNode < T >>  children  =   new  ArrayList < GenericTreeNode < T >> ();

    
public  GenericTreeNode(T userObject) {
        
this .userObject  =  userObject;
    }

    
public  GenericTreeNode() {
        
this ( null );
    }

    
/**
     * 添加子节点。
     * 
     * 
@param  child
     
*/
    
public   void  addChild(GenericTreeNode < T >  child) {
        children.add(child);
        child.setParent(
this );
    }

    
/**
     * 删除指定的子节点。
     * 
     * 
@param  child
     *            子节点。
     
*/
    
public   void  removeChild(TreeNode child) {
        removeChildAt(getChildIndex(child));
    }

    
/**
     * 删除指定下标处的子节点。
     * 
     * 
@param  index
     *            下标。
     
*/
    
public   void  removeChildAt( int  index) {
        TreeNode child 
=  getChildAt(index);
        children.remove(index);
        child.setParent(
null );
    }

    
public  TreeNode getChildAt( int  index) {
        
return  children.get(index);
    }

    
public   int  getChildCount() {
        
return  children.size();
    }

    
public   int  getChildIndex(TreeNode child) {
        
return  children.indexOf(child);
    }

    
public  List < GenericTreeNode < T >>  getChildren() {
        
return  Collections.unmodifiableList(children);
    }

    
public   void  setParent(TreeNode parent) {
        
this .parent  =  parent;
    }

    
public  TreeNode getParent() {
        
return  parent;
    }

    
/**
     * 是否为根节点。
     * 
     * 
@return  是根节点,返回true;否则,返回false。
     
*/
    
public   boolean  isRoot() {
        
return  getParent()  ==   null ;
    }

    
public   boolean  isLeaf() {
        
return  getChildCount()  ==   0 ;
    }

    
/**
     * 判断指定的节点是否为当前节点的子节点。
     * 
     * 
@param  node
     *            节点。
     * 
@return  是当前节点的子节点,返回true;否则,返回false。
     
*/
    
public   boolean  isChild(TreeNode node) {
        
boolean  result;
        
if  (node  ==   null ) {
            result 
=   false ;
        } 
else  {
            
if  (getChildCount()  ==   0 ) {
                result 
=   false ;
            } 
else  {
                result 
=  (node.getParent()  ==   this );
            }
        }

        
return  result;
    }

    
public  T getUserObject() {
        
return  userObject;
    }

    
public   void  setUserObject(T userObject) {
        
this .userObject  =  userObject;
    }

    @Override
    
public  String toString() {
        
return  userObject  ==   null   ?   ""  : userObject.toString();
    }
}

UserObject是节点上的用户对象,相当于是数据。
public   class  UserObject {

    
private  String name  =   null ;

    
private  Integer value  =  Integer.valueOf( 0 );

    
public  UserObject() {

    }

    
public  UserObject(String code, Integer value) {
        
this .name  =  code;
        
this .value  =  value;
    }

    
public  String getName() {
        
return  name;
    }

    
public   void  setName(String code) {
        
this .name  =  code;
    }

    
public  Integer getValue() {
        
return  value;
    }

    
public   void  setValue(Integer value) {
        
this .value  =  value;
    }

    @Override
    
public  String toString() {
        StringBuilder result 
=   new  StringBuilder();
        result.append(
" [name= " ).append(name).append( " , value= " ).append(value).append( " ] " );
        
return  result.toString();
    }
}

TreeUtils是用于创建树的工具类。
public   class  TreeUtils {

    
public   static  GenericTreeNode < UserObject >  buildTree() {
        GenericTreeNode
< UserObject >  root  =   new  GenericTreeNode < UserObject > ();
        root.setUserObject(
new  UserObject( " ROOT " , Integer.valueOf( 0 )));

        GenericTreeNode
< UserObject >  node1  =   new  GenericTreeNode < UserObject > ();
        node1.setUserObject(
new  UserObject( " 1 " , Integer.valueOf( 0 )));
        GenericTreeNode
< UserObject >  node2  =   new  GenericTreeNode < UserObject > ();
        node2.setUserObject(
new  UserObject( " 2 " , Integer.valueOf( 0 )));
        GenericTreeNode
< UserObject >  node3  =   new  GenericTreeNode < UserObject > ();
        node3.setUserObject(
new  UserObject( " 3 " , Integer.valueOf( 5 )));

        root.addChild(node1);
        root.addChild(node2);
        root.addChild(node3);

        GenericTreeNode
< UserObject >  node11  =   new  GenericTreeNode < UserObject > ();
        node11.setUserObject(
new  UserObject( " 11 " , Integer.valueOf( 0 )));
        GenericTreeNode
< UserObject >  node21  =   new  GenericTreeNode < UserObject > ();
        node21.setUserObject(
new  UserObject( " 21 " , Integer.valueOf( 0 )));

        node1.addChild(node11);
        node2.addChild(node21);

        GenericTreeNode
< UserObject >  node111  =   new  GenericTreeNode < UserObject > ();
        node111.setUserObject(
new  UserObject( " 111 " , Integer.valueOf( 3 )));
        GenericTreeNode
< UserObject >  node112  =   new  GenericTreeNode < UserObject > ();
        node112.setUserObject(
new  UserObject( " 112 " , Integer.valueOf( 9 )));
        GenericTreeNode
< UserObject >  node211  =   new  GenericTreeNode < UserObject > ();
        node211.setUserObject(
new  UserObject( " 211 " , Integer.valueOf( 6 )));
        GenericTreeNode
< UserObject >  node212  =   new  GenericTreeNode < UserObject > ();
        node212.setUserObject(
new  UserObject( " 212 " , Integer.valueOf( 3 )));

        node11.addChild(node111);
        node11.addChild(node112);
        node21.addChild(node211);
        node21.addChild(node212);

        
return  root;
    }
}

2. 递归法
使用递归法的最大好处就是--简单,但一般地,我们都认为递归的效率不高。
private   static   void  recursiveTravel(GenericTreeNode < UserObject >  node) {
    travelNode(node); 
//  访问节点,仅仅只是打印该节点罢了。
    List < GenericTreeNode < UserObject >>  children  =  node.getChildren();
    
for  ( int  i  =   0 ; i  <  children.size(); i ++ ) {
        recursiveTravel(children.get(i)); 
//  递归地访问当前节点的所有子节点。
    }
}
大家肯定知道,系统在执行递归方法(对于其它方法也是如此)时是使用运行时栈。对方法的每一次调用,在栈中都会创建一份此次调用的活动记录--包括方法的参数,局部变量,返回地址,动态链接库,返回值等。
既然系统能够隐式地使用栈去执行递归方法,那么我们就可以显式地使用栈来执行上述递归程序,这也是将递归程序转化为迭代程序的常用思想。下面的iterativeTravel方法就运用了这一思想。

3. 迭代法
private   static   void  iterativeTravel(GenericTreeNode < UserObject >  node) {
    Stack
< GenericTreeNode < UserObject >>  nodes  =   new  Stack < GenericTreeNode < UserObject >> ();

    nodes.push(node); 
//  将当前节点压入栈中。
    while  ( ! nodes.isEmpty()) {
        GenericTreeNode
< UserObject >  bufNode  =  nodes.pop();  //  从栈中取出一个节点。
        travelNode(bufNode);  //  访问节点。
         if  ( ! bufNode.isLeaf()) {  //  如果该节点为分枝节点,则将它的子节点全部加入栈中。
            nodes.addAll(bufNode.getChildren());
        }
    }
}
与递归法相比,迭代法的代码略多了几行,但仍然很简单。

4. 小结
由于上述两种方法均(隐式或显式地)使用了运行栈,所以此处的迭代法并不能提高整个程序的效率。相反地,由于在应用程序中显式地使用栈(java.util.Stack),iterativeTravel方法的效率可能反而更低。但iterativeTravel的最大好处是,能够有效地避免运行时栈溢出(java.lang.StackOverflowError)。
如果树的层次不太深,每层的子节点数不太多,那么使用递归法应该是没有问题的。毕竟,简洁地程序会提供更多的好处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值