【5】二叉树

目录

一.二叉树节点结构

二.用递归方式实现二叉树的先序、中序、后序遍历

三.用非递归方式实现二叉树的先序、中序、后序遍历

四.如何直观打印一颗二叉树

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

1.宽度优先搜索(层次遍历)

2.求一棵二叉树的最大宽度 

六.二叉树的相关概念及其实现判断

1.如何判断一棵二叉树是搜索二叉树

2.如何判断一棵二叉树是完全二叉树

3.如何判断一棵二叉树是否是满二叉树

4.如何判断一颗二叉树是平衡二叉树

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

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

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

十.折横问题


一.二叉树节点结构

public static class Node{
    int value;
    Node left;
    Node right;
}

二.用递归方式实现二叉树的先序、中序、后序遍历

	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.print("pre-order: ");
		if (head != null) {
			Stack<Node> stack = new Stack<Node>();
			stack.add(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();
	}

先序遍历:

  1. 从栈中弹出一个节点cur
  2. 打印(处理)cur
  3. 先右再左(如果有) 
  4. 重复1-3直到栈空
	public static void inOrderUnRecur(Node head) {
		System.out.print("in-order: ");
		if (head != null) {
			Stack<Node> stack = new Stack<Node>();
			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();
	}

中序遍历:

  1. 整棵树左边界进栈
  2. 当没有左子树或者右子树,依次弹栈打印
  3. 对弹出节点重复操作

为什么可以实现中序?

因为左边界依次进栈的顺序是:头左 => 弹出顺序是:左头 

每次都是先左再头,然后再在右树上进行”先左再头“,一直循环往复。

这里其实没有右的概念,只是右树是后面进行“先左再头”操作的,所以总体看来是左头右

	public static void posOrderUnRecur1(Node head) {
		System.out.print("pos-order: ");
		if (head != null) {
			Stack<Node> s1 = new Stack<Node>();
			Stack<Node> s2 = new Stack<Node>();
			s1.push(head);
			while (!s1.isEmpty()) {
				head = s1.pop();
				s2.push(head);
				if (head.left != null) {
					s1.push(head.left);
				}
				if (head.right != null) {
					s1.push(head.right);
				}
			}
			while (!s2.isEmpty()) {
				System.out.print(s2.pop().value + " ");
			}
		}
		System.out.println();
	}

	public static void posOrderUnRecur2(Node h) {
		System.out.print("pos-order: ");
		if (h != null) {
			Stack<Node> stack = new Stack<Node>();
			stack.push(h);
			Node c = null;
			while (!stack.isEmpty()) {
				c = stack.peek();
				if (c.left != null && h != c.left && h != c.right) {
					stack.push(c.left);
				} else if (c.right != null && h != c.right) {
					stack.push(c.right);
				} else {
					System.out.print(stack.pop().value + " ");
					h = c;
				}
			}
		}
		System.out.println();
	}

后序遍历:

  1. 弹出栈,cur
  2. cur放入收集栈stack2
  3. 先左再右压入stack1

为什么可以实现后序?

因为stack1的弹出顺序为:头右左 => 放入收集栈stack2然后一起弹出 => 左右头

直接实现左右头比较难,考虑实现头右左,然后通过另外一个收集栈收集起来反向打印。

四.如何直观打印一颗二叉树

package class05;

public class Code02_PrintBinaryTree {

	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static void printTree(Node head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	public static void printInOrder(Node head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.right, height + 1, "v", len);
		String val = to + head.value + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.left, height + 1, "^", len);
	}

	public static String getSpace(int num) {
		String space = " ";
		StringBuffer buf = new StringBuffer("");
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(-222222222);
		head.right = new Node(3);
		head.left.left = new Node(Integer.MIN_VALUE);
		head.right.left = new Node(55555555);
		head.right.right = new Node(66);
		head.left.left.right = new Node(777);
		printTree(head);

		head = new Node(1);
		head.left = new Node(2);
		head.right = new Node(3);
		head.left.left = new Node(4);
		head.right.left = new Node(5);
		head.right.right = new Node(6);
		head.left.left.right = new Node(7);
		printTree(head);

		head = new Node(1);
		head.left = new Node(1);
		head.right = new Node(1);
		head.left.left = new Node(1);
		head.right.left = new Node(1);
		head.right.right = new Node(1);
		head.left.left.right = new Node(1);
		printTree(head);

	}

}

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

1.宽度优先搜索(层次遍历)

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

2.求一棵二叉树的最大宽度 

如下图,这个棵树的第三层节点最多,所以这棵树最大宽度是3。

这题其实就是求每层的节点个数,因此除了简单层序遍历外,还应该可以清晰辨别每个节点所在的层数。

=> 这里可以借助一个HashMap来记录每个节点对应的层数 => HashMap<Node, Integer>

	public static int getMaxWidth(Node head) {
		if(head==null)return 0;
		Queue<Node> queue=new LinkedList<>();
		queue.add(head);
		HashMap<Node, Integer> hashmap=new HashMap<>();
		hashmap.put(head, 1);
		int max=0;
		int curLevel=0;
		int curLevelNode=0;
		while(!queue.isEmpty()) {
			Node cur=queue.poll();
			int curNodeLevel=hashmap.get(cur);
			if(curNodeLevel==curLevel) {
				curLevelNode++;
			}
			else {
				curLevel++;
				max=max<curLevelNode?curLevelNode:max;
				curLevelNode=1;
			}
			if(cur.left!=null) {
				hashmap.put(cur.left, curNodeLevel+1);
				queue.add(cur.left);
			}
			if(cur.right!=null) {
				hashmap.put(cur, curNodeLevel+1);
				queue.add(cur.right);
			}
			
		}
		return max;
	}

也可以不用哈希表,通过维护三个变量也可以完成。

当前层的最后一个节点curend

下一层的最后一个节点nextend

当前层的节点数量curLevelNode

利用了curend判断有没有到当前层的最后一个节点,同时在当前层节点的孩子节点从左到右入队的过程中将nextend不断更新,当遍历到当前层的最后一个节点时,恰好也完成了nextend的更新。利用curend和nextend的滚动更新,可以实现标记每层的终止节点,从而可以正确地统计同一层的节点数量。

	public static int getMaxWidth2(Node head) {
		if(head==null)return 0;
		Node curend=head;
		Node nextend=null;
		int curLevelNode=0;
		int max=0;
		Queue<Node> queue=new LinkedList<>();
		queue.add(head);
		while(!queue.isEmpty()) {
			Node cur=queue.poll();
			if(cur.left!=null) {
				queue.add(cur.left);
				nextend=cur.left;
			}
			if(cur.right!=null) {
				queue.add(cur.right);
				nextend=cur.right;
			}
			curLevelNode++;
			if(cur==curend) {
				curend=nextend;
				max=max<curLevelNode?curLevelNode:max;
				curLevelNode=0;
			}
		}
		return max;
	}

六.二叉树的相关概念及其实现判断

只要是树形DP(我可以向左树和右树要信息来解决的问题)就可以用递归套路解决。

1.如何判断一棵二叉树是搜索二叉树

中序遍历的结果是依次升序就是搜索二叉树,否则不是。

	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);  //右树如果是整棵树就是BST,如果不是整棵树就不是BST
	}
	public static boolean inorder(Node head) {
		if(head==null)return true;
		int preValue=Integer.MIN_VALUE;
		Stack<Node> ss=new Stack<>();
		while(!ss.empty() || head!=null) {
			if(head!=null) {
				ss.push(head);
				head=head.left;
			}
			else {
				head=ss.pop();
				if(preValue>=head.value)return false;
				else preValue=head.value;
				head=head.right;
			}
		}
		return true;
	}

 递归套路:我需要向左树和右树要什么信息,然后建立递归。

	public static class ReturnType1{
		public boolean isBST;
		public int max;
		public int min;
		public ReturnType1(boolean x, int y, int z) {
			isBST=x;
			max=y;
			min=z;
		}
	}
	
	public static boolean process1(Node head) {
		return process2(head).isBST;
	}
	
	public static ReturnType1 process2(Node x) {
		if(x==null)return null;
		ReturnType1 leftData=process2(x.left);
		ReturnType1 rightData=process2(x.right);
		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, leftData.min);
			max=Math.max(max, rightData.max);
		}
		boolean isBST=true;
		if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)) {
			isBST=false;
		}
		if(rightData!=null&&(!rightData.isBST||x.value>=rightData.min)) {
			isBST=false;
		}
		return new ReturnType1(isBST, max, min);
	}

2.如何判断一棵二叉树是完全二叉树

两个条件:

  1. 任一节点如果出现有右无左,则返回false;
  2. 在条件一不违规下,如果遇到了第一个左右孩子不全的节点,那么从该节点往后的所有节点都必须是叶子节点。
	public static boolean isCBT(Node head) {
		if (head == null) {
			return true;
		}
		LinkedList<Node> queue = new LinkedList<>();
		boolean leaf = false;
		Node l = null;
		Node r = null;
		queue.add(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.add(l);
			}
			if (r != null) {
				queue.add(r);
			} 
            if(l==null || r==null) {
				leaf = true;
			}
		}
		return true;
	}

3.如何判断一棵二叉树是否是满二叉树

递归套路:

 统计数上的节点数量N和数的深度h,判断是否满足N=2^h-1

	public static class Info{
		public int height;
		public int nodes;
		public Info(int h, int n) {
			height=h;
			nodes=n;
		}
	}
	
	public static boolean isF(Node head) {
		if(head==null)return true;
		Info tmp=process3(head);
		return Math.pow(2, tmp.height)-1==tmp.nodes;
	}

	public static Info process3(Node x) {
		if(x==null)return new Info(0,0);
		Info leftData=process3(x.left);
		Info rightData=process3(x.right);
		
		int height=Math.max(leftData.height, rightData.height)+1;
		int nodes=leftData.nodes+rightData.nodes+1;
		return new Info(height, nodes);
	}

4.如何判断一颗二叉树是平衡二叉树

满足条件:

  1. 左树是平衡二叉树
  2. 右树的平衡二叉树
  3. |左树高-右树高|<=1 
	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}
	
    public static boolean isBalanced(Node head) {
		return process(head).isBalanced;
	}

	public static class ReturnType {
		public boolean isBalanced;
		public int height;

		public ReturnType(boolean isB, int hei) {
			isBalanced = isB;
			height = hei;
		}
	}

	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);
		boolean isBalanced = leftData.isBalanced && rightData.isBalanced
				&& Math.abs(leftData.height - rightData.height) < 2;
		return new ReturnType(isBalanced, height);
	}

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

情况一:O1是O2的LCA 或 O2是O1的LCA

情况二:O1与O2不互为LCA,向上才找到的LCA

	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 static 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的后继节点

两种情况:

情况一:x节点有右树的时候,x右树上的最左节点就是x的后继

情况二:x节点没有右树的时候,x往上找到的(第一个节点是其父亲的左孩子 的那个节点 的父亲)就是x的后继节点 【因为x节点一定是那个父亲节点左树上最后一个打印的】

PS:情况二有可能找不到后继节点,此时就让其后继为null

	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 != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

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

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

 先序遍历序列化:先序遍历,遇到节点记录节点值,遇到空就用'#'来表示。

	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<String>();
		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.equals("#")) {
			return null;
		}
		Node head = new Node(Integer.valueOf(value));
		head.left = reconPreOrder(queue);
		head.right = reconPreOrder(queue);
		return head;
	}

十.折横问题

一张纸条对折n次,请打印出从左到右的折横状况。

这是头节点为凹,每个左子树头节点的为凹,右子树头节点为凸的二叉树。 对这棵树中序遍历打印就是从左到右的折横情况。

	public static void printAllFolds(int N) {
		printProcess(1, N, true);
	}

	public static void printProcess(int i, int N, boolean down) {
		if (i > N) {
			return;
		}
		printProcess(i + 1, N, true);
		System.out.println(down ? "down " : "up ");
		printProcess(i + 1, N, false);
	}

	public static void main(String[] args) {
		int N = 1;
		printAllFolds(N);
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看未来捏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值