二叉树的性质
- 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 (i>0)个结点
- 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0)
- 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
- 具有n个结点的完全二叉树的深度k为 log2^(n+1)上取整
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
若2i+1<n,左孩子序号:2i+1,否则无左孩子
若2i+2<n,右孩子序号:2i+2,否则无右孩子
二叉树的遍历
理解递归序 :
一棵树,如果是以递归的形式去遍历的话,那么每一个节点都会被调用三次.而先序中序后序只是打印操作时机不同而已.
递归与非递归实现打印
所有递归都可以改成非递归实现-用栈
先序
1先将头结点入栈,
2每次从栈中弹出一个节点打印,
3先压入右孩子,再压入左孩子(没有什么也不干) (重复上述步骤)
/*先序遍历的递归和非递归打印*/
public void preOrder (TreeNode root) {
if (root == null)return;
System.out.println(root);
inOrder(root.left);
inOrder(root.right);
}
public void preOrderStack (TreeNode root) {
if (root == null)return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty() ){
root=stack.pop();
System.out.println(root.val);
if (root.right != null)stack.push(root.right);
if (root.left != null) stack.push(root.left);
}
}
后序
俩个栈 (下述流程为:头右左,但是不打印,反而放到另一个栈,所以收集栈就是头右左的逆序—左右头)
1先将头结点入栈,
2弹出的元素进入收集栈
3先压入左孩子,再压入右孩子(没有什么也不干)
等所有过程结束后,单独打印收集栈----后序 (重复上述步骤)
/*后序遍历的递归和非递归打印*/
public void postOrder (TreeNode root) {
if (root == null)return;
inOrder(root.left);
inOrder(root.right);
System.out.println(root);
}
public void postOrderStack (TreeNode root) {
if (root == null)return;
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> cur = new Stack<>();
stack.push(root);
while (!stack.isEmpty() ){
root=stack.pop();
cur.push(root);
if (root.left != null) stack.push(root.left);
if (root.right != null)stack.push(root.right);
}
while (!cur.isEmpty()){
root=cur.pop();
System.out.println(root.val);
}
}
中序
1先将左边节点全入栈,
2依次弹出的过程中打印
3每次弹出的节点(有右树进行就上述步骤)
/*中序遍历的递归和非递归打印*/
public void inOrder(TreeNode root) {
if (root == null)return;
inOrder(root.left);
System.out.println(root);
inOrder(root.right);
}
public void inOrderStack(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() && root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
System.out.println(root.val);
root = root.right;
}
}
}
}
层序遍历(队列)
1:先将头结点放入队列中
2:然后弹出节点,
3:先进左,再进右节点(没有就不放) 重复上述过程-直到队列为空
//层序遍历
public void levelOrder(TreeNode root){
if (root == null )return;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.println(cur.val);
if (cur.left != null)queue.add(cur.left);
if (cur.right != null)queue.add(cur.right);
}
}
求一个二叉树的最大深度
在先序遍历的基础上加上判断深度
public int maxDepth (TreeNode root) {
if (root==null)return 0;
int leftDepth=0;
int rightDepth=0;
leftDepth=maxDepth(root.left);
rightDepth=maxDepth(root.right);
return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
求一个二叉树的最大宽度
在层序遍历的基础上,要知道每一层的节点个数,并找到最大值
1:设置一个hashMap,从来知道当前节点是第几层
2:定义每一层的节点数
3:定义全局的max,用来获取最大的节点数
可以优化-不使用HashMap
简单说一下思路,
定义TreeNode俩个变量,一个获取当前节点值变量,一个全局变量MAx
1:一个变量为当前这一层的最后一个节点,另一个变量为,下一层的最后一个节点
2:当前节点值变量,只要队列出来的借点值不是最后一个节点就+1(下一层的最后一个节点捕获新入队的节点),等是最后一个节点时,该变量+1,并被MAx捕获
3:这一层的最后一个节点=下一层的最后一个节点,下一层的最后一个节点重新捕获节点,当前节点值变量置位0,
重复上述流程
//求一棵树的最大宽度-
public int maxWidth (TreeNode root) {
if (root == null )return -1;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
HashMap<TreeNode,Integer> levelMap= new HashMap<>();
levelMap.put(root,1);
int curLevel=1;//层数
int curLevelNode =0;//当前层的节点数
int max= Integer.MIN_VALUE;
while (!queue.isEmpty()){
TreeNode cur=queue.poll();
int curNodeLevel=levelMap.get(cur);
//获取当前节点的层,是否还在这一层
if (curNodeLevel == curLevel){
curLevelNode++;
}else {
max = Math.max(max,curLevelNode);
curLevel++;
curLevelNode=1;
}
if (cur.left != null){
levelMap.put(cur.left,curNodeLevel+1);
queue.add(cur.left);
}
if (cur.right != null){
levelMap.put(cur.right,curNodeLevel+1);
queue.add(cur.right);
}
}
return max;
}
怎么判断一颗树是否是完全二叉树
解题思路
二叉树按层序遍历,基础上判断
1:任一节点如果只有右孩子,没有左孩子,直接FALSE
2:在1的基础上,如果第一个左右孩子不双全,那么接下来所有的节点都是叶节点
/*判断是否是完全二叉树*/
public boolean isCBT(TreeNode root){
if (root == null)return true;
Queue<TreeNode> queue=new LinkedList<>();
boolean leaf=false;//判断是否遇到俩个左右孩子不双全的节点
TreeNode l =null;
TreeNode r=null;
queue.add(root);
while (!queue.isEmpty()){
root = queue.poll();
l=root.left;
r=root.right;
//判断失败逻辑的俩种情况
if ((leaf && (l != null || r != null)) || (l == null && r != null)){
return false;
}
if (l !=null){
queue.add(l);
}
if (r != null){
queue.add(r);
}
if (l == null || r == null){
leaf = true;
}
}
return true;
}
树形DP的递归套路–很强!-树形面试题目几乎都能写
树形DP:可以通过向左树要信息,可以通过向右树要信息来解决的问题
1找到你需要的所有信息,
2然后直接加工出一个包含所有信息的类,设计一个黑盒,形成子问题递归(树形DP)
判断一颗二叉树是否是搜索二叉树
搜索二叉树,左树节点都比根节点小,右树节点都比二叉树大
中序遍历结果集是有递增的
思路1记录上一次结果的值,然后和当前获取的值进行比较
思路2----递归套路
所需要的信息
左树是不是搜索二叉树
右树是不是搜索二叉树
左树的最大值根小
右树的最小值比根大
注:递归是所有问题都一致,只是规模不同,所以直接返回三个值,是不是搜索二叉树.和这个二叉树的最大值与最小值
思路1
/*判断一棵树是否为搜索二叉树的递归与非递归实现*/
//思路1
private int preValue=Integer.MIN_VALUE;
public boolean isBST(TreeNode root){
if (root == null)return true;
boolean isLeftBst=isBST(root.left);
if (!isLeftBst)return false;
if (root.val <= preValue ){
return false;
}else {
preValue=root.val;
}
return isBST(root.right);
}
public boolean isBSTStack(TreeNode root){
if (root == null) {
return true;
}else {
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
if (root.val <= preValue ){
return false;
}else {
preValue=root.val;
}
root = root.right;
}
}
}
return true;
}
思路2
public class ReturnData{
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean isBST, int min, int max) {
this.isBST = isBST;
this.min = min;
this.max = max;
}
}
public boolean isBST3(TreeNode root){
ReturnData data=processData(root);
return data.isBST&& data.max < root.val && data.min > root.val;
}
public ReturnData processData(TreeNode x){
if ( x == null)return null;
ReturnData leftData=processData(x.left);
ReturnData rightData=processData(x.right);
int min = x.val;
int max = x.val;
if (leftData != null){
min=Math.min(min,leftData.min);
max=Math.max(max,leftData.max);
}
if (rightData != null){
min=Math.min(min,rightData.min);
max=Math.max(max,rightData.max);
}
boolean isBST=true;
/*俩种违规条件-不是平衡二叉树,最大值大于节点值*/
if (leftData != null && (!leftData.isBST || leftData.max >= x.val)){
isBST=false;
}
if (rightData != null && (!rightData.isBST || rightData.min <= x.val)){
isBST=false;
}
return new ReturnData(true,min,max);
}
判断一个二叉树是不是平衡二叉树
平衡二叉树条件
1:左子树平衡
2:右子树平衡
3:| 左高-右高 | <= 1
public class ReturnType{
public boolean isBlanced;
public int height;
public ReturnType(boolean isBlanced, int height) {
this.isBlanced = isBlanced;
this.height = height;
}
}
public boolean isBalance(TreeNode root){
if (root == null)return true;
ReturnType type=processType(root);
return type.isBlanced;
}
public ReturnType processType(TreeNode x){
if (x == null)return new ReturnType(true,0);
ReturnType leftData=processType(x.left);
ReturnType rightdata=processType(x.right);
int height=Math.max(leftData.height,rightdata.height)+1;
boolean isBalanced=(leftData.isBlanced&&rightdata.isBlanced)&&(Math.abs(leftData.height-rightdata.height)<=1);
return new ReturnType(isBalanced,height);
}
判断一颗二叉树是不是满二叉树
满二叉树:条件 节点数 = 2^二叉树高度 -1
public boolean isFull(TreeNode root){
if (root == null) return true;
Info data=processInfo(root);
return data.nodes == (1 << data.height -1);
}
public class Info{
public int height;
public int nodes;
public Info(int h,int n){
height=h;
nodes=n;
}
}
public Info processInfo(TreeNode x){
if (x == null) return new Info(0,0);
Info leftData=processInfo(x.left);
Info rightData=processInfo(x.right);
int height = Math.max(leftData.height,rightData.height)+1;
int nodes = leftData.nodes+rightData.nodes;
return new Info(height,nodes);
}
最近公共祖先-LCA
思路:哈希表,栈,(栈是模拟递归来实现的)
重点就是怎么保存父类的信息
哈希表,生成一个哈希表用来存放每个节点的父节点
然后O1节点只要不是根节点就往上遍历,直到遍历结束-生成一个路径
然后O2节点去这个路径中判断
/*最近公共祖先--哈希表不抽象*/
public TreeNode LCA(TreeNode root ,TreeNode n1,TreeNode n2){
HashMap<TreeNode,TreeNode> fatherMap=new HashMap<>();
fatherMap.put(root,root);
processLCA(root,fatherMap);
HashSet<TreeNode> set=new HashSet<>();
TreeNode cur=n1;
while (cur != fatherMap.get(cur)){
set.add(cur);
cur=fatherMap.get(cur);
}
cur=n2;
while (cur != fatherMap.get(cur)){
if (set.contains(cur)){
return cur;
}
cur=fatherMap.get(cur);
}
return null;
}
private void processLCA(TreeNode root,HashMap<TreeNode,TreeNode> fatherMap){
if (root == null)return;
fatherMap.put(root.left,root);
fatherMap.put(root.right,root);
processLCA(root.left,fatherMap);
processLCA(root.right,fatherMap);
}
/*最近公共祖先,递归方式-抽象
* 要分类:n1 是 n2 的最近公共祖先或 n2 是 n1 的最近公共祖先
* n1 与n2 不为公共组向,要继续往上找*/
public TreeNode LCA2(TreeNode root ,TreeNode n1,TreeNode n2){
if (root == null || root == n1 || root == n2){
return root;
}
TreeNode left=LCA2(root.left,n1,n2);
TreeNode right=LCA2(root.right,n1,n2);
if (left != null && right != null){
//这个说明head是最初的汇聚点
return root;
}
/*左右俩个树,谁不为空返回谁*/
return left!=null?left:right;
}
寻找二叉树的后继节点
后继节点:中序遍历的下一个节点
最简单思路,肯定是中序遍历得到一个序列,然后找到后继节点
当如果有父亲指针的话,你肯定就不能用中序了要优化形成O(路径)
第一种情况: X 有右树的时候–那他的后继节点就是它的右树的最左节点
第二种情况: X 没有右树的时候,X是不是X父亲的左孩子,如果则这个父亲就是后继(这种情况X一定是Y树最后打印的节点)
第三种情况:整个树的最后一个节点是没有后继的,或者为空
/*找这棵树的后继节点---默认这棵树是有父亲节点的*/
public TreeNode getSuccessTreeNode(TreeNode node){
if (node == null)return node;
*//*第一种情况*//*
if (node.right != null){
while (node.left !=null){
node=node.left;
}
return node;
}else {
*//*第二种情况: 无右子树*//*
TreeNode parent=node.parent;
*//*parent != null这个条件就是判断node是最右节点的情况*//*
while (parent != null && parent.left != node){
node=parent;
parent=node.parent;
}
return parent;
}
}
二叉树的序列化和反序列化
序列化很简单
反序列化就有点意思了
序列化,规定以下划线_分割,如果是空则是#
反序列化,形成一个队列,你是怎样序列化的就怎样去递归的去调用
public String serialByPre(TreeNode root){
if (root == null)return "#_";
String res=root.val+"_";
res+=serialByPre(root.left);
res+=serialByPre(root.right);
return res;
}
public TreeNode reconByPreString(String preStr){
String[] values = preStr.split("_");
Queue<String> queue=new LinkedList<>();
for (int i = 0; i != values.length ; i++) {
queue.add(values[i]);
}
return reconPreOrder(queue);
}
public TreeNode reconPreOrder(Queue<String> queue){
String value= queue.poll();
if (value.equals("#")){
return null;
}
TreeNode node=new TreeNode(Integer.valueOf(value));
node.left=reconPreOrder(queue);
node.right=reconPreOrder(queue);
return node;
}
折纸问题
当你把这张纸折三次,然后构建成二叉树,你会发现输出规律真好是中序遍历
/*折纸问题*/
public void printALLFolds(int N){
printProcess(1,N,true);
}
/*递归的过程来到某一个节点
* i是节点的层数,N是一共的层数,flg==true表示是凹 flg==false表示是凸*/
private void printProcess(int i,int N,boolean flg){
if (i > N)return;
printProcess(i+1,N,true);
System.out.println(flg?"凹":"凸");
printProcess(i+1,N,false);
}
根据前序和中序来构建二叉树
解题思路自然是根据:先序来每次找到根节点,然后在递归的调用
public int i = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeChild(preorder,inorder,0,preorder.length-1);
}
public TreeNode buildTreeChild(int[] preorder, int[] inorder,
int inbegin,int inend) {
if(inbegin > inend) {
return null;
}
TreeNode root = new TreeNode(preorder[i]);
//找到当前根,在中序遍历的位置
int rootIndex = findIndex(inorder,inbegin,inend,preorder[i]);
i++;
root.left = buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
root.right = buildTreeChild(preorder,inorder,rootIndex+1,inend);
return root;
}
private int findIndex( int[] inorder,int inbegin,int end, int key) {
for (int j = inbegin; j <= end; j++) {
if (key==inorder[j]){
return j;
}
}
return -1;
}