二叉樹基本操作java實現及遍歷淺析

前言
這段時間複習數據結構,從二叉樹開始看,到後面是二叉排序樹,平衡樹,紅黑樹等,看完樹還要看圖,然後是排序和查找算法。今天把實現了的二叉樹的代碼總結一下,理理思路。 
數據結構中,二叉樹的遍歷分爲三種方式:前序,中序,後序。所謂前中後,我覺得就是根節點的遍歷順序,如果根節點第一個訪問就是前序,第二個訪問就是中序,最後一個訪問就是後序。而遍歷的手段有兩種–遞歸和非遞歸。 
我寫的是java代碼,在讀懂別人的基礎上自己實現。

思路
遞歸遍歷
如果不死命的去想清楚每一次的調用的話(我曾經想破腦袋),遞歸理解起來非常簡單,利用分治的思想,設置好遞歸結束的判斷條件,這裏是節點==null,在訪問根節點的孩子節點的時候,又會訪問到孩子節點的孩子節點,如果孩子節點的孩子節點還有孩子節點,就一直從上到下去訪問,直到碰到了結束條件,即節點爲空。然後就可以往回返結果了,一層層退回去,返回最初的入口。然後繼續執行下面的語句。可以使用遞歸的原因是每個節點都可能是根節點,而且這些根節點的基本結構都是一樣的。代碼如下:

//    前序遞歸遍歷
    public void preOrder(TreeNode subTree){
        if(subTree==null)
            return;
        else{
            visit(subTree);
            preOrder(subTree.leftChild);
            preOrder(subTree.rightChild);
        }
    }
//    中序遞歸遍歷
    public void inOrder(TreeNode subTree){
        if(subTree==null)
            return;
        else{
            inOrder(subTree.leftChild);
            visit(subTree);
            inOrder(subTree.rightChild);
        }
    }
//    後序遞歸遍歷
    public void postOrder(TreeNode subTree){
        if(subTree==null)
            return;
        else{
            postOrder(subTree.leftChild);
            postOrder(subTree.rightChild);
            visit(subTree);
        }
    } 
非遞歸遍歷
因爲遞歸遍歷是非常消耗內存而且耗時的操作,遞歸分爲遞,歸兩個步驟。遞的時候要保存入口,方便歸。所以最好還是使用非遞歸算法。非遞歸算法是用棧實現的,因爲棧的特點是先進後出,這樣從最上面的根節點開始壓棧,然後是它的孩子節點壓棧,一直到最下面。彈棧時候就可以先訪問最下面的節點,即棧頂節點,一直彈棧直到最初放入的那個節點。這樣就實現了和遞歸一樣的功能,就是把路過的需要保存的節點都保存起來,然後往回走。這個也與樹的結構有關係,因爲樹的結構就是每一個節點都可能是下面的一個分支的根節點,所以遍歷的時候也是從最下面的開始,遍歷完一個分支就完成了一個孩子節點的遍歷。訪問完三個這樣的分支一個大的分支也就訪問完了。

非遞歸前序遍歷
前序遍歷就是根,左,右的順序。先從最頂端的根節點開始沿着左支壓棧,每次壓棧都訪問,這樣一路下來就遍歷了根,左,只有右節點都還沒有被訪問,所以就開始彈棧,得到棧中根節點的右孩子,然後重複這個循環,因爲每個根節點都要經歷這個循環。代碼如下:

//非遞歸前序遍歷,用棧實現
    public void nonRecPreOrder(TreeNode subTree){
        Stack<TreeNode> stack=new Stack<>();
        TreeNode p=subTree;
        while(p!=null||stack.size()>0){
//            先把左節點全部入棧,這些左節點將作爲根節點
            while(p!=null){
                visit(p);
                stack.push(p);
                p=p.leftChild;
            }
//            沒有左節點了,就彈棧,將彈出的節點的右孩子節點壓棧
            if(stack.size()>0){
                p=stack.pop();
                p=p.rightChild;
            }
        }
    } 
非遞歸中序遍歷
左,根,右的順序,和前序的區別就是訪問是在彈棧的時候,因爲彈的時候是從最左開始。代碼如下:

//    非遞歸中序遍歷
    public void nonRecInOrder(TreeNode subTree){
        Stack<TreeNode> stack=new Stack<>();
        TreeNode p=subTree;
        while(p!=null || stack.size()>0){
            while(p!=null){
                stack.push(p);
                p=p.leftChild;
            }
            if(stack.size()>0){
                p=stack.pop();
                visit(p);
                p=p.rightChild;
            }
        }
    } 
非遞歸後序遍歷
這個就稍微複雜一些,因爲訪問順序是左,右,根,這樣左和根節點是不緊挨着訪問的。實現的思路仍然是要先把節點從左邊一直壓棧,然後從最左開始訪問,如果遇到了右孩子沒有被訪問的節點就把這個右節點當作一個新的根節點去壓棧,然後從最左開始訪問。這裏面就是多了一個節點的右孩子節點是否爲空的判斷。代碼如下:

//    非遞歸後序遍歷
    public void nonRecPostOrder(TreeNode subTree){
        Stack<TreeNode> stack=new Stack<>();
        TreeNode p=subTree;
        TreeNode node=null;
        while(p!=null){//處理一個根節點
//            把這個根節點的所有左孩子都壓入棧中,從上到下
//            這裏最後一個節點沒有壓棧
            for(;p.leftChild!=null;p=p.leftChild)
                stack.push(p);
//            開始依次處理棧中的根節點
//            當前節點不爲空且沒有右孩子或者右孩子已經訪問過,根據後序遍歷的定義,可以直接訪問該根節點了
            while(p!=null&&p.rightChild==null||p.rightChild==node){
                visit(p);
//                記錄一下,因爲如果彈棧之後遇到了右孩子不爲空的根節點,就會先把這個根節點再次壓回棧中,然後把它的右孩子節點也壓入棧中
//                這樣,棧中,右孩子節點就在根節點的上面緊挨着根節點,訪問的時候,也是訪問完右孩子就直接訪問它
//                所以,判斷某個右孩子不爲空的根節點孩子是否被訪問過,就看node==rightChild與否
                node=p;
                if(stack.size()==0)
                    return;
//                棧不爲空,繼續彈出處理根節點
                p=stack.pop();
            }
//           這裏的p是右孩子沒有被訪問過的根節點,把這個已經在while循環中彈出的根節點先壓回棧中
            stack.push(p);
//            然後得到它的右孩子,繼續新的一支的壓棧,彈棧,訪問處理
//            處理完它的右孩子自然就會處理棧中剛剛壓回的處於下一個位置的根節點
            p=p.rightChild;
        }
    } 
運行結果:

 

代碼:
 

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