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的子樹?我們采用遞歸的思想進行判斷。遞歸三要素如下:
- 遞歸結束條件:這里遞歸結束條件要考慮全,首先如果B為null,意味着在遞歸過程中B已經進入了null節點,則應該接受此次遞歸,向其他分支遞歸。如果A==null且B!=null則意味着,A判斷完了,但是B還沒有判斷完全,所以此時應該接受遞歸,B不可能是A的子樹,因為同樣位置下A!=B.
- 結束情況處理:如果是進入B的null節點,則應該接受此次遞歸,向其他分支遞歸。如果是此次遞歸不成功,則應返回結束所有遞歸即可!
- 遞歸邏輯:如果當前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; }