写在前面
正巧在看《算法:C语言实现》这本书,翻到树遍历这一篇。文章中生动形象的列出了前、中、后序遍历二叉树的过程,和算法思路。恰逢我又失眠,然后想看下Java实现。发现大多人的实现并没有书中实现的那么直观,甚至有些晦涩,于是我整理了下书中提供的算法,供大家参考。
话不多说
书中提到 ”为简单起见,我们从一个抽象栈开始考察,这个栈能够保存数据项或树,以将被遍历的树初始化。然后,我们进入一个循环,在这个循环中我们弹出并处理栈顶的元素,如此继续,直到栈空为止。如果弹出的元素是一个数据项,就访问它;如果弹出的元素是一颗树,就按照希望的顺序执行一系列的入栈操作:“
- 对于前序,我们压入右子树,然后左子树,最后是节点。
- 对于中序,我们压入右子树,然后是节点,最后是左子树。
- 对于后序,我们压入节点,然后是右子树,最后是左子树。
下面先看下书中前序遍历的实现C :
void traverse(link h, void (*visit)(link))
{
STACKinit(max); STACKpush(h);
while(!STACKempty())
{
(*visit)(h = STACKpop());
if(h -> r != NULL) STACKpush(h -> r)
if(h -> r != NULL) STACKpush(h -> l);
}
}
对比下其他的实现:
void TreversePreorder(struct BTree *T)
{
struct BTree *stack[1000],*p=T;
int top=0;
while(p||top)
{
if(p)
{
printf("%d ",p->data);
stack[top++]=p;
p=p->left;
}
else
{
p=stack[--top];
p=p->right;
}
}
}
这种实现节点的顺序没有直接在栈中表现,一开始就直接找到最左边节点,然后从下往上走,出栈后只找右节点。
相比之下,我还是更倾向书中的实现,更简洁,更容易理解接受。只考虑当前节点的关系。
完整实现
Java:
// 遍历接口:Traverse
public interface Traverse<E> {
void traverse( VisitHandler<E> visit);
}
//item处理接口
public interface VisitHandler<E> {
void visit(E item);
}
//二叉树结点
public class Node<E> {
private E item;
private Node left;
private Node right;
public Node(E item, Node left, Node right) {
this.setItem(item);
this.setLeft(left);
this.setRight(right);
}
public E getItem() {
return item;
}
public void setItem(E item) {
this.item = item;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
}
//打印操作
public class PrintVisitHandler<E> implements VisitHandler<E> {
@Override
public void visit(E item) {
System.out.print(item.toString()+" ");
}
}
//前序遍历
public class PreorderTraverse<E> implements Traverse<E> {
private Stack<Node<E>> stack;
public PreorderTraverse(Node<E> node) {
stack = new Stack<>();
stack.push(node);
}
/**
* 如果弹出的元素是一个数据项,就访问它
* 否则对于前序,我们压入右子树,然后左子树,最后是节点。
*/
@Override
public void traverse(VisitHandler<E> handler) {
while (!stack.isEmpty()) {
Node<E> node = stack.pop();
handler.visit(node.getItem());
if (node.getRight() != null)
stack.push(node.getRight());
if (node.getLeft() != null)
stack.push(node.getLeft());
}
}
}
//中序实现
public class MidorderTraverse<E> implements Traverse<E> {
private Stack<Object> stack;
public MidorderTraverse(Node<E> node) {
stack = new Stack<>();
stack.push(node);
}
/**
* 如果弹出的元素是一个数据项,就访问它
* 否则对于中序,我们压入右子树,然后是节点,最后是左子树。
*/
@Override
public void traverse(VisitHandler<E> handler) {
while (!stack.isEmpty()) {
Object obj = stack.pop();
if (obj instanceof Node) {
Node node = (Node) obj;
if (node.getRight() != null)
stack.push(node.getRight());
stack.push(node.getItem());
if (node.getLeft() != null)
stack.push(node.getLeft());
} else {
handler.visit((E)obj);
}
}
}
}
//后序实现
public class PostorderTraverse<E> implements Traverse<E> {
private Stack<Object> stack;
public PostorderTraverse(Node<E> node) {
stack = new Stack<>();
stack.push(node);
}
/**
* 如果弹出的元素是一个数据项,就访问它
* 否则对于后序,我们压入节点,然后是右子树,最后是左子树。
*/
@Override
public void traverse(VisitHandler<E> handler) {
while (!stack.isEmpty()) {
Object obj = stack.pop();
if (obj instanceof Node) {
Node node = (Node) obj;
stack.push(node.getItem());
if (node.getRight() != null)
stack.push(node.getRight());
if (node.getLeft() != null)
stack.push(node.getLeft());
} else {
handler.visit((E)obj);
}
}
}
}
附上于书中相同的测试用例:
public class Main {
public static void main(String[] args) {
Node<Character> tree = makeTree();
new PreorderTraverse<Character>(tree).traverse(new PrintVisitHandler<Character>());
System.out.println();
new MidorderTraverse<Character>(tree).traverse(new PrintVisitHandler<Character>());
System.out.println();
new PostorderTraverse<Character>(tree).traverse(new PrintVisitHandler<Character>());
}
public static Node<Character> makeTree(){
Node<Character> nodeE = new Node('E',null,null);
Node<Character> nodeD = new Node('D',null,null);
Node<Character> nodeH = new Node('H',null,null);
Node<Character> nodeB = new Node('B',null,null);
Node<Character> nodeF = new Node('F',null,null);
Node<Character> nodeA = new Node('A',null,null);
Node<Character> nodeC = new Node('C',null,null);
Node<Character> nodeG = new Node('G',null,null);
nodeE.setLeft(nodeD); nodeE.setRight(nodeH);
nodeD.setLeft(nodeB);
nodeH.setLeft(nodeF);
nodeB.setLeft(nodeA);
nodeB.setRight(nodeC);
nodeF.setRight(nodeG);
return nodeE;
}
}
结果:
E D B A C H F G
A B C D E F G H
A C B D G F H E
写在最后
几年前,还在大厂的时候,遍历目录树,还是自己想了一个递归方法,来匹配层级数据。写完之后,总觉得有点怪异,
直到看过书本,才有种醍醐灌顶的感觉。还是非递归的方式,更能又清晰,又有条理的解决问题。