基於遞歸的BFS(Level-order)


上篇中學習了二叉樹的DFS深度優先搜索算法,這次學習另外一種二叉樹的搜索算法:BFS,下面看一下它的概念:

有些抽象是不?下面看下整個的遍歷過程的動畫演示就曉得是咋回事啦:

了解其概念之后,下面看下如何實現它?在正式實現逐層遍歷之前,需要解決一個問題,那就是:得知道該樹有多少層,也就是樹的深度如何計算,下面來解決這個問題:

還是基於上篇的搜索二叉樹的代碼進行實現:

public class BinarySearchTree {
    TreeNode root = null;

    class TreeNode{
      int value;
      int position;
      TreeNode left = null, right = null;
      TreeNode(int value, int position){ 
        this.value = value; 
        this.position = position; 
      }
    }

    public void add(int value, int position){
      if(root == null){//生成一個根結點
        root = new TreeNode(value, position);
      } else {
        //生成葉子結點
        add(value, position, root);
      }
    }

    private void add(int value, int position, TreeNode node){
      if(node == null)
        throw new RuntimeException("treenode cannot be null");
      if(node.value == value)
        return; //ignore the duplicated value
      if(value < node.value){
        if(node.left == null){
          node.left = new TreeNode(value, position);
        }else{
          add(value, position, node.left);
        }
      }else{
        if(node.right == null){
          node.right = new TreeNode(value, position);
        }else{
          add(value, position, node.right);
        }
      }
    }


    //打印構建的二叉搜索樹
    static void printTreeNode(TreeNode node) {
      if(node == null)
        return;
      System.out.println("node:" + node.value);
      if(node.left != null) {
        printTreeNode(node.left);
      }
      if(node.right != null) {
        printTreeNode(node.right);
      }
    }

    //搜索結點
    public int search(int value){
      return search(value, root);
    }

    private int search(int value, TreeNode node){
      if(node == null)
        return -1; //not found
      else if(value < node.value){
        System.out.println("Searching left");
        return search(value, node.left);
      }
      else if(value > node.value){
        System.out.println("Searching right");
        return search(value, node.right);
      }
      else  
        return node.position;
    }

    //二叉樹DFS遍歷
    public void travel(){
      travel(root);
    }
    public void travel(TreeNode node){
      if(node == null)
        return;
      travel(node.left);
      travel(node.right);
      System.out.println(" " + node.value);
    }

    //二叉樹的深度數
    public int depth(){
      return depth(root);
    }

    private int depth(TreeNode node){
      if(node == null)
        return 0;
      int leftDepth = depth(node.left);
      int rightDepth = depth(node.right);
      return Math.max(leftDepth, rightDepth) + 1;
    }

    public static void main(String[] args) {
      BinarySearchTree bst = new BinarySearchTree();
      int a[] = { 5, 8, 3, 4, 1, 7, 6};
      for(int i = 0; i < a.length; i++){
        bst.add(a[i], i);    
      }

      System.out.println("Tree Depth:" + bst.depth());
    }

}

其上面搜索二叉樹再貼一下,以便可以直觀的可以查看:

編譯運行:

下面來debug看一下程序看如何計算出樹的深度的:

a、將root = TreeNode(5, 0)傳給帶參數的depth方法進行遞歸遍歷。

Loop1:其參數node = TreeNode(5, 0)

  b、,條件不滿足,執行步驟c;

  c、,int leftDepth = depth(node.left = TreeNode(3, 2)),繼續遞歸左結點:

    cb、,條件不滿足,執行步驟cc;

    cc、,int leftDepth = depth(node.left = TreeNode(1, 4)),繼續遞歸左結點:

      ccb、,條件不滿足,執行步驟ccc;

      ccc、,int leftDepth = depth(node.left = null),繼續遞歸左結點:

        cccb、,條件滿足退出返回leftDepth = 0,執行步驟ccd;
      ccd、,int rightDepth = depth(node.right = null),繼續遞歸右結點:

        cccb、,條件滿足退出返回rightDepth = 0,執行步驟cce;

      cce、,result = 1; 

    所以這時leftDepth = 1;  

    cd、,int rightDepth = depth(node.right = TreeNode(4, 3)),繼續遞歸右結點:

      cdb、,條件不滿足,執行步驟cdc;

      cdc、,int leftDepth = depth(node.left = null),繼續遞歸左結點:

        cdcb、,條件滿足退出返回leftDepth = 0,執行步驟cdd;
      cdd、,int rightDepth = depth(node.right = null),繼續遞歸右結點:

        cddb、,條件滿足退出返回rightDepth = 0,執行步驟cde;

      cde、,result = 1; 
    所以這時leftDepth = 1;

    ce、,result = max(1, 1) + 1 = 2;

  所以這時leftDepth = 2;

  d、,int rightDepth = depth(node.right = TreeNode(8, 1)),繼續遞歸右結點:

    db、,條件不滿足,執行步驟dc;

    dc、,int leftDepth = depth(node.left = TreeNode(7, 5)),繼續遞歸左結點:

      dcb、,條件不滿足,執行步驟dcc;

      dcc、,int leftDepth = depth(node.left = TreeNode(6, 6)),繼續遞歸左結點:

        dccb、,條件不滿足,執行步驟dccc;

        dccc、,int leftDepth = depth(node.left = null),繼續遞歸左結點:
          dcccb、,條件滿足退出返回leftDepth = 0,執行步驟dccd;

        dccd、,int rightDepth = depth(node.right = null),繼續遞歸右結點:

          dccdb、,條件滿足退出返回rightDepth = 0,執行步驟dcce; 

        dcce、,result = max(0, 0) + 1 = 1;

      所以這時leftDepth = 1;  

      dcd、,int rightDepth = depth(node.right = null),繼續遞歸右結點:

        dcdb、,條件滿足退出返回rightDepth = 0,執行步驟dce;

      dce、,result = max(1, 0) + 1 = 2; 

    所以這時leftDepth = 2;

    dd、,int rightDepth = depth(node.right = null),繼續遞歸右結點:

      ddb、,條件滿足退出返回rightDepth = 0,執行步驟de;

    所以這時rightDepth = 0;

    de、,result = max(2, 0) + 1 = 3;

  所以這時rightDepth = 3;

  e、,result = max(2,3) + 1 = 4,所以最終此樹的深度為4!

總結其實現思路:

1、遞歸的邊界結束條件是傳過來的節點為空了。

2、遞歸左結點的深度

3、遞歸右結點的深度

4、總結點的深度為左結點的深度+右結點的深度+1

上面已經實現了樹的深度的計算,接下來則是利用DFS來將二叉樹進行遍歷啦,先上代碼:

public class BinarySearchTree {
    TreeNode root = null;

    class TreeNode{
      int value;
      int position;
      TreeNode left = null, right = null;
      TreeNode(int value, int position){ 
        this.value = value; 
        this.position = position; 
      }
    }

    public void add(int value, int position){
      if(root == null){//生成一個根結點
        root = new TreeNode(value, position);
      } else {
        //生成葉子結點
        add(value, position, root);
      }
    }

    private void add(int value, int position, TreeNode node){
      if(node == null)
        throw new RuntimeException("treenode cannot be null");
      if(node.value == value)
        return; //ignore the duplicated value
      if(value < node.value){
        if(node.left == null){
          node.left = new TreeNode(value, position);
        }else{
          add(value, position, node.left);
        }
      }else{
        if(node.right == null){
          node.right = new TreeNode(value, position);
        }else{
          add(value, position, node.right);
        }
      }
    }


    //打印構建的二叉搜索樹
    static void printTreeNode(TreeNode node) {
      if(node == null)
        return;
      System.out.println("node:" + node.value);
      if(node.left != null) {
        printTreeNode(node.left);
      }
      if(node.right != null) {
        printTreeNode(node.right);
      }
    }

    //搜索結點
    public int search(int value){
      return search(value, root);
    }

    private int search(int value, TreeNode node){
      if(node == null)
        return -1; //not found
      else if(value < node.value){
        System.out.println("Searching left");
        return search(value, node.left);
      }
      else if(value > node.value){
        System.out.println("Searching right");
        return search(value, node.right);
      }
      else  
        return node.position;
    }

    //二叉樹DFS遍歷
    public void travel(){
      travel(root);
    }
    public void travel(TreeNode node){
      if(node == null)
        return;
      travel(node.left);
      travel(node.right);
      System.out.println(" " + node.value);
    }

    //二叉樹的深度數
    public int depth(){
      return depth(root);
    }

    private int depth(TreeNode node){
      if(node == null)
        return 0;
      int leftDepth = depth(node.left);
      int rightDepth = depth(node.right);
      return Math.max(leftDepth, rightDepth) + 1;
    }

    //二叉樹的BFS遍歷
    public void levelOrder(){
      int depth = depth();
      for(int level = 0; level < depth; level ++){
        printLevel(root, level);
        System.out.println("\n-------------------");
      }
    }
    private void printLevel(TreeNode node, int level){
      if(node == null)
        return;
      if(level == 0){
        System.out.print(" " + node.value);
      }else{
        printLevel(node.left, level - 1);
        printLevel(node.right, level - 1);
      }
    }

    public static void main(String[] args) {
      BinarySearchTree bst = new BinarySearchTree();
      int a[] = { 5, 8, 3, 4, 1, 7, 6};
      for(int i = 0; i < a.length; i++){
        bst.add(a[i], i);    
      }

      System.out.println("Tree Depth:" + bst.depth());
      bst.levelOrder();
    }

}

編譯運行:

下面再來debug一下其利用遞歸來BFS遍歷的整個過程:

a、depth = 4

b、根據樹的層次依次進行遍歷打印,具體如下:

  Loop1:level = 0,level < 4條件為真,進入循環體:

    ①、遞歸打印第一層的所有結點:printLevel(root = TreeNode(5, 0), 0):

      c、判斷node是否為null,條件為假,繼續執行d;

      d、條件為真,直接打印"5"【level=0表示當前就是要打印的結點,因為每遞歸一次層會遞減一,等到指定層也就減為0了】

    ②、打印一個分隔行以便結果可以看起來比較直觀。"-------------------"

    level = level + 1 = 1;

  Loop2:level = 1,level < 4條件為真,進入循環體:

    ①、遞歸打印第一層的所有結點:printLevel(root = TreeNode(5, 0), 1):

      c、判斷node是否為null,條件為假,繼續執行d;

      d、條件為假,繼續執行e;

      e、分別遞歸左右結點:

        ①、printLevel(node.left = TreeNode(3, 2), 0);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為真,直接打印"3";

        ②、printLevel(node.right = TreeNode(8, 1), 0);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為真,直接打印"8";

    ②、打印一個分隔行以便結果可以看起來比較直觀。"-------------------"

    level = level + 1 = 2;

  Loop3:level = 2,level < 4條件為真,進入循環體:

    ①、遞歸打印第一層的所有結點:printLevel(root = TreeNode(5, 0), 2):

      c、判斷node是否為null,條件為假,繼續執行d;

      d、條件為假,繼續執行e;

      e、分別遞歸左右結點:

        ①、printLevel(node.left = TreeNode(3, 2), 1);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為假,繼續執行e;

          e、分別遞歸左右結點:
            ①、printLevel(node.left = TreeNode(1, 4), 0);   

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為真,直接打印"1";

            ②、printLevel(node.right = TreeNode(4, 3), 0);

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為真,直接打印"4";

        ②、printLevel(node.right = TreeNode(8, 1), 1);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為假,繼續執行e;

          e、分別遞歸左右結點:
            ①、printLevel(node.left = TreeNode(7, 5), 0);   

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為真,直接打印"7";

            ②、printLevel(node.right = null);

              c、判斷node是否為null,條件為真,直接返回遞歸結束。

    ②、打印一個分隔行以便結果可以看起來比較直觀。"-------------------"

    level = level + 1 = 3;

  Loop4:level = 3,level < 4條件為真,進入循環體:

    ①、遞歸打印第一層的所有結點:printLevel(root = TreeNode(5, 0), 3):

      c、判斷node是否為null,條件為假,繼續執行d;

      d、條件為假,繼續執行e;

      e、分別遞歸左右結點:

        ①、printLevel(node.left = TreeNode(3, 2), 2);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為假,繼續執行e;

          e、分別遞歸左右結點:
            ①、printLevel(node.left = TreeNode(1, 4), 1);   

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為假,繼續執行e;

              e、分別遞歸左右結點:

                ①、printLevel(node.left = null, 0);

                  c、判斷node是否為null,條件為真,直接返回遞歸結束。

                ②、printLevel(node.right = null, 0);    

                  c、判斷node是否為null,條件為真,直接返回遞歸結束。            

            ②、printLevel(node.right = TreeNode(4, 3), 1);

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為假,繼續執行e;

              e、分別遞歸左右結點:

                ①、printLevel(node.left = null, 0);

                  c、判斷node是否為null,條件為真,直接返回遞歸結束。

                ②、printLevel(node.right = null, 0);    

                  c、判斷node是否為null,條件為真,直接返回遞歸結束。  

        ②、printLevel(node.right = TreeNode(8, 1), 2);

          c、判斷node是否為null,條件為假,繼續執行d;

          d、條件為假,繼續執行e;

          e、分別遞歸左右結點:
            ①、printLevel(node.left = TreeNode(7, 5), 1);   

              c、判斷node是否為null,條件為假,繼續執行d;

              d、條件為假,繼續執行e;

              e、分別遞歸左右結點:

                ①、printLevel(node.left = TreeNode(6, 6), 0);   

                  c、判斷node是否為null,條件為假,繼續執行d;

                  d、條件為真,直接打印"6";

                ②、printLevel(node.right = null);

                  c、判斷node是否為null,條件為真,直接返回遞歸結束。

            ②、printLevel(node.right = null);

              c、判斷node是否為null,條件為真,直接返回遞歸結束。

    ②、打印一個分隔行以便結果可以看起來比較直觀。"-------------------"

    level = level + 1 = 4;

  Loop5:level = 4,level < 4條件為假,結束循環。

總結其實現思路:

1、首先獲得樹的層數,然后進行逐層打印。

2、每層打印時,都是從根節點開始來遍歷的【很顯示這種做法不是很高效,這節先學一種,未來會有更高效的做法】

3、在遞歸函數中有三個條件:

  a、如果當前節點是null,則直接返回遞歸結束。

  b、如果當前的層數為0,那證明就是要打印的層,則直接打印當前節點。

  c、以上兩個條件都不滿足,則說明該結點還有子結點,於是乎分別再次遞歸它的左結點和右結點,並且將層數減一。

 

下面來分析一下它的時間復雜度:

實際上整個算法是比較低效的,而上面的時間復雜度是O(n^2)級別的,未來會有更高效的O(n)線性級別的算法待學習,加油! 


免責聲明!

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



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