左神算法笔记03:二叉树

二叉树节点结构:

class Node{
	V value;
	Node left;
	Node right;
}

用递归和非递归两种方式实现二叉树的前序、中序、后序遍历

如何直观的打印一颗二叉树

如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)

用递归和非递归两种方式实现二叉树的前序、中序、后序遍历

递归方式

太简单了,无需赘述

非递归方式

前序遍历:

  1. 从栈中弹出一个节点cur
  2. 处理cur
  3. 先把cur的右孩子节点压入栈,再将cur的左孩子节点压入栈
  4. 周而复始

中序遍历:
定义一个栈

  1. 当前节点的左子树(左边界)(不止一个节点)压栈
  2. 依次弹出节点的过程中打印
  3. 有右子树的就压栈
  4. 周而复始
    原理:整棵树可以被左边界分解
    在这里插入图片描述

在这里插入图片描述

后序遍历:
准备两个栈,一个是辅助栈,一个是收集栈,先把根节点压辅助栈

  1. 弹出一个节点cur
  2. 将cur压进收集栈
  3. 将cur节点的左右节点压栈,先压左节点
  4. 周而复始
import java.util.Stack;

public class PreInPosTraversal {
    public static class Node{
        private int value;
        private Node left;
        private Node right;
        public Node(int value){
            this.value = value;
        }
    }
    //递归方法
    //前序遍历
    public static void preOrderRecur(Node head){
        if (head == null){
            return;
        }
        System.out.print(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }
    //中序遍历
    public static void inOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        inOrderRecur(head.left);
        System.out.print(head.value + " ");
        inOrderRecur(head.right);
    }

    //后序遍历
    public static void posOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        posOrderRecur(head.left);
        posOrderRecur(head.right);
        System.out.print(head.value + " ");
    }

    //非递归方法
    //前序遍历
    public static void preOrderUnRecur(Node head){
        System.out.println("preOrder:");
        if (head != null){
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.isEmpty()){
                head = stack.pop();
                System.out.print(head.value + " ");
                if (head.right != null){
                    stack.push(head.right);
                }
                if (head.left != null){
                    stack.push(head.left);
                }
            }
        }
        System.out.println();
    }
    //中序遍历
    public static void inOrderUnRecur(Node head){
        System.out.println("inOrder:");
        if (head != null){
            Stack<Node> stack = new Stack<>();
            while(!stack.isEmpty() || head != null){
                if(head != null){
                    stack.push(head);
                    head = head.left;
                }else {
                    head = stack.pop();
                    System.out.print(head.value + " ");
                    head = head.right;
                }
            }
        }
        System.out.println();
    }
    //后序遍历
    public static void posOrderUnRecur(Node head){
        System.out.println("proOrder:");
        if(head != null){
            Stack<Node> stack1 = new Stack<>();
            Stack<Node> stack2 = new Stack<>();
            stack1.push(head);
            while (!stack1.isEmpty()){
                head = stack1.pop();
                stack2.push(head);
                if (head.left != null){
                    stack1.push(head.left);
                }
                if (head.right != null){
                    stack1.push(head.right);
                }
            }
            while (!stack2.isEmpty()){
                System.out.print(stack2.pop().value + " ");
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Node head = new Node(5);
        head.left = new Node(3);
        head.right = new Node(8);
        head.left.left = new Node(2);
        head.left.right = new Node(4);
        head.left.left.left = new Node(1);
        head.right.left = new Node(7);
        head.right.left.left = new Node(6);
        head.right.right = new Node(10);
        head.right.right.left = new Node(9);
        head.right.right.right = new Node(11);

        // recursive
        System.out.println("==============recursive==============");
        System.out.print("pre-order: ");
        preOrderRecur(head);
        System.out.println();
        System.out.print("in-order: ");
        inOrderRecur(head);
        System.out.println();
        System.out.print("pos-order: ");
        posOrderRecur(head);
        System.out.println();

        // unrecursive
        System.out.println("============unrecursive=============");
        preOrderUnRecur(head);
        inOrderUnRecur(head);
        posOrderUnRecur(head);
    }
}

如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)

宽度优先遍历

public static void w(Node head){
	if(head == null){
		return;
	}
	Queue<Node> queue = new LinkedList<>();
	queue.add(head);
	while(!queue.isEmpty){
		Node cur = queue.poll();
		System.out.println(cur.value);
		if(cur.left != null){
			queue.add(cur.left);
		}
		if(cur.right!= null){
			queue.add(cur.right);
		}
	}
}

求一颗二叉树的宽度

public static int w(Node head){
	if(head == null){
		return;
	}
	Queue<Node> queue = new LinkedList<>();
	queue.add(head);
	HashMap<Node,Integer> levelMap = new HashMap<>();
	levelMap.add(head,1);
	int curLevel = 1;
	int curLevelNodes = 0;
	int max = Integer.MIN_VALUE;
	while(!queue.isEmpty){
		Node cur = queue.poll();
		int curNodeLevel = levelMap.get(cur);
		if(curNodeLevle == curLevel){
			curLevelNodes++;
		}else{
			max = Math.max(max,curLevelNodes);
			curLevel++;
			curLevelNodes = 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);
		}
	}
	max = Math.max(max,curLevelNodes);
	return max;
}

判断一棵二叉树是否是搜索二叉树

搜索二叉树:左节点的值肯定比右节点的值小(左中右递增)
中序遍历的结果一定是升序

	//中序遍历改
	public static int preValue = Integer.MIN_VALUE;
    public static boolean checkBST(Node head){
        if (head == null)
            return true;
        boolean isLeftBst = checkBST(head.left);
        if (!isLeftBst){
            return false;
        }
        if (head.value <= preValue){
            return false;
        }else {
            preValue = head.value;
        }
        return checkBST(head.right);
    }
//非递归中序遍历改
	public static int preValue = Integer.MIN_VALUE;
    public static boolean unRecursecheckBST(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null){
                if (head != null){
                    stack.push(head);
                    head = head.left;
                }else {
                    head = stack.pop();
                    if (head.value <= preValue){
                        return false;
                    }else {
                        preValue = head.value;
                    }
                    head = head.right;
                }
            }
        }
        return true;
    }

判断一棵二叉树是否是完全二叉树

思路:
使用宽度优先算法,并满足一下条件:

  1. 任意节点,只有右孩子节点,没有左孩子节点,肯定不是完全二叉树,返回false
  2. 在1.成立的情况下,如果遇到了第一个左右孩子节点不全的节点,则后续所有节点都是叶子结点
//判断是否为完全二叉树
    public static boolean isCBT(Node head){
        if (head == null) {
            return true;
        }
        Queue<Node> queue = new LinkedList<>();
        //是否遇到过左右孩子不双全的节点
        boolean leaf = false;
        Node l = null;
        Node r = null;
        queue.offer(head);
        while (!queue.isEmpty()){
            head = queue.poll();
            l = head.left;
            r = head.right;
            if (
            	//遇到过俩孩子不双全的情况,且当前节点有孩子节点
            	(leaf && (l != null || r != null)) 
            	|| 
            	//有右无左
            	(l == null && r != null)){
                	return false;
            }
            if (l != null){
                queue.offer(l);
            }
            if (r != null){
                queue.offer(r);
            }
            if(l == null || r ==null) {
                leaf = true;
            }
        }
        return true;
    }

判断一棵二叉树是否是满二叉树

一种做法:统计最大深度L,再统计节点个数N,满足N = 2的L次方-1,则是满二叉树,否则不是

判断一棵二叉树是否是平衡二叉树

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

具体代码看下面递归套路

二叉树的递归套路(解决树形DP问题)

平衡二叉树的判断

public static class ReturnType{
	public boolean isBalanced;
	public int height;
	public ReturnType(boolean isB,int hei){
		isBalanced = isB;
		height = hei;
	}
}
public static boolean isBalanced(Node head){
	return process(head).isBalanced;
}
public static 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);
}

在这里插入图片描述
分析题目,需要子树提供什么信息,就建立对应的返回类型

搜索二叉树的判断

在这里插入图片描述

//套路解
    static class ReturnBSTData{
        boolean isBST;
        int min;
        int max;

        public ReturnBSTData(boolean isCBT, int min, int max) {
            this.isBST = isBST;
            this.min = min;
            this.max = max;
        }
    }

    static boolean BST(Node head) {
        if (head==null)
            return true;
        return process(head).isBST;
    }
    static ReturnBSTData process(Node x) {
        if (x == null)
            return null;
        ReturnBSTData left = process(x.left);
        ReturnBSTData right = process(x.right);
        
        int min = x.value;
        int max = x.value;
        //从全局考虑,min是左子树和右子树和当前节点的最小值
        //因为左右子树可能有空值
        //max是左子树和右子树和当前节点的最大值
        if (left != null){
            min = Math.min(min,left.min);
            max = Math.max(max,left.max);
        }
        if (right != null){
            min = Math.min(min,right.min);
            max = Math.max(max,right.max);
        }
        
        boolean isBST = true;
        //左子树不为空,则会有返回值,
        //这时候就得看看左子树是否是搜索二叉树
        //或者左子树的最大值和当前节点比较一下
        if (left != null && (!left.isBST || x.value <= left.max))
            isBST = false;
        //右子树不为空,则会有返回值,
        //这时候就得看看右子树是否是搜索二叉树
        //或者右子树的最小值和当前节点比较一下
        if (right != null && (!right.isBST || x.value >= right.min))
            isBST = false;
        return new ReturnBSTData(isBST,min,max);
    }

此套路适合用来解决一切树型DP问题

满二叉树的判断

public static class Info{
	private int height;
	private int nodes;
	public Info(int height, int nodes) {
        this.height = height;
        this.nodes= nodes;
    }
}

private static boolean isFull(Node head){
	if (null == head){
		return true;
	}
	Info res = process(head);
	//这边需要注意,一定要加括号,位运算符<<优先级比-低
	return res.nodes == (1 << res.height)-1;
	
}
private static Info process(Node x){
	if (x == null){
		return new Info(0, 0);
	}
	Info leftData = process(x.left);
	Info rightData = process(x.right);
	
	int height = Math.max(leftData.height,rightData.height) + 1;
	int nodes = leftData.nodes + rightData.nodes + 1;
	return new Info(height, nodes);
}

给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

D和F的最低公共祖先就是B
E和F的最低公共祖先就是E
在这里插入图片描述

//o1,o2一定属于head为头的树
//返回o1,o2的最低公共祖先
public static Node lca(Node head,Node o1,Node o2){
	HashMap<Node,Node> fatherMap = new HashMap<>();
	fatherMap.put(head,head);
	process(head,fatherMap);
	
	HashSet<Node> set1 = new HashSet<>();
	set1.add(o1);
	
	Node cur = o1;
	//set存了o1往上的所有父节点
	while(cur != fatherMap.get(cur) ){
		set1.add(cur);
		cur = fatherMap.get(cur);
	}
	set1.add(head);
	
	cur = o2;
	//遍历o2的父节点,第一个在set里,则是最低公共祖先
	while( !set1.contains(cur) ){
		cur = fatherMap.get(cur);
	}
	return cur;
}
//递归的目的是找到每个节点的父节点,除了head节点
public static void process(Node head,
			HashMap<Node,Node> fatherMap){
	if(head == null){
		return;
	}
	fatherMap.put(head.left,head);
	fatherMap.put(head.right,head);
	process(head.left, fatherMap);
	process(head.right, fatherMap);
}


//另一种做法
public static Node lowestAncestor(Node head,Node o1,Node o2){
	if(head == null || head == o1 || head == o2){
		return head;
	}
	Node left = lowestAncestor(head.left,o1,o2);
	Node right= lowestAncestor(head.right,o1,o2);
	if(left != null && right != null){
		return head;
	}
	//左右两棵树,并不都有返回值
	return left != null ? left : right;
}

解法二解析:
在这里插入图片描述

在二叉树中找到一个节点的后继节点

现在有一种新的二叉树节点类型如下

public class Node{
	public int value;
	public Node left;
	public Node right;
	public Node parent;
	public Node(int val){
		value = val;
	}
}

该结构比普通二叉树节点结构多了一个指向父节点的parent节点

假设有一颗Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头结点的parent指向null

只给一个在二叉树的某个节点node,请实现返回node的后继节点的函数

在二叉树的中序遍历中,node的下一个节点叫做node的后继节点

时间复杂度o(k)
在这里插入图片描述

public static Node getSuccessorNode(Node node){
	if(node == null){
		return node;
	}
	if(node.right != null{
		//返回右树的最左节点
		return getLeftMost(node.right);
	}else{ //无右树
		Node parent = node.parent;
		//当前节点是其父节点的右孩子
		while(parent != null && parent.left != null){
			node = parent;
			parent = node.parent;
		}
		return parent;
	}
}
//找到根为当前节点的树的最左节点
public static Node getLeftMost(Node node){
	if(node == null){
		return node;
	}
	while(node.left != null){
		node = node.left;
	}
	return node;
}

二叉树的序列化和反序列化

就是内存里的一颗树如何变成字符串形式,又如何从字符串变成内存里的树
如何判断一颗二叉树是不是另一颗二叉树的子树?

使用前序方式序列化和反序列化

在这里插入图片描述

//以head开头的树,请序列化成字符串返回
public static String serialByPre(Node head){
	if(head == null){
		return "#_";
	}
	String res = head.value + "_";
	res += serialByPre(head.left);
	res += serialByPre(head.right);
	return res;
}
public static 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 static Node reconPreOrder(Queue<String> queue){
	String value = queue.poll();
	if(value.equal("#"){
		return null;
	}
	Node head = new Node(Integer.valueOf(value));
	head.left = reconPreOrder(queue);
	head.right = reconPreOrder(queue);
	return head;
}

折纸问题(微软原题)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
所以实际就是二叉树的中序遍历
这是一颗头结点是凹折痕,左子树的头结点为凹折痕,右子树的头结点为凸折痕的二叉树

public static void printAllFolds(int N){
	printProcess(1,N,true);
}
//递归过程,来到某一节点
//i的节点的尾数,N为一共的层数,down == true 凹 down == false 凸
public static void printProcess(int i,int N,boolean down){
	if(i > N){
		return;
	}
	printProcess(i + 1,N,true);
	System.out.println(down ? "凹" : "凸");
	printProcess(i + 1,N,false);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值