二叉查找樹(三)


  我們知道二叉查找樹是一種數據結構,它支持多種動態集合的操作,包括:查詢,最大值,最小值,前驅,后繼,插入和刪除等操作。那么我們在前一篇已經創建了二叉查找樹,那么我們來實現二叉查找樹的各種操作吧。(*^__^*) (以下純屬個人理解,個人原創,理解不當的地方,請指正,謝謝)

  我們來看二叉樹的遍歷操作,所謂遍歷,顧名思義,就是能夠依次的訪問二叉查找樹中的各個結點。在數據結構課堂上,我們知道,遍歷方式有深度優先和廣度優先遍歷,深度優先又包括前序、中序和后序遍歷;廣度優先遍歷,在二叉樹的范疇中,就是層序遍歷,就是先遍歷某一層的結點,然后再遍歷下一層的結點。好了,一個個的來介紹吧。

  • 前序遍歷

  前序遍歷,就是先遍歷父結點,然后遍歷左子樹,最后遍歷右子樹的遍歷方法,所謂前序,指的是遍歷父結點發生在遍歷左右子樹之前。

  前序遍歷的遞歸算法很簡單,代碼如下:

 1 /**
 2      * 遞歸前序遍歷以x為根的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void preOrderTreeWalk(TreeNode x){
 7         if(x != null){
 8             System.out.print(x);//訪問形式為打印輸出一下
 9             preOrderTreeWalk(x.getLeft());
10             preOrderTreeWalk(x.getRight());
11         }
12     }

  這個算法,沒什么難點可說,就是先訪問當前結點,然后遞歸訪問當前結點的左子樹,最后遞歸訪問當前結點的右子樹。因為要遍歷所有節點,所以該算法的時間復雜度為O(n);

  遞歸往往意味着低效,那么,如果不用遞歸的算法,如果實現前序遍歷呢?這里有兩個方法,都是借助“棧”來實現的。

  方法1是模擬遞歸算法的實現效果,具體代碼如下:

 1 /**
 2      * 非遞歸前序遍歷以x為根結點的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void preOrderTreeWalkNonrecursive1(TreeNode x){
 7         //借助棧來實現。
 8         Stack<TreeNode> stack = new Stack<TreeNode>();
 9         while(x != null || !stack.empty()){
10             if(x != null){
11                 System.out.print(x);//遍歷輸出
12                 stack.push(x);//壓棧
13                 x = x.getLeft();
14             }else{
15                 x = stack.pop();//出棧
16                 x = x.getRight();
17             }
18         }
19     }

  方法1每次都將遇到的節點壓入棧,當左子樹遍歷完畢后才從棧中彈出最后一個訪問的節點,訪問其右子樹。在同一層中,不可能同時有兩個節點壓入棧,因此棧的大小空間為O(h),h為二叉樹高度。時間方面,每個節點都被壓入棧一次,彈出棧一次,訪問一次,復雜度為O(n)。

  方法2是直接模擬遞歸來實現的,代碼如下:

/**
     * 非遞歸前序遍歷以x為根結點的二叉樹
     * @author Alfred
     * @param x 根結點
     */
    public void preOrderTreeWalkNonrecursive2(TreeNode x){
        Stack<TreeNode> stack = new Stack<TreeNode>();
        if(x != null){
            stack.push(x);
            while(!stack.empty()){
                TreeNode tmpNode = stack.pop();
                System.out.print(tmpNode);//遍歷輸出
                if(tmpNode.getRight() != null){
                    stack.push(tmpNode.getRight());
                }
                if(tmpNode.getLeft() != null){
                    stack.push(tmpNode.getLeft());
                }
            }
        }
    }

  方法2每次將節點壓入棧,然后彈出,壓右子樹,再壓入左子樹,在遍歷過程中,遍歷序列的右節點依次被存入棧,左節點逐次被訪問。同一時刻,棧中元素為m-1個右節點和1個最左節點,最高為h,所以空間也為O(h);每個節點同樣被壓棧一次,彈棧一次,訪問一次,時間復雜度O(n)。

  • 中序遍歷

  中序遍歷,就是先遍歷左子樹,然后遍歷父結點,最后遍歷右子樹,所謂中序,是指遍歷父結點在遍歷左子樹和遍歷右子樹之間。同時,根據二叉查找樹的性質,中序遍歷的輸出結果,恰好就是排序之后的序列。

  中序遍歷的遞歸算法也很簡單,具體代碼如下:

 1 /**
 2      * 遞歸中序遍歷以x為根的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void inOrderTreeWalk(TreeNode x){
 7         if(x != null){
 8             inOrderTreeWalk(x.getLeft());
 9             System.out.print(x);
10             inOrderTreeWalk(x.getRight());
11         }
12     }

  先遞歸遍歷左子樹,然后訪問父結點,最后遞歸遍歷右子樹,上面的算法很簡單,不多說。

  同理,非遞歸該如何實現二叉查找樹的中序遍歷呢?同樣,也需要借助“棧”來實現。代碼如下:

 1 /**
 2      * 非遞歸中序遍歷以x為根結點的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void inOrderTreeWalkNonrecursive(TreeNode x){
 7         Stack<TreeNode> stack = new Stack<TreeNode>();
 8         while(x != null || !stack.empty()){
 9             if(x != null){
10                 stack.push(x);
11                 x = x.getLeft();
12             }else{
13                 x = stack.pop();
14                 System.out.print(x);//遍歷輸出
15                 x = x.getRight();
16             }
17         }
18     }

  該算法與前序遍歷的非遞歸算法1非常的相近,時間和空間復雜度也相同。至於對應的第二種非遞歸中序遍歷方法,沒有想到,牛逼的童鞋們自己來補充吧~

  • 后續遍歷

  后序遍歷,就是先遍歷左子樹,然后遍歷右子樹,最后遍歷父結點,所謂后序,是指遍歷父結點在遍歷左右子樹之后。

  同樣,后序遍歷的遞歸算法也很簡單,代碼如下:

 1 /**
 2      * 遞歸后序遍歷以x為根的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void postOrderTreeWalk(TreeNode x){
 7         if(x != null){
 8             postOrderTreeWalk(x.getLeft());
 9             postOrderTreeWalk(x.getRight());
10             System.out.print(x);
11         }
12     }

  先遞歸遍歷左子樹,然后遞歸遍歷右子樹,最后父結點。

  那么,后序遍歷的遞歸算法該如何寫呢?這里同樣有兩個方法,方法1用單棧實現,方法2用雙棧實現。

  方法1代碼如下:

 1 /**
 2      * 非遞歸后序遍歷以x為根結點的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void postOrderTreeWalkNonrecursive1(TreeNode x){
 7         Stack<TreeNode> stack = new Stack<TreeNode>();
 8         TreeNode prev = null;
 9         TreeNode curr = null;
10         if(x != null){
11             stack.push(x);
12         }
13         while(!stack.empty()){
14             curr = stack.peek();
15             if(prev == null || prev.getLeft() == curr || prev.getRight() == curr){
16                 if(curr.getLeft() != null){
17                     stack.push(curr.getLeft());//壓左孩子
18                 }else if(curr.getRight() != null){
19                     stack.push(curr.getRight());//壓右孩子
20                 }
21             }else if(curr.getLeft() == prev){
22                 if(curr.getRight() != null){
23                     stack.push(curr.getRight());//壓右孩子
24                 }
25             }else{
26                 System.out.print(curr);//遍歷輸出
27                 stack.pop();
28             }
29             prev = curr;
30         }
31     }

  方法1是根據訪問前后元素的關系來進行的算法,根據關系的不同,發生的行為也就不同。

  方法2的具體代碼為:

 1 /**
 2      * 非遞歸后序遍歷以x為根結點的二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void postOrderTreeWalkNonrecursive2(TreeNode x){
 7         Stack<TreeNode> stack = new Stack<TreeNode>();
 8         Stack<TreeNode> output = new Stack<TreeNode>();
 9         TreeNode curr = null;
10         if(x != null){
11             stack.push(x);
12         }
13         while(!stack.empty()){
14             curr = stack.pop();
15             output.push(curr);//存放到輸出地棧里面
16             if(curr.getLeft() != null){
17                 stack.push(curr.getLeft());//壓左孩子
18             }
19             if(curr.getRight() != null){
20                 stack.push(curr.getRight());//壓右孩子
21             }
22         }
23         while(!output.empty()){
24             TreeNode tmpNode = output.pop();
25             System.out.print(tmpNode);//打印輸出
26         }
27     }

  方法2利用一個棧出棧並壓入到另一個棧的手法,對二叉查找樹進行了后序遍歷。

  • 層序遍歷

  層序遍歷,就是按照二叉樹的層次結構,逐層進行遍歷的方法。層序遍歷需要借助“隊列”來實現,具體的代碼如下:

 1 /**
 2      * 層序遍歷二叉樹
 3      * @author Alfred
 4      * @param x 根結點
 5      */
 6     public void levelOrderTreeWalk(TreeNode x){
 7         Queue<TreeNode> queue = new LinkedList<TreeNode>();
 8         TreeNode tmpNode = null;
 9         if(x != null){
10             queue.offer(x);
11         }
12         while(!queue.isEmpty()){
13             tmpNode = queue.poll();
14             System.out.print(tmpNode);//打印輸出
15             if(tmpNode.getLeft() != null){
16                 queue.offer(tmpNode.getLeft());//左孩子入隊
17             }
18             if(tmpNode.getRight() != null){
19                 queue.offer(tmpNode.getRight());//右孩子入隊
20             }
21         }
22     }

  上面的算法,先將根結點入隊,然后出隊,遍歷輸出,然后將左孩子和右孩子分別入隊,依次循環下去,知道隊列為空為止。

  好了,遍歷就先寫到這兒吧。

  ps:有寫的不好的,請各位童鞋指正。


免責聲明!

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



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