樹的前序後序中序遍歷

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

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

 

在學習前面三種深度優先遍歷以前,頗有必要了解它們之間究竟是怎麼遍歷的,要本身去親自去遍歷,不要只看文字node

 

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

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

後序遍歷 : 左孩子 - 右孩子 - 根結點數組

 

看下面的圖(本身去寫出幾種遍歷的狀況):ide

D8FFU9XE`N9BXA9@NW}Z{K0 

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

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

 

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

在這幅圖裏所的結果是:- + / 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);           //!一、葉子節點入棧,指向的r.child是null 二、處理右孩子
            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);       //!奇葩 一、葉子節點入棧,指向的r.child是null 二、不知足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

相關文章
相關標籤/搜索