二叉树总结
树的遍历方式
分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
常见的 DFS : 先序遍历、中序遍历、后序遍历;
先序:根、左、右
中序:左、根、右
后序:左、右、根。从底至顶
往往利用 递归 或 栈 实现
常见的 BFS : 层序遍历(即按层遍历)
往往利用 队列 实现
前序
中序
后序
层序
特殊的树
二叉查找树(英语:Binary Search Tree),也称为 二叉搜索树、有序二叉树(Ordered Binary Tree)或排序二叉树(Sorted Binary Tree),是指一棵空树或者具有下列性质的二叉树:(左<根<右,有点二分查找的意思)
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
- 对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。
- 查找所需的次数小于等于二叉树的高度。
- 最好的情况是O(logn),存在于完全二叉树中。最差的情况是O(n),比如插入的元素所有节点都没有左子树(右子树),这种情况需要将二叉树的全部节点遍历一次。
平衡⼆叉树——平衡⼆叉树⼜被称为AVL树(区别于AVL算法),它是⼀棵⼆叉查找树,且具有以下性质:它是⼀棵空树或它的左右两个⼦树的⾼度差的绝对值不超过1,并且左右两个⼦树都是⼀棵平衡⼆叉树。
- 它必须是二叉查找树。
- 每个节点的左子树和右子树的高度差至多为1。
由于普通的二叉查找树会容易失去”平衡“,极端情况下,二叉查找树会退化成线性的链表,导致插入和查找的复杂度下降到 O(n),所以,这也是平衡二叉树设计的初衷。
插入或删除节点后,通过左旋右旋来恢复平衡。
ref:二叉查找树与平衡二叉树
满⼆叉树——⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满⼆叉树。 (国内外定义不同,此为国内定义)
完全⼆叉树——二叉树除了最后一层,都是满的,并且最后一层的叶⼦结点都是从左到右依次排布,这就是完全⼆叉树。
堆 是具有以下性质的完全⼆叉树:每个结点的值都⼤于或等于其左右孩⼦结点的值,称为⼤顶堆;或者每个结点的值都⼩于或等于其左右孩⼦结点的值,称为⼩顶堆。用数组实现。(见另一篇堆详解)
红黑树
由于普通的二叉查找树会容易失去”平衡“,极端情况下,二叉查找树会退化成线性的链表,导致插入和查找的复杂度下降到 O(n)。红黑树可解决此问题。
红⿊树是自平衡的二叉查找树,还有以下特点:
1、每个节点不是红色就是黑色的;
2、根节点总是黑色的;
3、所有的叶节点都是是黑色的(红黑树的叶子节点都是空节点(NIL或者NULL));
4、如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
5、从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
红⿊树的应⽤:TreeMap、TreeSet以及JDK1.8的HashMap底层都⽤到了红⿊树。
优势?红黑树的特性保证了自平衡,使得插入、删除、查找的最坏时间负责度为O(logn)。
比较AVL树和红黑树
通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树)。
相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。
原文链接:https://blog.csdn.net/u010899985/article/details/80981053
什么情况下变色?
什么情况下旋转?
当插入或者删除节点的时候,红黑树的特性可能被打破,此时需要调整。调整有变色和旋转两种方式,旋转包括左旋转和右旋转。
情形和变化可复杂了。
B树、B+树、B*树
数据库索引
LSM 树
前序遍历
递归法(很简单,效率低)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
preorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
}
迭代法(需要stack)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(root!=null || !stack.isEmpty()){
while(root != null){
result.add(root.val);
stack.push(root);
root= root.left;
}
root = stack.pop().right;
}
return result;
}
}
中序遍历
递归法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
}
迭代法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val); //
root = root.right;
}
return res;
}
}
后序遍历
递归法
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
inorder(root.right, res);
res.add(root.val);
}
}
迭代法 (绕晕了)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left; //存到最左
}
root = stack.pop();
if (root.right == null || root.right == prev) {
res.add(root.val);
prev = root;
root = null;
} else {
stack.push(root); //
root = root.right;
}
}
return res;
}
}
层序遍历
使用队列实现。很好理解:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/die-dai-di-gui-duo-tu-yan-shi-102er-cha-shu-de-cen/
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
// 先将根节点放入队列
queue.add(root);
while (queue.size() > 0) {
int size = queue.size();
List<Integer> temp = new ArrayList<>();
// 遍历队列,把当前层的元素从队列取出来,将下一层放入队列
for (int i = 0; i < size; i++) {
// 取出队列元素,放入集合
TreeNode current = queue.poll();
temp.add(current.val);
if (current.left != null) {
// 将当前节点的左儿子放入队列
queue.add(current.left);
}
if (current.right != null) {
// 将当前节点的右儿子放入队列
queue.add(current.right);
}
}
result.add(temp);
}
return result;//以类似二维数组的形式
}
解决树的问题
- 递归法
当遇到树问题时,请先思考一下两个问题:
你能确定一些参数,从该节点自身解决出发寻找答案吗?
你可以使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数吗?
如果答案都是肯定的,那么请尝试使用 “自顶向下” 的递归来解决此问题。
或者你可以这样思考:对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出该节点的答案吗? 如果答案是肯定的,那么 “自底向上” 的递归可能是一个不错的解决方法。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/data-structure-binary-tree/xefb4e/
递归:
考虑终止条件
考虑传递的参数
- 自顶向下的递归 (从根到叶)
类似于前序遍历
例如:求解二叉树的深度问题中,节点深度为1,子节点深度为父节点深度+1。
private int answer;
private void maximum_depth(TreeNode root, int depth) {
if (root == null) {
return; //到叶节点终止
}
if (root.left == null && root.right == null) {
answer = Math.max(answer, depth); //凡到叶节点更新answer
}
maximum_depth(root.left, depth + 1);
maximum_depth(root.right, depth + 1);
}
2.自底向上的递归(从叶到根)
类似于后序遍历
例如:求解二叉树深度问题中,父节点深度为左右子树深度的最大值+1
public int maximum_depth(TreeNode root) {
if (root == null) {
return 0; // return 0 for null node
}
int left_depth = maximum_depth(root.left);
int right_depth = maximum_depth(root.right);
return Math.max(left_depth, right_depth) + 1; // return depth of the subtree rooted at root
}
- 迭代法
常考虑层序遍历。
经典例题
- 例子:判断二叉树是否对称
参考:https://leetcode-cn.com/problems/symmetric-tree/solution/dong-hua-yan-shi-101-dui-cheng-er-cha-shu-by-user7/
递归法
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null){
return true;
}
return dfs(root.left, root.right);
}
public static boolean dfs(TreeNode left, TreeNode right){
//终止条件:三种情况
if(left == null && right == null)return true;
if(left == null || right == null) return false;
if (left.val != right.val) return false;
//递归调用
return dfs(left.left, right.right) && dfs(left.right, right.left);
}
}
迭代法:(用队列)
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null || (root.left==null && root.right==null)) {
return true;
}
//用队列保存节点,相当于逐层比较
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
//将根节点的左右孩子放到队列中
queue.add(root.left);
queue.add(root.right);
while(queue.size()>0) {
//从队列中取出两个节点,再比较这两个节点
TreeNode left = queue.removeFirst();
TreeNode right = queue.removeFirst();
//如果两个节点都为空就继续循环,比较同一层的后续节点
if(left==null && right==null) {
continue;
}
//有一个为空,则不对称,返回false
if(left==null || right==null) {
return false;
}
//两值不相等,则不对称,返回false
if(left.val!=right.val) {
return false;
}
//将左节点的左孩子, 右节点的右孩子放入队列
queue.add(left.left);
queue.add(right.right);
//将左节点的右孩子,右节点的左孩子放入队列
queue.add(left.right);
queue.add(right.left);
}
return true;
}
}
- 路经总和
给你二叉树的根节点 root 和一个表示目标和的整数 sum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和sum 。
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
return dfs(root, 0, sum);
}
public boolean dfs(TreeNode root, int plus, int sum){
if(root == null) return false;
plus += root.val;
if(plus == sum && root.left == null && root.right == null){
return true;
}
return dfs(root.left,plus,sum) || dfs(root.right,plus,sum);
}
}
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum == root.val;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/path-sum/solution/lu-jing-zong-he-by-leetcode-solution/
- 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
class Solution {
//思路很简单。遍历A,找到和B的root相等的Node, 比较是否一致
//注意递归的三个终止情况
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A== null || B == null) return false;
return isSame(A, B) || isSubStructure(A.left,B)||isSubStructure(A.right, B);
}
boolean isSame(TreeNode A, TreeNode B){
if (B == null ) return true; //越过B叶节点
if (A == null || A.val != B.val) return false; //越过A或者值不等
return isSame(A.left, B.left) && isSame(A.right, B.right);
}
}