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; }