数据结构基础之《(17)—二叉树》

一、什么是二叉树

1、结构描述
Class Node {
    V value;
    Node left;
    Node right;
}

2、二叉树的先序、中序、后序遍历
先序:任何子树的处理顺序都是,先头节点、再左子树、然后右子树(中左右)
中序:任何子树的处理顺序都是,先左子树、再头节点、然后右子树(左中右)
后序:任何子树的处理顺序都是,先左子树、在右子树、然后头节点(左右中)

package class07;

public class Code01_RecursiveTraversalBT {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int v) {
			value = v;
		}
	}
	
	/**
	 * 先序遍历方式打印
	 * @param head
	 */
	public static void pre(Node head) {
		if (head == null) {
			return;
		}
		System.out.println(head.value);
		pre(head.left);
		pre(head.right);
	}
	
	/**
	 * 中序遍历方式
	 * @param head
	 */
	public static void in(Node head) {
		if (head == null) {
			return;
		}
		in(head.left);
		System.out.println(head.value);
		in(head.right);
	}
	
	/**
	 * 后序遍历方式
	 * @param head
	 */
	public static void pos(Node head) {
		if (head == null) {
			return;
		}
		pos(head.left);
		pos(head.right);
		System.out.println(head.value);
	}
	
	/**
	 * 是不是发现上面三个函数结构一样,只是打印位置不同
	 * @param head
	 */
	public static void f(Node head) {
		if (head == null) {
			return;
		}
		// 把打印行为放在这儿就是先序打印
		f(head.left);
		// 把打印行为放在这儿就是中序打印
		f(head.right);
		// 把打印行为放在这儿就是后序打印
	}
	
	public static void main(String[] args) {
		Node n1 = new Node(1);
		Node n2 = new Node(2);
		Node n3 = new Node(3);
		Node n4 = new Node(4);
		Node n5 = new Node(5);
		Node n6 = new Node(6);
		Node n7 = new Node(7);
		n1.left = n2;
		n1.right = n3;
		n2.left = n4;
		n2.right = n5;
		n3.left = n6;
		n3.right = n7;
		n6.left = null;
		n6.right = null;
		n7.left = null;
		n7.right = null;
		
		pre(n1);
		System.out.println();
		in(n1);
		System.out.println();
		pos(n1);
		
	}
}

3、所以递归遍历的本质是递归序
上面的代码,f函数处理节点的时候一定会回到函数3次
(1)刚进来的时候
(2)去左树转了一圈之后,返回来了
(3)去右树转了一圈之后,返回来了

所以
先序:第一次到达的时候打印
中序:第二次到达的时候打印
后序:第三次到达的时候打印

说明,任何一个节点,都有机会到它左树转一圈回到它,收集一些信息。任何节点都可以向它右树转一圈回到它,收集一些信息。还能第三次回到它,把左右两个信息做整合。这是在树上做动态规划的基础。
为什么一个方法可以改出三种序列,只是打印方法位置不同?因为递归很强,递归可以让一个东西来到3次,所以可以随便加工先序、中序、后序。

4、递归方式实现二叉树的先序、中序、后序遍历(看上面的代码)
(1)理解递归序
(2)先序、中序、后序都可以在递归的基础上加工出来
(3)第一次到达一个节点就打印就是先序、第二次打印即中序、第三次打印即后序

5、非递归方式实现二叉树的先序、中序、后序遍历
(1)任何递归函数都可以改成非递归
(2)自己设计压栈来实现
  规则:
  先序遍历
  1)弹出就打印
  2)如果有右孩子,先压入右孩子,如果没有就不压
  3)然后如果有左孩子,就压入左孩子,没有就不压了,先右后左
  4)出栈顺序:头、左、右
  5)出栈结果:1,2,4,5,3,6,7
  中序遍历
  1)整条左边界依次入栈
  2)第一步到底了,弹出就打印
  3)来到弹出节点的右树上,继续执行第一步
  4)出栈结果:4,2,5,1,6,3,7
  后续遍历
  1)弹出就打印
  2)基于先序遍历,对每一颗子树来说,先头,再右,再左。把右和左交换一下,改成先头,再左,再右
  3)如有左,压入左
  4)如有右,压入右
  5)出栈顺序:头、右、左
  6)此时出栈结果:1,3,7,6,2,5,4
  7)把出栈结果逆着看就是后序:4,5,2,6,7,3,1

package class07;

import java.util.Stack;

public class Code02_UnRecursiveTraversalBT {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node (int v) {
			value = v;
		}
	}
	
	/**
	 * 用非递归方式实现先序遍历
	 * @param head
	 */
	public static void pre(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();
	}
	
	/**
	 * 用非递归方式实现中序遍历
	 * @param head
	 */
	public static void in(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();
	}
	
	/**
	 * 用非递归方式实现后序遍历
	 * @param head
	 */
	public static void pos1(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); //头节点进入到s1里去
			while (!s1.isEmpty()) {
				head = s1.pop(); //没一个节点从s1弹出了,不打印,加到s2里去
				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();
	}
	
	/**
	 * 用一个栈搞定后序遍历
	 * @param h
	 */
	public static void pos2(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(); //c指向顶部节点,没有弹出
				if (c.left != null && h != c.left && h != c.right) {
					stack.push(c.left); //栈中压入c的左孩子
				} else if (c.right != null && h != c.right) {
					stack.push(c.right);
				} else {
					System.out.print(stack.pop().value + " ");
					h = c;
				}
			}
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		Node n1 = new Node(1);
		Node n2 = new Node(2);
		Node n3 = new Node(3);
		Node n4 = new Node(4);
		Node n5 = new Node(5);
		Node n6 = new Node(6);
		Node n7 = new Node(7);
		n1.left = n2;
		n1.right = n3;
		n2.left = n4;
		n2.right = n5;
		n3.left = n6;
		n3.right = n7;
		n6.left = null;
		n6.right = null;
		n7.left = null;
		n7.right = null;
		
		pre(n1);
		in(n1);
		pos1(n1);
		pos2(n1);
		
	}
}

6、实现二叉树的按层遍历

(1)按层级遍历二叉树

package class07;

import java.util.LinkedList;
import java.util.Queue;

public class Code03_LevelTraversalBT {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int v) {
			value = v;
		}
	}
	
	public static void level(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 void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(2);
		head.right = new Node(3);
		head.left.left = new Node(4);
		head.left.right = new Node(5);
		head.right.left = new Node(6);
		head.right.right = new Node(7);
		
		level(head);
		
	}
}

(2)计算二叉树的最大宽度,其实就是宽度优先遍历,用队列
可以通过设置flag变量的方式,来发现某一层的结束

package class07;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 求二叉树的最大宽度
 */
public class Code06_TreeMaxWidth {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int data) {
			this.value = data;
		}
	}
	
	/**
	 * 用map实现计算二叉树的宽度
	 * @param head
	 * @return
	 */
	public static int maxWidthUseMap(Node head) {
		if (head == null) {
			return 0;
		}
		Queue<Node> queue = new LinkedList<>();
		queue.add(head);
		//value是Node,key是一个整数,表示key在哪一层
		HashMap<Node, Integer> levelMap = new HashMap<>();
		levelMap.put(head, 1);
		int curLevel = 1; //当前在第几层
		int curLevelNodes = 0; //当前层宽度目前是多少
		int max = 0; //所有层的宽度的最大值
		while (!queue.isEmpty()) {
			Node cur = queue.poll();
			int curNodeLevel = levelMap.get(cur); //当前节点在哪一层
			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);
			}
			//当前节点所在层数和目前统计的层数一样
			if (curNodeLevel == curLevel) {
				curLevelNodes++; //当前层的宽度加1
			} else {
				max = Math.max(max, curLevelNodes);
				curLevel++;
				curLevelNodes = 1;
			}
		}
		max = Math.max(max, curLevelNodes);
		return max;
	}
	
	/**
	 * 不用map实现计算二叉树的宽度
	 * @param head
	 * @return
	 */
	public static int maxWidthNoMap(Node head) {
		if (head == null) {
			return 0;
		}
		Queue<Node> queue = new LinkedList<>();
		queue.add(head);
		Node curEnd = head; //当前层,最右节点是谁
		Node nextEnd = null; //如果有下一层,最右节点是谁
		int max = 0;
		int curLevelNodes = 0; //当前层的节点数
		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;
			}
			curLevelNodes++; //当前层加加
			if (cur == curEnd) { //如果当前节点是当前层最右的节点
				max = Math.max(max, curLevelNodes);
				curLevelNodes = 0;
				curEnd = nextEnd;
			}
		}
		return max;
	}
	
	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(2);
		head.right = new Node(3);
		head.left.left = new Node(4);
		head.left.right = new Node(5);
		head.right.left = new Node(6);
		head.right.right = new Node(7);
		
		System.out.println(maxWidthUseMap(head));
		System.out.println(maxWidthNoMap(head));
		
	}
}

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

(1)可以用先序或者中序或者后序或者按层遍历,来实现二叉树的序列化
注意:不要忽略空节点,把这棵树用空节点补全
(2)用了什么方式序列化,就用什么方式反序列化

package class07;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 二叉树的序列化和反序列化
 */
public class Code04_SerializeAndReconstructTree {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int data) {
			this.value = data;
		}
	}
	
	/**
	 * 先序遍历的序列化
	 * @param head
	 * @return
	 */
	public static Queue<String> preSerial(Node head) {
		Queue<String> ans = new LinkedList<>();
		pres(head, ans);
		return ans;
	}
	
	public static void pres(Node head, Queue<String> ans) {
		if (head == null) {
			ans.add(null);
		} else {
			ans.add(String.valueOf(head.value));
			pres(head.left, ans);
			pres(head.right, ans);
		}
	}
	
	/**
	 * 先序遍历的反序列化
	 * @param prelist
	 * @return
	 */
	public static Node buildByPreQueue(Queue<String> prelist) {
		if (prelist == null || prelist.size() == 0) {
			return null;
		}
		return preb(prelist);
	}
	
	public static Node preb(Queue<String> prelist) {
		String value = prelist.poll();
		if (value == null) {
			return null;
		}
		Node head = new Node(Integer.valueOf(value));
		head.left = preb(prelist);
		head.right = preb(prelist);
		return head;
	}
	
	/**
	 * 按层序列化
	 * @param head
	 * @return
	 */
	public static Queue<String> levelSerial(Node head) {
		Queue<String> ans = new LinkedList<>();
		if (head == null) {
			ans.add(null);
		} else {
			ans.add(String.valueOf(head.value)); //加入队列的时候,序列化进去
			Queue<Node> queue = new LinkedList<>();
			queue.add(head);
			while(!queue.isEmpty()) {
				head = queue.poll();
				if (head.left != null) { //左孩子不等于空,既序列化也加队列
					ans.add(String.valueOf(head.left.value));
					queue.add(head.left);
				} else {
					ans.add(null); //左孩子为空,只序列化不加队列
				}
				if (head.right != null) { //右孩子不等于空,既序列化也加队列
					ans.add(String.valueOf(head.right.value));
					queue.add(head.right);
				} else {
					ans.add(null); //右孩子为空,只序列化不加队列
				}
			}
		}
		return ans;
	}
	
	/**
	 * 按层反序列化
	 * @param head
	 * @return
	 */
	public static Node buildByLevelQueue(Queue<String> levelList) {
		if (levelList == null || levelList.size() == 0) {
			return null;
		}
		Node head = generateNode(levelList.poll());
		Queue<Node> queue = new LinkedList<>();
		if (head != null) {
			queue.add(head);
		}
		Node node = null;
		while (!queue.isEmpty()) {
			node = queue.poll();
			node.left = generateNode(levelList.poll()); //左孩子建出来,null是占位符
			node.right = generateNode(levelList.poll()); //右孩子建出来,null是占位符
			if (node.left != null) { //左孩子不等于空,队列加左孩子
				queue.add(node.left);
			}
			if (node.right != null) { //右孩子不等于空,队列加右孩子
				queue.add(node.right);
			}
		}
		return head;
	}
	
	public static Node generateNode(String val) {
		if (val == null) {
			return null;
		}
		return new Node(Integer.valueOf(val));
	}
	
	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(2);
		head.right = new Node(3);
		head.left.left = new Node(4);
		head.left.right = new Node(5);
		head.right.left = new Node(6);
		head.right.right = new Node(7);
		
		Queue<String> queue = preSerial(head);
		queue.forEach(str -> {
			System.out.print(str + " ");
		});
		
		head = buildByPreQueue(queue);
		System.out.println();
		
		queue = levelSerial(head);
		queue.forEach(str -> {
			System.out.print(str + " ");
		});
		
		head = buildByLevelQueue(queue);
		System.out.println();
		
		queue = preSerial(head);
		queue.forEach(str -> {
			System.out.print(str + " ");
		});
	}
}

二、二叉树的递归套路

1、可以解决面试中绝大多数的二叉树问题,尤其是树型dp问题,本质是利用递归遍历二叉树的便利性
(1)假设以X节点为头,假设可以向X左树和X右树要任何信息
(2)在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要)
(3)列出所有可能性后,确定到底需要向左树和右树要什么样的信息
(4)把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S
(5)递归函数都返回S,每一棵子树都这么要求
(6)写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息

2、给定一棵二叉树的头节点head,返回这棵二叉树是不是平衡二叉树
平衡性‌:它的左子树和右子树的高度差的绝对值不超过1。

package class08;

/**
 * 这棵二叉树是不是平衡二叉树
 */
public class Code01_IsBalanced {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int data) {
			this.value = data;
		}
	}
	
	/**
	 * 方法一
	 * @param head
	 * @return
	 */
	public static boolean isBalanced1(Node head) {
		boolean[] ans = new boolean[1];
		ans[0] = true;
		process1(head, ans);
		return ans[0];
	}
	
	public static int process1(Node head, boolean[] ans) {
		if (!ans[0] || head == null) {
			return -1;
		}
		int leftHeight = process1(head.left, ans);
		int rightHeight = process1(head.right, ans);
		if (Math.abs(leftHeight - rightHeight) > 1) {
			ans[0] = false;
		}
		return Math.max(leftHeight, rightHeight) + 1;
	}
	
	/**
	 * 方法二
	 * @param head
	 * @return
	 */
	public static boolean isBalanced2(Node head) {
		return process2(head).isBalanced;
	}
	
	/**
	 * 对左子树、右子树要求一样
	 * Info是信息返回的结构体
	 */
	public static class Info {
		public boolean isBalanced;
		public int height;
		
		public Info(boolean b, int h) {
			isBalanced = b;
			height = h;
		}
	}
	
	/**
	 * 以X为头的树
	 * @param X
	 * @return
	 */
	public static Info process2(Node X) {
		if (X == null) {
			return new Info(true, 0); //空树,返回是平衡树,深度是0
		}
		
		Info leftInfo = process2(X.left); //左树上是否平衡,左树上高度是多少
		Info rightInfo = process2(X.right); //右树上是否平衡,右树上高度是多少
		
		//X节点的高度,是左树的高度和右树的高度,取最大值,加当前头节点的高度
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		
		//看是否平衡
		boolean isBalanced = true;
		//左树不平衡,或者右树不平衡,或者左右树高度差大于1
		if (!leftInfo.isBalanced || !rightInfo.isBalanced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
			isBalanced = false;
		}
		return new Info(isBalanced, height);
	}
	
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}
	
	public static Node generate(int level, int maxLevel, int maxValue) {
		if (level > maxLevel || Math.random() < 0.6) {
			return null;
		}
		Node head = new Node((int) (Math.random() * maxValue));
		head.left = generate(level + 1, maxLevel, maxValue);
		head.right = generate(level + 1, maxLevel, maxValue);
		return head;
	}
	
	public static void main(String[] args) {
		Node head = generateRandomBST(100, 100);
		System.out.println(isBalanced1(head));
		System.out.println(isBalanced2(head));
	}
}

3、给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离
分类:
(1)和X头节点无关
左树的最大距离,和右树的最大距离,取max
(2)和X头节点有关
最大距离通过X
X左树上离它最远的点,走到X右树上离它最远的点
即:左树的高度 + 1 + 右树的高度

package class08;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class Code08_MaxDistance {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int data) {
			this.value = data;
		}
	}
	
	/**
	 * 方法一(比较用)
	 * @param head
	 * @return
	 */
	public static int maxDistance1(Node head) {
		if (head == null) {
			return 0;
		}
		ArrayList<Node> arr = getPrelist(head);
		HashMap<Node, Node> parentMap = getParentMap(head);
		int max = 0;
		for (int i = 0; i < arr.size(); i++) {
			for (int j = i; j < arr.size(); j++) {
				max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j)));
			}
		}
		return max;
	}
	
	public static ArrayList<Node> getPrelist(Node head) {
		ArrayList<Node> arr = new ArrayList<>();
		fillPrelist(head, arr);
		return arr;
	}
	
	public static void fillPrelist(Node head, ArrayList<Node> arr) {
		if (head == null) {
			return;
		}
		arr.add(head);
		fillPrelist(head.left, arr);
		fillPrelist(head.right, arr);
	}
	
	public static HashMap<Node, Node> getParentMap(Node head) {
		HashMap<Node, Node> map = new HashMap<>();
		map.put(head, null);
		fillParentMap(head, map);
		return map;
	}
	
	public static void fillParentMap(Node head, HashMap<Node, Node> parentMap) {
		if (head.left != null) {
			parentMap.put(head.left, head);
			fillParentMap(head.left, parentMap);
		}
		if (head.right != null) {
			parentMap.put(head.right, head);
			fillParentMap(head.right, parentMap);
		}
	}
	
	public static int distance(HashMap<Node, Node> parentMap, Node o1, Node o2) {
		HashSet<Node> o1Set = new HashSet<>();
		Node cur = o1;
		o1Set.add(cur);
		while (parentMap.get(cur) != null) {
			cur = parentMap.get(cur);
			o1Set.add(cur);
		}
		cur = o2;
		while (!o1Set.contains(cur)) {
			cur = parentMap.get(cur);
		}
		Node lowestAncestor = cur;
		cur = o1;
		int distance1 = 1;
		while (cur != lowestAncestor) {
			cur = parentMap.get(cur);
			distance1++;
		}
		cur = o2;
		int distance2 = 1;
		while (cur != lowestAncestor) {
			cur = parentMap.get(cur);
			distance2++;
		}
		return distance1 + distance2 - 1;
	}
	
	/**
	 * 方法二(用递归)
	 * @param head
	 * @return
	 */
	public static int maxDistance2(Node head) {
		return process(head).maxDistance;
	}
	
	public static class Info {
		public int maxDistance; //最大距离
		public int height; //高度
		
		public Info(int dis, int h) {
			this.maxDistance = dis;
			this.height = h;
		}
	}
	
	public static Info process(Node X) {
		if (X == null) {
			return new Info(0, 0);
		}
		//左树给我的信息
		Info leftInfo = process(X.left);
		//右树给我的信息
		Info rightInfo = process(X.right);
		
		//加工高度
		//左树和右树的最大高度,加上我自己
		int height = Math.max(leftInfo.height , rightInfo.height) + 1;
		
		//加工最大距离
		//左树的最大距离、右树的最大距离、左树的高度+右树的高度+我自己,求最大值
		int maxDistance = Math.max(Math.max(
				leftInfo.maxDistance, rightInfo.maxDistance), 
				leftInfo.height + rightInfo.height + 1);
		return new Info(maxDistance, height);
	}
	
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}
	
	public static Node generate(int level, int maxLevel, int maxValue) {
		if (level > maxLevel || Math.random() < 0.2) {
			return null;
		}
		Node head = new Node((int) (Math.random() * maxValue));
		head.left = generate(level + 1, maxLevel, maxValue);
		head.right = generate(level + 1, maxLevel, maxValue);
		return head;
	}
	
	public static void main(String[] args) {
		Node head = generateRandomBST(20, 100);
		System.out.println(maxDistance1(head));
		System.out.println(maxDistance2(head));
	}
}

4、给定一棵二叉树的头节点head,返回这棵二叉树中最大的二叉搜索子树的头节点
什么叫搜索二叉树:整棵树上没有重复值,然后左树的值都比我小,右树的值都比我大,每棵子树都如此

部分是搜索二叉树:

分类:
(1)和X头节点无关
左树上满足搜索二叉树的子树或右树上的子树
(2)和X头节点有关
以X为头整体的树。左树整体是搜索二叉树,右树整体也是搜索二叉树,左树上的最大值要小于X,右树上的最小值要大于X

每个节点要收集的信息:
(1)最大二叉搜索子树的大小
(2)是否是搜索二叉树
(3)整棵树的最大值
(4)整棵树的最小值

package class08;

import java.util.ArrayList;

/**
 * 给定一棵二叉树的头节点head,返回这棵二叉树中最大的二叉搜索子树的头节点
 */
public class Code04_MaxSubBSTSize {

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		
		public Node(int data) {
			this.value = data;
		}
	}
	
	public static int getBSTSize(Node head) {
		if (head == null) {
			return 0;
		}
		
		ArrayList<Node> arr = new ArrayList<>();
		in(head, arr);
		for (int i = 1; i < arr.size(); i++) {
			if (arr.get(i).value <= arr.get(i - 1).value) {
				return 0;
			}
		}
		return arr.size();
	}
	
	public static void in(Node head, ArrayList<Node> arr) {
		if (head == null) {
			return;
		}
		in(head.left, arr);
		arr.add(head);
		in(head.right, arr);
	}
	
	public static int maxSubBSTSize1(Node head) {
		if (head == null) {
			return 0;
		}
		int h = getBSTSize(head);
		if (h != 0) {
			return h;
		}
		return Math.max(maxSubBSTSize1(head.left), maxSubBSTSize1(head.right));
	}
	
	public static int maxSubBSTSize2(Node head) {
		if (head == null) {
			return 0;
		}
		return process(head).maxSubBSTSize;
	}
	
	/**
	 * 任何子树都返回四个信息
	 */
	public static class Info {
		public boolean isAllBST; //子树整体是不是BST
		public int maxSubBSTSize; //满足搜索二叉树条件的最大子树的大小
		public int min; //整棵树的最小值
		public int max; //整棵树的最大值
		
		public Info(boolean is, int size, int mi, int ma) {
			isAllBST = is;
			maxSubBSTSize = size;
			min = mi;
			max = ma;
		}
	}
	
	public static Info process(Node X) {
		if (X == null) { //如果是空树
			return null;
		}
		Info leftInfo = process(X.left); //左树可以给我四个信息
		Info rightInfo = process(X.right); //右树可以给我四个信息
		
		//加工出我的四个信息
		boolean isBST = false;
		int min = X.value;
		int max = X.value;
		int maxSubBSTSize = 0;
		
		if (leftInfo != null) { //如果左树不空
			min = Math.min(min, leftInfo.min); //我的最小值,和左树信息中最小值比较
			max = Math.max(max, leftInfo.max); //我的最大值,和左树信息中最大值比较
			maxSubBSTSize = Math.max(maxSubBSTSize, leftInfo.maxSubBSTSize); //我的最大子树大小和左树信息中的最大子树大小比较
		}
		if (rightInfo != null) { //如果右树不空
			min = Math.min(min, rightInfo.min); //我的最小值,和右树信息中最小值比较
			max = Math.max(max, rightInfo.max); //我的最大值,和右树信息中最大值比较
			maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize); //我的最大子树大小和右树信息中的最大子树大小比较
		}
		
		//是不是BST?
		//X的值要大于左树的最大值,小于右树的最小值
//		if ((leftInfo == null ? true : (leftInfo.isAllBST && leftInfo.max < X.value))
//				&& (rightInfo == null ? true : (rightInfo.isAllBST && rightInfo.min > X.value))) 
		if (
				//更清晰的写法
				(leftInfo == null ? true : leftInfo.isAllBST) //左树整体需要是搜索二叉树
				&& 
				(rightInfo == null ? true : rightInfo.isAllBST) //右树整体需要是搜索二叉树
				&& 
				(leftInfo == null ? true : leftInfo.max < X.value) //X的值要大于左树的最大值
				&& 
				(rightInfo == null ? true : rightInfo.min > X.value) //X的值要小于右树的最小值
				)
		{
			isBST = true;
			//以X为头整体的树是搜索二叉树时,计算最大子树的大小,左树大小加右树大小加X这一个节点的大小
			maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
					+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
		}
		return new Info(isBST, maxSubBSTSize, min, max);
	}
	
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}
	
	public static Node generate(int level, int maxLevel, int maxValue) {
		if (level > maxLevel || Math.random() < 0.2) {
			return null;
		}
		Node head = new Node((int) (Math.random() * maxValue));
		head.left = generate(level + 1, maxLevel, maxValue);
		head.right = generate(level + 1, maxLevel, maxValue);
		return head;
	}
	
	public static void main(String[] args) {
		Node head = generateRandomBST(20, 100);
		System.out.println(maxSubBSTSize1(head)); //方法一,非常耗时
		System.out.println(maxSubBSTSize2(head)); //方法二,递归,很快
	}
}

5、派对的最大快乐值
员工信息的定义如下:
class Employee {
    public int happy; //这名员工可以带来的快乐值
    List<Employee> subordinates; //这名员工有哪些直接下级
}
公司的每个员工都符合Employee类的描述。整个公司的人员结构可以看作是一颗标准的、没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。

这个公司现在要办party,你可以决定哪些员工来,哪些员工不来,规则:
(1)如果某个员工来了,那么这个员工的所有直接下级都不能来
(2)派对的整体快乐值是所有到场员工快乐值的累加
(3)你的目标是让派对的整体快乐值尽量大

给定一棵多叉树的头节点boss,请返回派对的最大快乐值。

分类:
(1)X不来
    X快乐值是0
    (直接下级可以来,也可以不来)求下面每个节点来的时候整棵树的最大值,或者不来时整棵树的最大值,求max
(2)X来
    X自己的快乐值
    (直接下级肯定不来)加上下面每个节点不来时,整棵树的最大值

package class08;

import java.util.ArrayList;
import java.util.List;

public class Code09_MaxHappy {

	public static class Employee {
		public int happy;
		public List<Employee> nexts;
		
		public Employee(int h) {
			happy = h;
			nexts = new ArrayList<>();
		}
	}
	
	public static int maxHappy1(Employee boss) {
		if (boss == null) {
			return 0;
		}
		return process1(boss, false);
	}
	
	public static int process1(Employee cur, boolean up) {
		if (up) {
			int ans = 0;
			for (Employee next : cur.nexts) {
				ans += process1(next, false);
			}
			return ans;
		} else {
			int p1 = cur.happy;
			int p2 = 0;
			for (Employee next : cur.nexts) {
				p1 += process1(next, true);
				p2 += process1(next, false);
			}
			return Math.max(p1, p2);
		}
	}
	
	public static int maxHappy2(Employee boss) {
		if (boss == null) {
			return 0;
		}
		Info all = process2(boss);
		return Math.max(all.yes, all.no);
	}
	
	public static class Info {
		public int yes; //这个节点来的情况下整棵树的快乐最大值
		public int no; //这个节点不来的情况下整棵树的快乐最大值
		
		public Info(int y, int n) {
			yes = y;
			no = n;
		}
	}
	
	public static Info process2(Employee x) {
		if (x.nexts.isEmpty()) { //没有下级了,整棵树就你一个人
			return new Info(x.happy, 0);
		}
		int yes = x.happy; //这个节点来
		int no = 0; //这个节点不来
		for (Employee next : x.nexts) {
			Info nextInfo = process2(next); //对每一个下级都调递归函数要来它的信息
			yes += nextInfo.no; //如果x是来的,则累加这个下级节点不来的子树的快乐最大值
			no += Math.max(nextInfo.yes, nextInfo.no); //如果x是不来的,则每一个下级节点yes和no都做max累加到x的no上
		}
		return new Info(yes, no);
	}
	
	// for test
	public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) {
		if (Math.random() < 0.02) {
			return null;
		}
		Employee boss = new Employee((int) (Math.random() * (maxHappy + 1)));
		genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy);
		return boss;
	}
	
	// for test
	public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) {
		if (level > maxLevel) {
			return;
		}
		int nextsSize = (int) (Math.random() * (maxNexts + 1));
		for (int i = 0; i < nextsSize; i++) {
			Employee next = new Employee((int) (Math.random() * (maxHappy + 1)));
			e.nexts.add(next);
			genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy);
		}
	}
	
	public static void main(String[] args) {
		int maxLevel = 4;
		int maxNexts = 7;
		int maxHappy = 100;
		int testTimes = 100000;
		for (int i = 0; i < testTimes; i++) {
			Employee boss = genarateBoss(maxLevel, maxNexts, maxHappy);
			if (maxHappy1(boss) != maxHappy2(boss)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值