二叉树常考算法整理
希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路。也欢迎大家来给我的Github的Leetcode算法项目点star呀~~
前言
二叉树即子节点数目不超过两个的树,基于这个基本特性,许多算法都围绕这种树本身或其变体而展开。
二叉树的类型
此部分参考一句话弄懂常见二叉树类型
根据树的构成特性,树存在一些比较常见的类型,我们先来分别介绍一下:
- 满二叉树
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。
- 完全二叉树
一棵二叉树至多只有最下面的一层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
- 平衡二叉树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
- 二叉搜索/查找/排序树
它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
- 红黑树
属于AVL树(平衡二叉查找树)的一种,对树的高度的要求不如AVL树那么严格(不是严格控制左、右子树高度或节点数之差小于等于1),使得其插入结点的效率相对更高。
算法分类
遍历(Traversal)问题
先序、中序与后序遍历
对任一二叉树结点,若以其本身为根结点(Root Node),它的左右子节点(left/right child),那么遍历指的就是以某种固定顺序将整个二叉树的所有结点都过一遍。
按照根节点与子节点先后遍历关系,一共有以下三种常见的遍历顺序:
- 先序遍历(Preorder)
根结点-左子结点-右子结点
- 中序遍历(Inorder)
左子结点-根结点-右子结点
- 后序遍历(Postorder)
左子结点-右子结点-根结点
遍历,根据实现思路,可以分为递归(Recursive)和非递归两种方式。递归相对来说更为直观易理解,但是由于递归需要不断地多重地进行函数自身调用,会需要消耗更多的栈空间。而非递归方式就是用一般的循环(Iteration)来进行。(而其实由于二叉树的结构特性,许多相关的题目的解题思路都会存在递归和非递归两种方式,只要多做几道,就会自然体味到其中的规律。)
先给出递归实现的三种遍历:
/*
* 递归的主要思想就是:
* 由于是重复调用自身,故根据遍历顺序调整根节点与左右子节点的处理语句之间的相对顺序即可。
*/
//递归先序遍历
public static void recurivePreorder(TreeNode root){
if(root==null)
return;
System.out.println(root.val);
if(root.left!=null)
recurivePreorder(root.left);
if(root.right!=null)
recurivePreorder(root.right);
}
//递归中序遍历
public static void recursiveInorder(TreeNode root){
if(root==null)
return;
if(root.left!=null)
recursiveInorder(root.left);
System.out.println(root.val);
if(root.right!=null)
recursiveInorder(root.right);
}
//递归后序遍历
public static void recursivePostorder(TreeNode root){
if(root==null)
return;
if(root.left!=null)
recursivePostorder(root.left);
if(root.right!=null)
recursivePostorder(root.right);
System.out.println(root.val);
}
非递归实现三种遍历:
/*
* 非递归的主要思想是:
* 利用栈stack存下路过的结点,依照遍历顺序打印结点,利用stack回溯。
*/
//非递归先序遍历
public static void nonRecurPreorder(TreeNode root){
ArrayDeque<TreeNode> stack=new ArrayDeque<TreeNode>();
while(root!=null || stack.size()!=0){
if (root != null) {
System.out.println(root.val); //访问结点并入栈
stack.push(root);
root = root.left; //访问左子树
} else {
root = stack.pop(); //回溯至父亲结点
root = root.right; //访问右子树
}
}
}
//非递归中序遍历
public static void nonRecurInorder(TreeNode root){
ArrayDeque<TreeNode> stack=new ArrayDeque<TreeNode>();
while(root!=null || stack.size()!=0){
if(root!=null){
stack.push(root);
root=root.left; //访问左子树
}
else{
root=