樹的前序后序中序遍歷


在計算機科學里,樹的遍歷是指通過一種方法按照一定的順序訪問一顆樹的過程。

對於二叉樹,樹的遍歷通常有四種:先序遍歷、中序遍歷、后序遍歷廣度優先遍歷。(前三種亦統稱深度優先遍歷)對於多叉樹,樹的遍歷通常有兩種:深度優先遍歷、廣度優先遍歷。

 

在學習前面三種深度優先遍歷之前,很有必要了解它們之間到底是怎么遍歷的,要自己去親自去遍歷,不要只看文字

 

先序遍歷:  節點 - 左孩子 - 右孩子

中序遍歷: 左孩子  - 根結點 - 右孩子

后序遍歷 : 左孩子 - 右孩子 - 根結點

 

看下面的圖(自己去寫出幾種遍歷的情況):

D8FFU9XE`N9BXA9@NW}Z{K0 

按照上面的三種方法去遍歷,結果是

前序遍歷:- + a * b – c d / e f
中序遍歷:a + b * c – d – e / f
后序遍歷:a b c d – * + e f / -

 

廣度優先遍歷,又叫層次遍歷:從二叉樹的第一層(根結點)開始,自上至下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。

在這幅圖里所的結果是:- + / a * c f b – c d

 

下面給出java實現上面幾種遍歷的算法以及解釋:

三種遞歸的算法不說了,直接說非遞歸算法:

前序非遞歸實現 

節點 - 左孩子 - 右孩子
     1)訪問結點P,並將結點P入棧;
     2)判斷結點P的左孩子是否為空,若為空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置為當前的結點P,循環至1);若不為空,則將P的左孩子置為當前的結點P;
     3)直到P為NULL並且棧為空,則遍歷結束。

有兩種實現方法:

public static void preOrder1_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        while(t != null || !s.empty()){       //只要滿足節點非空或則棧為非空,就要不斷遍歷
            
            while(t !=null ){                 //壓入所有左孩子節點,壓入前輸出
                System.out.print(t.date);
                s.push(t);
                t = t.lchild;
            }
            if(!s.empty()){        //左孩子都搞定了,從棧頂取出一個節點,指向它的右孩子,繼續前面while循環
                t = s.pop();
                t = t.rchild;
            }
        }
        System.out.println();
    }
    public static void preOrder1_2(BinTree t){
        Stack<BinTree> stack = new Stack<BinTree>();
        if(t!=null)
        {
            stack.push(t);
            while(!stack.empty())
            {
                t = stack.pop();
                System.out.print(t.date);
                if(t.rchild!=null)         //先把右孩子壓入棧,利用下壓棧的特性
                    stack.push(t.rchild);
                if(t.lchild!=null)
                    stack.push(t.lchild);                
            }
        }
    }
View Code

 

中序非遞歸算法:

  左孩子  - 根結點 - 右孩子
     *訪問任意一節點p,若其左孩子非空,p入棧,且p的左孩子作為當前節點,然后在對其進行同樣的處理
     *若其左孩子為空,則輸出棧頂點元素並進行出棧操作,訪問該棧頂的節點的右孩子
     *直到p為null並且棧為空則遍歷結束
   (和前序的非遞歸算法1_1有相似之處)

public static void InOrder2_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        while(t != null || !s.empty()){
            while(t !=null){           //和前序的非遞歸有點像
                s.push(t);
                t = t.lchild;           //一路指向左孩子
            }
            if(! s.empty()){
                t = s.pop();              //取棧頂元素並輸出,並指向右孩子
                System.out.print(t.date);
                t = t.rchild;
            }
        }
        System.out.println();
    }
View Code

 

后序非遞歸算法

左孩子 - 右孩子 - 根結點

這個有難度  因為要保證左孩子和右孩子都已經被訪問過了才訪問根節點,這里就要思考一下如何標記一下右孩子是否已經被標記了。有兩種方法:

 

雙棧法(這個比較容易理解)
     * 對於跟節點t,先入棧,然后沿着其左子樹往下收索,直到沒有左子樹的節點,此時該節點入棧
     * 但此時不能進行出棧訪問,要檢查右孩子,所以接下來一相同的規則進行處理,當訪問其右孩子時,
     * 該節點又出現在棧頂,此時就可以出棧訪問,這樣就保證了正確的訪問順序,這個過程中,
     * 每個節點都兩次出現在棧頂,只有在第二次出現在棧頂的時候才能訪問它, 因此有必要設置兩個棧

public static void PostOrder3_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        Stack<Integer> s2 = new Stack<Integer>();   
        Integer i = new Integer(1);        //第二個棧有0 1 倆元素,1表示第二次訪問,0,表示第一次訪問
        while(t!=null || !s.empty()){
            while(t!=null){               //一路壓入左孩子
                s.push(t);                  
                s2.push(new Integer(0));
                t = t.lchild;
            }
            //peek查看堆棧頂部的對象,但不從堆棧中移除它。
            while(!s.empty() && s2.peek().equals(i)){  //有兩次訪問的記錄了,就出棧且輸出啦,倆棧是同步pop的
                s2.pop();
                System.out.print(s.pop().date);
            }
            if(!s.empty()){         //第二次訪問了,棧2的元素置為1,這回要指向右孩子
                s2.pop();
                s2.push(new Integer(1));
                t = s.peek();
                t = t.rchild;
            }
        }
    }
View Code

 

單棧法:

     * 先掃描跟節點的所有左節點並入棧,接着將棧頂元素出棧,
     * 再掃描該節點的右孩子節點並入棧,掃描右孩子的所有左節點並入棧,當一個節點的左右孩子均被訪問后在訪問該節點,
     * 這里用一個初始值為null的節點表示右子樹剛剛被訪問的
     * 一直到棧為空,這里的難點是如何判斷右孩子已經被訪問過了
     這個構造真的是太贊了!!

public static void PostOrder3_2(BinTree t){
        BinTree q = null;
        Stack<BinTree> stack = new Stack<BinTree>();
        while(t!=null)
        {    
            while(t.lchild!=null)            //先左子樹入棧,注意:這里最左節點(葉子)沒有入棧,后面才入棧
            {
                stack.push(t);
                t = t.lchild;
            }
            while(t.rchild==null || t.rchild ==q )
            {            
                System.out.print(t.date);   //當前節點無右孩子或者右孩子已經輸出
                q = t;                      //用來記錄上一個節點
                if(stack.empty())
                    return ;               //棧為空時,就結束程序
                t = stack.pop();           //節點出棧
            }        
            stack.push(t);           //!1、葉子節點入棧,指向的r.child是null 2、處理右孩子
            t = t.rchild;
        }
    }
View Code

 

另外一個單棧實現,單棧的構造都好神奇!

public static void PostOrder3_3(BinTree t){
        BinTree node = t, prev = t;
        Stack<BinTree> stack = new Stack<BinTree>();
        while(node != null || stack.size()>0)
        {
            while(node!=null)               //壓入左孩子
            {
                stack.push(node);
                node = node.lchild;
            }
            if(!stack.empty())
            {
                BinTree temp = stack.peek().rchild;  //記錄當前節點的右孩子
                if((temp == null)||temp == prev)
                {
                    node = stack.pop();           //滿足倆條件就出棧輸出
                    System.out.print(node.date);
                    prev = node;               //標記已經被訪問
                    node = null;               //把這個沒用的節點置為null.,防止執行上面的while循環
                } 
                else{
                    node = temp;
                }
            }        
        }
    }
View Code

 

如果用數組去實現stack的話,可以用這個,這個是在求解兩節點個最近公共祖先時用到的(LCA算法),改寫自考研書上

//棧是用數組實現的 后序非遞歸算法,數組里含有所有的元素
    public static void  PostOrder3_4(BinTree t)
    {
            BinTree st[] = new BinTree[1000];        //用數組實現順序棧    
            int flag,top=-1;                         //棧指針初始化
            BinTree p =t,q;
            do
            {
                while(p!=null)                  //將p的所有左節點入棧
                {
                    top++;
                    st[top] = p;
                    p = p.lchild;
                } 
                q = null;                       //q指向棧頂節點的前一個已經訪問的節點
                flag = 1;                       //設置flag = 1表示處理棧頂節點            
                while(top !=-1 && flag ==1)
                {
                    p = st[top];                //取出棧頂的元素
                    if(p.rchild==q)             //右孩子不存在或者右孩子已經被訪問,訪問之
                    {
                        System.out.print(p.date);
                        top--;
                        q=p;                    //q指向剛剛被訪問的節點
                    }
                    else
                    {    
                        p = p.rchild;           //p指向右孩子節點
                        flag =0;                //flag=0表示棧頂節點處理完畢
                    }                        
                }            
        }while(top!=-1);                       //棧不為空時循環
        System.out.println(); 
    }
View Code

 

下面是全部的代碼外加一個測試實例:

/***************************************
 * 時間:2013年12月2日
 * author:lm
 * 內容:二叉樹前中后遞歸與非遞歸遍歷,以及廣度優先遍歷
 ***************************************/
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;

public class BinTree {
    private char date;
    private BinTree lchild;   //左孩子
    private BinTree rchild;   //右孩子
    
    private BinTree(char c ){
        date = c;
    }
    
    //先序遍歷 遞歸實現  節點 - 左孩子 - 右孩子
    public static void preOrder1(BinTree t){
        if(t != null){
        System.out.print(t.date);
        preOrder1(t.lchild);
        preOrder1(t.rchild);
        }
    }
    
    //中序遍歷 遞歸實現 左孩子  - 根結點 - 右孩子
    public static void InOrder2(BinTree t){
        if(t != null){    
        InOrder2(t.lchild);
        System.out.print(t.date);
        InOrder2(t.rchild);
        }
    }
    
    //后序遍歷  遞歸實現  左孩子 - 右孩子 - 根結點
    public static void PostOrder3(BinTree t){
        if(t!=null)
        {
            PostOrder3(t.lchild);
            PostOrder3(t.rchild);
            System.out.print(t.date);
        }
    }
    
    /*前序非遞歸實現    節點 - 左孩子 - 右孩子
     1)訪問結點P,並將結點P入棧;
     2)判斷結點P的左孩子是否為空,若為空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置為當前的結點P,
              循環至1);若不為空,則將P的左孩子置為當前的結點P;
     3)直到P為NULL並且棧為空,則遍歷結束。
     */
    
    public static void preOrder1_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        while(t != null || !s.empty()){       //只要滿足節點非空或則棧為非空,就要不斷遍歷
            
            while(t !=null ){                 //壓入所有左孩子節點,壓入前輸出
                System.out.print(t.date);
                s.push(t);
                t = t.lchild;
            }
            if(!s.empty()){        //左孩子都搞定了,從棧頂取出一個節點,指向它的右孩子,繼續前面while循環
                t = s.pop();
                t = t.rchild;
            }
        }
        System.out.println();
    }
    public static void preOrder1_2(BinTree t){
        Stack<BinTree> stack = new Stack<BinTree>();
        if(t!=null)
        {
            stack.push(t);
            while(!stack.empty())
            {
                t = stack.pop();
                System.out.print(t.date);
                if(t.rchild!=null)         //先把右孩子壓入棧,利用下壓棧的特性
                    stack.push(t.rchild);
                if(t.lchild!=null)
                    stack.push(t.lchild);                
            }
        }
    }
    
    /*中序非遞歸的實現  左孩子  - 根結點 - 右孩子
     * 訪問任意一節點p,若其左孩子非空,p入棧,且p的左孩子作為當前節點,然后在對其進行同樣的處理
     * 若其左孩子為空,則輸出棧頂點元素並進行出棧操作,訪問該棧頂的節點的右孩子
     * 直到p為null並且棧為空則遍歷結束
     */
    public static void InOrder2_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        while(t != null || !s.empty()){
            while(t !=null){           //和前序的非遞歸有點像
                s.push(t);
                t = t.lchild;           //一路指向左孩子
            }
            if(! s.empty()){
                t = s.pop();              //取棧頂元素並輸出,並指向右孩子
                System.out.print(t.date);
                t = t.rchild;
            }
        }
        System.out.println();
    }

    /*后序非遞歸實現  這個有難度  因為要保證左孩子和右孩子都已經被訪問過了才訪問根節點
     * 對於任一節點p,先入棧,然后沿着其左子樹往下收索,直到沒有左子樹的節點,此時該節點入棧
     * 但此時不能進行出棧訪問,要檢查右孩子,所以接下來一相同的規則進行處理,當訪問其右孩子時,
     * 該節點又出現在棧頂,此時就可以出棧訪問,這樣就保證了正確的訪問順序,這個過程中,
     * 每個節點都兩次出現在棧頂,只有在第二次出現在棧頂的時候才能訪問它, 因此有必要設置兩個棧
     */
    public static void PostOrder3_1(BinTree t){
        Stack<BinTree> s = new Stack<BinTree>();
        Stack<Integer> s2 = new Stack<Integer>();   
        Integer i = new Integer(1);        //第二個棧有0 1 倆元素,1表示第二次訪問,0,表示第一次訪問
        while(t!=null || !s.empty()){
            while(t!=null){               //壓入左孩子
                s.push(t);                  
                s2.push(new Integer(0));
                t = t.lchild;
            }
            //peek查看堆棧頂部的對象,但不從堆棧中移除它。
            while(!s.empty() && s2.peek().equals(i)){  //有兩次訪問的記錄了,就出棧且輸出啦,倆棧是同步pop的
                s2.pop();
                System.out.print(s.pop().date);
            }
            if(!s.empty()){         //第二次訪問了,棧2的元素置為1,這回要指向右孩子
                s2.pop();
                s2.push(new Integer(1));
                t = s.peek();
                t = t.rchild;
            }
        }
    }
    /*
     * 先掃描跟節點的所有左節點並入棧,接着將棧頂元素出棧,
     * 再掃描該節點的右孩子節點並入棧,掃描右孩子的所有左節點並入棧,當一個節點的左右孩子均被訪問后在訪問該節點,
     * 這里用一個初始值為null的節點表示右子樹剛剛被訪問的
     * 一直到棧為空,這里的難點是如何判斷右孩子已經被訪問過了
     * 個人覺得這個構造真的是太贊了!!
     */
    public static void PostOrder3_2(BinTree t){
        BinTree q = null;
        Stack<BinTree> stack = new Stack<BinTree>();
        while(t!=null)
        {    
            while(t.lchild!=null)            //先左子樹入棧,注意:這里最左節點(葉子)沒有入棧,后面才入棧
            {
                stack.push(t);
                t = t.lchild;
            }
            while(t.rchild==null || t.rchild ==q )
            {            
                System.out.print(t.date);   //當前節點無右孩子或者右孩子已經輸出
                q = t;                      //用來記錄上一個節點
                if(stack.empty())
                    return ;               //棧為空時,就結束程序
                t = stack.pop();           //節點出棧
            }        
            stack.push(t);       //!奇葩 1、葉子節點入棧,指向的r.child是null 2、不滿足while條件時,處理右孩子
            t = t.rchild;
        }
    }
    
    public static void PostOrder3_3(BinTree t){
        BinTree node = t, prev = t;
        Stack<BinTree> stack = new Stack<BinTree>();
        while(node != null || stack.size()>0)
        {
            while(node!=null)               //壓入左孩子
            {
                stack.push(node);
                node = node.lchild;
            }
            if(!stack.empty())
            {
                BinTree temp = stack.peek().rchild;  //記錄當前節點的右孩子
                if((temp == null)||temp == prev)
                {
                    node = stack.pop();           //滿足倆條件就出棧輸出
                    System.out.print(node.date);
                    prev = node;               //標記已經被訪問
                    node = null;               //把這個沒用的節點置為null.,防止執行上面的while循環
                } 
                else{
                    node = temp;
                }
            }        
        }
    }
    //棧是用數組實現的后序非遞歸算法,數組里含有所有的元素
    public static void  PostOrder3_4(BinTree t)
    {
            BinTree st[] = new BinTree[1000];        //用數組實現順序棧    
            int flag,top=-1;                         //棧指針初始化
            BinTree p =t,q;
            do
            {
                while(p!=null)                  //將p的所有左節點入棧
                {
                    top++;
                    st[top] = p;
                    p = p.lchild;
                } 
                q = null;                       //q指向棧頂節點的前一個已經訪問的節點
                flag = 1;                       //設置flag = 1表示處理棧頂節點            
                while(top !=-1 && flag ==1)
                {
                    p = st[top];                //取出棧頂的元素
                    if(p.rchild==q)             //右孩子不存在或者右孩子已經被訪問,訪問之
                    {
                        System.out.print(p.date);
                        top--;
                        q=p;                    //q指向剛剛被訪問的節點
                    }
                    else
                    {    
                        p = p.rchild;           //p指向右孩子節點
                        flag =0;                //flag=0表示棧頂節點處理完畢
                    }                        
                }            
        }while(top!=-1);                       //棧不為空時循環
        System.out.println(); 
    }        
    
    /*廣度優先遍歷樹,又叫層次遍歷
     * 1.首先將根節點放入隊列中。
       2.當隊列為非空時,循環執行步驟3到步驟5,否則執行6;
       3.出隊列取得一個結點,訪問該結點;
       4.若該結點的左子樹為非空,則將該結點的左子樹入隊列;
       5.若該結點的右子樹為非空,則將該結點的右子樹入隊列;
       6.結束。
     */
    /*
     * 補充:隊列是什么,提到隊列,就要想到先進先進先出,即為先進先出隊列,先進來的人先服務,我們日常生活
     * 排隊時不是先到的人先接受服務么?
     */
    public static void BFSOrder(BinTree t)
    {
        if(t==null) return ;
        Queue<BinTree> queue = new ArrayDeque<BinTree>();    
        //隊列小知識:使用offer和poll優於add和remove之處在於它們返回值可以判斷成功與否,而不拋出異常
        queue.offer(t);              //進入隊列
        while(!queue.isEmpty())
        {
            t=queue.poll();           //當前節點出隊列
            System.out.print(t.date);
            if(t.lchild!=null)              //當前節點左孩子去排隊,在前面哦
                queue.offer(t.lchild);
            if(t.rchild!=null)            //右孩子排第二
                queue.offer(t.rchild);    
        }
    }
    public static void main(String[] args) {
         BinTree b1 = new BinTree('a');
         BinTree b2 = new BinTree('b');
         BinTree b3 = new BinTree('c');
         BinTree b4 = new BinTree('d');
         BinTree b5 = new BinTree('e');
         BinTree b6 = new BinTree('f');
         BinTree b7 = new BinTree('g');
    
        /**
         *      a 
         *    /   \
         *   b     c
         *  / \   / \
         * d   e f   g
         */
        b1.lchild = b2;
        b1.rchild = b3;
        b2.lchild = b4;
        b2.rchild = b5;
        b3.lchild = b6;
        b3.rchild = b7;
    
        BinTree.preOrder1(b1);
        System.out.println();
        
        BinTree.preOrder1_1(b1);
        System.out.println();
        
        BinTree.InOrder2(b1);
        System.out.println();
        BinTree.InOrder2_1(b1);
        System.out.println();
        
        BinTree.PostOrder3(b1);
        System.out.println();

        BinTree.PostOrder3_1(b1);    
        System.out.println();
        BinTree.PostOrder3_2(b1);
        System.out.println();
        BinTree.PostOrder3_3(b1);
        System.out.println();
        BinTree.PostOrder3_4(b1);
        System.out.println();
        
        BinTree.BFSOrder(b1);
        System.out.println();    
        }
}
View Code

 

網上有好的很好的文章都有寫道這幾個搜索算法,這個博客是用c++實現的

http://www.blogjava.net/fancydeepin/archive/2013/02/03/cpp_binarytreesearch.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM