完全二叉樹的創建,遍歷與查找


1:如何創建完全二叉樹?

1.1完全二叉樹的基本特性

  • 1:n0和n2之間的關系:n0即為子節點為0的節點個數,n1即為子節點為1的節點個數,n2即為子節點為2的節點個數.很顯然,總節點個數n = n0 + n1 + n2 等式1,然后我們找第二個等式,我們發現,樹中所有連接線的個數為n-1,這是因為除了root的節點外,所以節點有且僅有一個父節點過來的連接線,而在n0和n1和n2角度來說,所以的連接線個數為n1+ 2*n2,這就是定義n1和n2的基本特性。所以有n-1= n1 + n2*2結合等式1,得到;n1 + 2*n2 = n0 + n1 + n2 -1;得到n2 = n0 -1;也就是所有,有兩個子節點的節點個數等於葉子節點個數 -1.
  • 2:節點在數值位置之間的關系:首先假設節點個數為n,則我們分析知道,若構成完全二叉樹,則只有前一般的節點能從當父節點,后一半的節點都是葉子節點。所有假設0到n的節點中,如果第i個節點,處於前一半即擁有子節點,則其左子節點為2*i+1,右子節點為2*i+2,當然這些子節點沒有超過總節點的長度。

1.2創建完全二叉樹

    //List和ArrayList的區別?用來存放所有Node的List
    public static List<Node> list=new ArrayList<Node>();    
    //由數組建樹的方法
    public void creatTree(int[] array){
        //將數組改裝成Node,放入list中,以方便后面創建完全二叉樹
        for(int i=0;i<array.length;i++){
            Node btnode =new Node(array[i],null,null);
            list.add(btnode);
        }
        //首先在list的長度 大於1的情況下進行
        //完全二叉樹,假設所有Node個數為n,則前n/2個Node都是父節點,后一半都是葉子節點,沒有子節點
        //所以在創建二叉樹中,只需要遍歷0到n/2-1即,前一半數據,為他們在其子節點位置添加對應的lchild和rchild即可!
        //同時我們直到完全二叉樹的假設該節點在數組的位置為i,且i<n/2,則其lchild節點為2i+1.rchild節點為2i+2.
        //當然,2i+1和2i+2都應該小於n才行!
        if(list.size()>0){
            //for循環比較巧妙,注意for循環的次數,i代表的是每一個帶有孩子的結點,0代表的是根節點
            //長度為n的數組,0到n/2-1(包括n/2-1位置)為前一半數據
            for(int i=0;i<array.length/2;i++){
                if(2*i+1<list.size())//這里不能等於,原因是list的長度必須大於2*i+1不然會數組越界
                    list.get(i).setLchild(list.get(2 * i + 1));
                if(2*i+2<list.size())// 這里也不能等於,原因是數組會越界
                    list.get(i).setRchild(list.get(2 * i + 2));
            }
        }
    }

利用前面分析的特性2,可以通過針對對前一半的節點進行循環,依次判斷其左子節點和右子節點是否依然存在,若存咋則連接上,否則null的方式來創建一個二叉樹。

2:如何操作二叉樹?

遍歷二叉樹;是一個基本的操作針對樹這種結構來說,數組,鏈表,隊列,stack等基本的線性結構的遍歷很容易實現,但是在二叉樹中,這是一個遞歸的結構,每一個節點又可以充當root進行遍歷左右子節點,所以我們遍歷二叉樹中也采用遞歸遍歷的方式。常規采用的方式有三種,前序遍歷,中序遍歷和后序遍歷不同遍歷的方區別在於,訪問本節點的順序,實在遞歸左子節點之前,之后,還是最后,下面用圖來表示。

2.1前序遍歷

 前序遍歷;在向子節點遍歷之前就已經遍歷過本節點了,在本程序遍歷本節點就相當於打印出來即可;在程序中,我們也可以看到,在向下遞歸之前就已經遍歷本節點了。

    //前序遍歷
    /**
     * 前序遍歷的操作;先遍歷根節點,在遍歷左節點,再右節點,
     * 不斷循環遍歷即可,可采用遞歸的思想來遍歷
     * @param root 待遍歷二叉樹的根節點
     */
    public void preOrder(Node root){
        //退出遞歸條件,若當前節點為null,
        //則return;
        if(root == null){
            return ;
        }
        //否則則前遍歷本節點,即直接打印
        else{
            System.out.println(root.val);
        }
        preOrder(root.lchild);//先遍歷左子樹
        preOrder(root.rchild);//再遍歷右子樹
    }

2.2中序遍歷

 

 

    //中序遍歷
    public void inOrder(Node root){
        //退出遞歸條件,若當前節點為null,
        //則return;
        if(root == null){
            return ;
        }
        inOrder(root.lchild);//先遍歷左子樹
        System.out.println(root.val);//再輸出本節點
        inOrder(root.rchild);//再遍歷右子樹

    }

我們發小中序遍歷和前序遍歷在程序上,只是調節了

        inOrder(root.lchild);//先遍歷左子樹
        System.out.println(root.val);//再輸出本節點
        inOrder(root.rchild);//再遍歷右子樹

 順序,先遍歷左子樹,然后再本節點,或者先右子樹,然后再本節點。

2.3后序遍歷

 

2.4按層遍歷

按層遍歷二叉樹需要借助大量的集合類,用來緩存每一層的所有兄弟類,因為兄弟類之間沒有指針指向,所以必須一次性都存入集合中,然后再依次進行遍歷,當然再遍歷后也要將該點的下一層節點也存入集合依次循環。

    //按層遍歷二叉樹
    public List<Integer> levelOrderBottom(TreeNode root) {
        List<Integer> result = new LinkedList<Integer>();
        if(root == null)
            return result;
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            int levelSize = queue.size();//記錄每層的元素個數,
            for(int i = 0; i < levelSize; i++) {//按層循環,循環次數為每層的元素個數
                TreeNode node = queue.poll();//訪問完后從隊列刪除,保證queue.size()等於每層的元素個數
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
                result.add(node.val);//第一個參數為index,從鏈表頭插入
            }
        }
        return result;
    }

這里的方法是通過記錄當前層的節點數來實現按層遍歷的,我們可以使用queue的先進先出的特性來解決這個問題,將最近的節點放在后面,通過直接獲取的方法可以保證當該層所有節點都獲取完成了,才能開始獲取下一層的節點,就不用統計每一層節點的個數了。

class Solution {
    public int[] levelOrder(TreeNode root) {
        TreeNode temp = null;
        Queue<TreeNode> queue = new LinkedList<>();
        ArrayList<Integer> ans = new ArrayList<>();
        if(root == null){
            return new int[0];
        }
        queue.add(root);
        while(true){
            //如果當前queue為空,則直接退出即可
            if(queue.isEmpty()){
                break;
            }
            //再queue中取出一個treeNode
            temp = queue.poll();
            //加入ans中,
            ans.add(temp.val);
            //然后判斷如果該節點有左右子節點,則依次加入queue。
            if(temp.left!=null){
                queue.add(temp.left);
            }
            if(temp.right!=null){
                queue.add(temp.right);
            }
        }

        int[] res = new int[ans.size()];
        int index = 0;
        //把LinkedList轉化成array,以供輸出來用。
        for(Integer i:ans){
            res[index++] = i;
        }

        return res;
    }
}

2.5查找節點

//前序遍歷查找節點,在樹A中,查找節點B
public TreeNode serachNode(TreeNode A, TreeNode B){
TreeNode temp;
if(A == null)//如果A為null,這及return,
return null;
if(A.val == B.val){
return A;
}
else {
temp = serachNode(A.left, B);
if(temp ==null){
temp = serachNode(A.right, B);
}
return temp;
}
}

 

2.5判斷二叉樹

判斷以節點A和節點B為根的兩顆二叉樹中,B是不是A的子樹?我們采用遞歸的思想進行判斷。遞歸三要素如下:

  1. 遞歸結束條件:這里遞歸結束條件要考慮全,首先如果B為null,意味着在遞歸過程中B已經進入了null節點,則應該接受此次遞歸,向其他分支遞歸。如果A==null且B!=null則意味着,A判斷完了,但是B還沒有判斷完全,所以此時應該接受遞歸,B不可能是A的子樹,因為同樣位置下A!=B.
  2. 結束情況處理:如果是進入B的null節點,則應該接受此次遞歸,向其他分支遞歸。如果是此次遞歸不成功,則應返回結束所有遞歸即可!
  3. 遞歸邏輯:如果當前node.val相等,則
    //判斷是否是子樹,
    //采用遞歸的方式來判斷是不是子樹
    //遞歸結束條件:B為null意味着此B的子節點為null,應該返回true,以進行其他子節點的判斷。
    //遞歸結束條件:A為null且B不為null,則意味着找遍了A也沒有實現其匹配,return false
    //遞歸邏輯:如果此時A.val == B.val則遞歸下去,直到上面兩個情況發生!
    public boolean issub(TreeNode A, TreeNode B){
        if(B == null){
            return true;
        }
        if(A == null && B != null){
            return false;
        }
        if(A.val == B.val){
            //只有當A和B的左右節點都相等才能 return true。
            return issub(A.left,B.left) && issub(A.right,B.right);
        }
        return false;
    }

 


免責聲明!

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



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