树的遍历
之前的工作都没有接触到树,也就很少研究它。幸运地的是,在目前的工作中多次遇到树型结构的数据,那么访问树节点中的数据就是必然的了,而且还需要按照指定规则对节点中的数据进行额外处理。经过学习之后,对与树相关的基本算法有了一些认知,就计划写几篇小文。其实这样的文章早已是汗牛充栋,而我只是把它当作我的学习总结罢了,以加深记忆与理解,如能对其他朋友有所助益,则更感愉悦了 :-) (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();
}
* 树节点的定义。
*/
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();
}
}
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();
}
}
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;
}
}
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)); // 递归地访问当前节点的所有子节点。
}
}
大家肯定知道,系统在执行递归方法(对于其它方法也是如此)时是使用运行时栈。对方法的每一次调用,在栈中都会创建一份此次调用的活动记录--包括方法的参数,局部变量,返回地址,动态链接库,返回值等。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());
}
}
}
与递归法相比,迭代法的代码略多了几行,但仍然很简单。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)。
如果树的层次不太深,每层的子节点数不太多,那么使用递归法应该是没有问题的。毕竟,简洁地程序会提供更多的好处。