二叉树是一个树形数据结构,每个节点最多可以有两个子节点,称为左子节点和右子节点。
二叉树的定义:
private static class TreeNode {
private TreeNode left;
private TreeNode right;
private final int val;
TreeNode(int x) {
this.val = x;
}
}
几种特殊的树:
二叉查找树是由n个节点随机构成,极端情况下,会退化成一个有n个节点的线性链表。此时,查询效率极低。为提高查找效率,二叉树得尽可能平衡。
平衡二叉树:AVL,带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的。AVL树适合用于插入删除次数比较少,但查找多的情况。
由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。
红黑树:一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是red或black。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树(由于是弱平衡,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,就用红黑树。
算法面试中,关于二叉树的考点:遍历,查找,
二叉树的遍历方式有很多,包括:层序(层次)遍历,前序(先序)遍历,中序遍历,后序遍历。后面三种遍历根据是否使用递归,又分为递归遍历和非递归遍历。
二叉查找树,按照中序遍历的方式可以从小到大的顺序排序输出。
即广度优先搜索(BFS),可考虑利用队列来实现。BFS,核心就在于使用队列Queue来做一个while循环,在循环内除了对当前节点的处理之外,还需要将当前节点的孩子节点放入队列的尾部。
public static List<Integer> postOrderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> nodeStack = new Stack<>();
TreeNode nodeTemp = root;
TreeNode preNode = null;
while (nodeTemp != null || !nodeStack.isEmpty()) {
while (nodeTemp != null) {
nodeStack.push(nodeTemp);
nodeTemp = nodeTemp.left;
}
nodeTemp = nodeStack.peek();
if (nodeTemp.right == null || nodeTemp.right == preNode) {
nodeTemp = nodeStack.pop();
list.add(nodeTemp.val);
preNode = nodeTemp;
nodeTemp = null;
} else {
nodeTemp = nodeTemp.right;
}
}
return list;
}
左叶子的定义,递归找到所有的子树的左叶子节点。
如果二叉树的左子树是叶子节点,要找的就是它。
如果二叉树的左子树不是一个叶子节点,则递归调用此过程去获取左子树的左叶子节点的值。
二叉树的右子树,递归调用获取右子树的左叶子节点值即可。
public class SumOfLeftLeaves {
/**
* 递归方式
*/
public static int sumOfLeftLeaves(TreeNode root) {
// 递归结束条件
if (root == null) {
return 0;
}
int left, right;
if (root.left != null && root.left.left == null && root.left.right == null) {
// 判断是否为左叶子节点
left = root.left.val;
} else {
left = sumOfLeftLeaves(root.left);
}
right = sumOfLeftLeaves(root.right);
return left + right;
}
/**
* 前序遍历迭代方式
*/
public static int sumOfLeftLeavesByPreOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
int sum = 0;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
if (root.left != null && root.left.left == null && root.left.right == null) {
sum += root.left.val;
}
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop().right;
}
}
return sum;
}
/**
* 层级遍历二叉树,层序遍历,广度遍历
*/
public static int sumOfLeftLeavesByLevelOrder(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new ArrayDeque<>();
int sum = 0;
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null && node.left.left == null && node.left.right == null) {
sum += node.left.val;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
return sum;
}
/**
* 层次遍历
*/
public static void levelOrderTraversal(TreeNode node) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(node);
while (!queue.isEmpty()) {
node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
}
public static TreeNode buildTree(int[] inOrder, int[] postOrder) {
return helper(inOrder, postOrder, postOrder.length - 1, 0, inOrder.length - 1);
}
private static TreeNode helper(int[] inOrder, int[] postOrder, int postEnd, int inStart, int inEnd) {
if (inStart > inEnd) {
return null;
}
int currentVal = postOrder[postEnd];
TreeNode current = new TreeNode(currentVal);
int inIndex = 0;
for (int i = inStart; i <= inEnd; i++) {
if (inOrder[i] == currentVal) {
inIndex = i;
}
}
TreeNode left = helper(inOrder, postOrder, postEnd - (inEnd - inIndex) - 1, inStart, inIndex - 1);
TreeNode right = helper(inOrder, postOrder, postEnd - 1, inIndex + 1, inEnd);
current.left = left;
current.right = right;
return current;
}
也叫最大高度;思路:分别求出左右子树的最大深度,然后取其较大者。
public static int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int deep = 1;
if (root.left == null && root.right == null) {
return 1;
}
int leftDeep = 0;
if (root.left != null) {
leftDeep = 1 + maxDepth(root.left);
}
int rightDeep = 0;
if (root.right != null) {
rightDeep = 1 + maxDepth(root.right);
}
return deep + leftDeep > rightDeep ? leftDeep : rightDeep;
}
简化版:
public static int maxDepth1(TreeNode root) {
if (root == null) {
return 0;
}
// 注意要+1
return Math.max(maxDepth1(root.left), maxDepth1(root.right)) + 1;
}
判断给定的二叉树是否是一颗平衡树。平衡树的定义是左右子树的高度差不超过1。
public static boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
return (Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1) && isBalanced(root.left)
&& isBalanced(root.right);
}
给定一个升序排序的整数数组 nums,将其转换为一棵高度平衡的二叉搜索树。
分析:给定一个有序数组,转换成二叉搜索树,即左子树小于根节点,根节点小于右子树,满足条件的二叉搜索树显然不止一种。
public static TreeNode sortedArrayToBST(int[] nums) {
return traversal(nums, 0, nums.length - 1);
}
public static TreeNode traversal(int[] nums, int left, int right) {
if (left > right){
return null;
}
int mid = left + ((right - left) / 2);
TreeNode node = new TreeNode(nums[mid]);
node.left = traversal(nums, left, mid - 1);
node.right = traversal(nums, mid + 1, right);
return node;
}
给定一个二叉树,和一个整数sum,求路径和为sum的方案。
分析:路径指的是从根节点到叶子节点,每次可以选择左子树或右子树。假如sum过小或过大,则不存在这样的路径;有时,存在不止一条路径。
public static List<List<Integer>> pathSum(TreeNode root, int sum) {
if (root == null) {
return new ArrayList<>();
}
List<List<Integer>> ans = new ArrayList<>();
if (root.val == sum && root.left == null && root.right == null) {
List<Integer> arr = new ArrayList<>();
arr.add(root.val);
ans.add(arr);
return ans;
}
List<List<Integer>> left = pathSum(root.left, sum - root.val);
List<List<Integer>> right = pathSum(root.right, sum - root.val);
for (List<Integer> list : left) {
list.add(0, root.val);
ans.add(list);
}
for (List<Integer> list : right) {
list.add(0, root.val);
ans.add(list);
}
return ans;
}
右视图的定义:从右侧看一棵树,从上到下看到的右叶子节点序列。
public static List<Integer> rightSideView(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {
return list;
}
list.add(root.val);
List<Integer> list1 = rightSideView(root.right);
List<Integer> list2 = rightSideView(root.left);
list.addAll(list1);
if (list1.size() < list2.size()) {
for (int i = list1.size(); i < list2.size(); i++) {
list.add(list2.get(i));
}
}
return list;
}
思路和层序遍历没有多大差别:
public static String serialize(TreeNode root) {
if (root == null) {
return "[]";
}
ArrayList<TreeNode> queue = new ArrayList<>();
queue.add(root);
for (int i = 0; i < queue.size(); i++) {
TreeNode node = queue.get(i);
if (node == null) {
continue;
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
while (queue.get(queue.size() - 1) == null) {
queue.remove(queue.size() - 1);
}
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(queue.get(0));
for (int i = 1; i < queue.size(); i++) {
if (queue.get(i) == null) {
sb.append(",");
sb.append("null");
} else {
sb.append(",");
sb.append(queue.get(i).val);
}
}
sb.append("]");
return sb.toString();
}