二叉樹前序、中序、後序遍歷Java和C非遞歸實現

寫在前面

      正巧在看《算法: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 

寫在最後

          幾年前,還在大廠的時候,遍歷目錄樹,還是自己想了一個遞歸方法,來匹配層級數據。寫完之後,總覺得有點怪異,

直到看過書本,纔有種醍醐灌頂的感覺。還是非遞歸的方式,更能又清晰,又有條理的解決問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章