寫在前面
正巧在看《算法: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
寫在最後
幾年前,還在大廠的時候,遍歷目錄樹,還是自己想了一個遞歸方法,來匹配層級數據。寫完之後,總覺得有點怪異,
直到看過書本,纔有種醍醐灌頂的感覺。還是非遞歸的方式,更能又清晰,又有條理的解決問題。