认识二叉树
- 递归实现先序中序后序
class Node<V>{
V value;
Node left;
Node right;
}
根据递归序得出,
先序:第一次到达时就打印,不是第一次什么也不做
中序:第二次来到节点时才打印
后序:第三次来到节点时才打印
在这里插入代码片
- 非递归实现
任何递归都可以改成非递归
@@@先序遍历
利用栈,第一步吧头结点压入栈中,然后玩固定的步骤(
1.每次在栈中弹出一个结点,记为current
2.弹出就打印打印current
3.如果有的话,把孩子先右后左压入栈中
4.周而复始)
这里是后序遍历(需要准备两个栈,一个原始栈,一个收集栈)
1 弹出,把当前节点记为cur
2.把cur放入收集栈
3.先压左在压右
4.周而复始
中序遍历(主要是找左边界)
每颗子树的,整棵树左边界进栈,依次弹出结点的过程中,打印,然后对弹出节点的右树重复(左边界进栈)
自己的理解:先将整棵树的左边界入栈,一个个弹出并打印,并将弹出节点右树的左边界入栈(如果有的话)
为什么可以这样做?
因为左子树可以被分解掉(也可以被右子树分解掉)
如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)
二叉树的先序遍历就是它的深度优先遍历
树的宽度优先遍历,利用队列(Queue q=new Linkedlist<>(),先将头结点入队,弹出并打印该结点,再将该结点的左右孩子入队(如果有的话)),代码在上面
123
方法1(利用哈希表).准备一张表(HashMap),这张表知道他在第几层,(左神讲的有点繁琐,其实是我自己没听懂)
方法2(不利用哈希表).准备4个东西, Node curEnd,当前层的最后一个结点; Node nextEnd,下一层的最后一个结点;int currentLevelNodes,当前层的节点数;int max,与currentLevelNodes相比,每次 到达当前层最后一个结点时,抓取max
二叉树的递归套路
搜索二叉树:对于任意一个结点,它的左子树都比他小,右子树都比他大。
(1)isBST,第一种-用中序遍历比较值的大小(动态检查的不太理解,第二种方法–静态检查好理解,用一个List存储结点,然后拿到List遍历,是升序 ,则是搜索二叉树)
public int preValue=Integer.MIN_VALUE;//遍历过程中上一次出现的
//用中序遍历动态检查是否是搜索二叉树(动态检查)---不理解
public boolean isBST(Node head) {
if(head==null) {
return true;
}
boolean isLeftBST=isBST(head.left);//左树是不是搜索二叉树
if(!isLeftBST) {
return false;
}
//当前节点是否比我上一次处理的结点大
//如果左树是搜索二叉树,head与左树最后一个打印的比较
if(head.value<=preValue) {
return false;
}else {
preValue=head.value;
}
System.out.print(head.value+" ");
return isBST(head.right);//如果右树是搜索二叉树,整棵树就是了
}
//用中序遍历检查是否是搜索二叉树(第二种方法静态检查)
public boolean isBST2(Node head) {
List<Node> inOrderList =new ArrayList<>();
//得到按照中序排列的list
process2(head,inOrderList);
for(int i=0;i<inOrderList.size()-1;i++) {
if(inOrderList.get(i).value>inOrderList.get(i+1).value) {
return false;
}
}
return true;
}
public void process2(Node head,List<Node> inOrderList) {
//这个方法是把一个个结点按照中序遍历的顺序放到List中
if(head==null) {
return ;
}
process2(head.left,inOrderList);
inOrderList.add(head);
process2(head.right,inOrderList);
}
应该算是第三种方法吧—递归套路解决
//判断是否是搜索二叉树的第三种方法,(递归套路)
//本来只需要四个条件,就可以判断一棵树是否是搜索二叉树,1左树是否是搜索二叉树2左树最大值(要小于head)3右树是否是搜索二叉树4右树最小值(要大于head值)
//使用递归,要求左树右树返回值一样,所以要三个变量,是否是搜索二叉树,最大值,最小值
public class ReturnData {
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean is,int mi,int ma) {
isBST=is;
min=mi;
max=ma;
}
}
public ReturnData process2(Node x) {
if(x==null) {
return null;
}
ReturnData leftData=process2(x.left);//左树给我三个信息
ReturnData rightData=process2(x.right);//右树给我三个信息
//我自己也要给出三个信息,递归才能连起来1.boolean isBST;2.int min;3.int max;
int min=x.value;
int max=x.value;
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;//先认为没有违规,返回true
if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)) {
//如果左树 有信息 并且左树已经不是搜索二叉树了违规;或者左树有信息 并且左树最大值大于等于x.value,就违规
isBST=false;
}
if(rightData!=null&&(!rightData.isBST||rightData.min<=x.value)) {
//如果右树 有信息 并且右树已经不是搜索二叉树了违规;或者右树有信息 并且左树最小值小于等于x.value,就违规
isBST=false;
}
/**或者使用三目运算,
* boolean isBST=false;//先认为是false,寻找成立条件
* if(
* (leftData!=null?(leftData.isBST&&leftData.max<x.value):true)
* &&
* (rightData!=null?(rightData.isBST&&rightData.min>x.value):true)
* ){
* isBST=true;
* }
* */
return new ReturnData(isBST,min,max);
}
(2)isCBT,a情况有右无左false,b情况,在a不违规的情况下,第一次遇到了左右孩子不双全,接下来所有必须是叶结点
//当前代码是完全二叉树的判断
public boolean isCBT(Node head) {
//采用宽度优先遍历
if(head==null) {
return false;
}
LinkedList<Node> queue=new LinkedList<>();
boolean leaf=false;//是否遇到过左右孩子不双全的节点,一旦遇到,就一直是true了,因为将诶下来都是叶子结点
Node l=null;
Node r=null;
queue.add(head);
while(!queue.isEmpty()) {
head=queue.poll();
l=head.left;
r=head.right;
if(
(l==null&&r!=null)//第一种,有右无左
||//如果遇到了左右孩子不双全的结点之后,又发现当前结点居然有孩子,也可以这样写(leaf&&!(l==null||r==null))
(leaf&&(l!=null||r!=null))//遇到了左右孩子不双全的结点(这种结点至多只能有一个),该结点它还有孩子,则返回false
) {
return false;
}
if(l!=null) {
//l!=null,r!=null,l==null||r==null相当于宽度优先遍历
queue.add(l);
}
if(r!=null) {
queue.add(r);
}
if(l==null||r==null) {
leaf=true;
}
}
return true;
}
(3)判断满二叉树
求树的最大深度L,树的结点个数N,如果满足2的L次方-1=N,则是true
用递归套路求解
//判断是否是满二叉树,需要的信息,树的高度h和结点个数n,
public class Info{
public int height;
public int nodes;
public Info(int h,int n) {
height=h;
nodes=n;
}
}
public boolean FullBT(Node head) {
if(head==null) {
return true;
}
Info data=process3(head);//收整棵树的两个信息
//判断,n是否等于2的L次方-1
boolean res=(data.nodes==1<<data.height-1)?true:false;
return res;
}
public Info process3(Node x) {
if(x==null) {
return new Info(0,0);
}
Info leftInfo=process3(x.left);//先向左树要信息
Info rightInfo=process3(x.right);//再向右树要
//加工自己的信息,树的高度及结点个数
int height=Math.max(leftInfo.height, rightInfo.height)+1;
int nodes=leftInfo.nodes+leftInfo.nodes;
return new Info(height,nodes);
}
(4)判断是否是平衡二叉树,二叉树的递归套路
//是否是平衡二叉树
public boolean isBalanced(Node head) {
return process(head).isBalanced;
}
public class ReturnType{
public boolean isBalanced;
public int height;
public ReturnType(boolean isB,int hei){
isBalanced=isB;
height=hei;
}
}
public ReturnType process(Node x) {
if(x==null) {
return new ReturnType(true,0);
}
ReturnType leftData=process(x.left);
ReturnType rightData=process(x.right);
int height=Math.max(leftData.height, rightData.height)+1;
boolean isBalanced=leftData.isBalanced&&rightData.isBalanced
&&Math.abs(leftData.height- rightData.height)<2;
return new ReturnType(isBalanced,height);
}
套路可以解决决树形DP(需要向左树要信息 ,也要想右树要信息,来解决头的信息)–准备搜索树形DP题目练习
题目:给定两个二叉树的结点node1和node2,找到他们的最低公共祖先结点
第一种解法:
//题目:给定两个二叉树的结点node1和node2,找到他们的最低公共祖先结点
public Node LowCommonAncestor(Node head,Node o1,Node o2) {
HashMap<Node,Node> fatherMap=new HashMap<>();
fatherMap.put(head, head);//大头结点的父是他自己
process4(head,fatherMap);//这样一来所有结点的父就都找到了
HashSet<Node> setO1=new HashSet<>(); //记录o1往上的链
Node cur=o1;
while(cur!=fatherMap.get(cur)) {
//只有头结点的父节点才是他自己,当跳到head,结束循环
setO1.add(cur);//把o1加入set
cur=fatherMap.get(cur);//o1往上窜
}
setO1.add(head);//最后把head加入set
Node cur2=o2;//让 o2一直网上窜去setO1里寻找o1,找到就返回
if(!setO1.contains(cur2)) {
cur2=fatherMap.get(cur2);
}
return cur2;
}
第二种解法,太难了,不理解
题目四
序列化与反序列化
序列化:内存中的树变为硬盘上的字符串
反序列化:硬盘上的字符串变为内存中的树
序列化代码
//以head为头的树,使用先序遍历,序列化为字符串返回
public String serialByPre(Node head) {
if(head==null) {
return "#_";
}
String res=head.value+"_";
res+=serialByPre(head.left);
res+=serialByPre(head.right);
return res;
}
序列化测试代码
public static void main(String[] args) {
XuLieHua xl=new XuLieHua();
Node node1=xl.new Node(1);
Node node2=xl.new Node(1);
Node node3=xl.new Node(1);
node1.right=node2;
node2.left=node3;
String xuliehuaRES=xl.serialByPre(node1);
System.out.println(xuliehuaRES);
}
序列化测试结果–变成这样子的字符串
反序列化代码
//反序列化-由字符串建树
public Node 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 Node reconPreOrder(Queue<String> queue) {
//不断消费一个队列
String value=queue.poll();
if(value.equals("#")) {
return null;
}
Node head=new Node(Integer.valueOf(value));//先建头
head.left=reconPreOrder(queue);//再建左子树
head.right=reconPreOrder(queue);//最后建右子树
return head;
}
反序列化测试代码
public static void main(String[] args) {
XuLieHua xl=new XuLieHua();
Node head=xl.reconByPreString("1_#_1_1_#_#_#_");
System.out.println(head.value);
}
测试结果:返回头节点的值是1