二叉树的每个节点最多只能包含两个子节点。在解题过程中二叉树主要有两种形式:满二叉树和完全二叉树。
满二叉树
一棵只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,的二叉树。
也可以说是深度为k,有2^k-1个节点的二叉树。
完全二叉树
除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
重点: 优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
优先级队列
在优先级队列中,每个元素都关联有一个优先级,每次出队的元素是队列中优先级最高的那个元素,而不是队首的元素。不满足先进先出的条件。
二叉搜索树
二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
常见的平衡二叉树包括AVL树、红黑树等。
红黑树
- 节点是红色或黑色。
- 根节点是黑色。
- 所有的叶子节点都是黑色。
- 每个红色节点必须有两个黑色的子节点。(不能出现两个连续的红色节点)
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
java中的TreeMap、TreeSet都是基于红黑树实现的容器类
94. 二叉树的中序遍历
- 步骤一:遍历左子树
- 步骤二:取根节点的值
- 步骤三:遍历右子树
//方法一:递归
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
inorder(root,list);
return list;
}
public void inorder(TreeNode root, List<Integer> list) {
if(root == null ){
return;
}
inorder(root.left,list);
list.add(root.val);
inorder(root.right,list);
}
- 时间复杂度:O(n),其中 n 为二叉树节点的个数
- 空间复杂度:O(n)。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。
方法一的递归函数我们也可以用迭代的方式实现,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来。
注意:返回的列表里存的是值,root.val;栈中存的是节点TreeNode。
//方法二:迭代
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stack = new LinkedList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.left;
}
root=stack.pop();
res.add(root.val);
root = root.right;
}
return res;
}
- 时间复杂度:O(n)。
- 空间复杂度:O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O 的级别。
144. 二叉树的前序遍历
- 步骤一:取根节点的值
- 步骤二:遍历左子树
- 步骤三:遍历右子树
//方法一:递归
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
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);
}
//方法二:迭代
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack =new LinkedList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
res.add(root.val);
stack.push(root);
root=root.left;
}
root = stack.pop();
root = root.right;
}
return res;
}
145. 二叉树的后序遍历
思路一:
在迭代法中,和中序遍历有些像。
开始的话,也是不停的往左子树走,然后直到为 null 。不同之处是,之前直接把节点 pop 并且加入到 list 中,然后直接转到右子树。
这里的话,需要判断一下当前根节点的右子树是否为空或者是否是从右子树回到的根节点。
- 记录上一个遍历的节点 last,用于判断当前节点的右子节点是否已经处理;如果当前节点的右节点和last相同,那就表明当前是从右节点过来的了。
- 处理完当前节点 node,置为空节点。避免下一次循环进入了 while(node) 导致当前节点的左子节点重复入栈。
//方法一:递归
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root,res);
return res;
}
public void postorder(TreeNode root, List<Integer> res){
if(root == null){
return;
}
postorder(root.left,res);
postorder(root.right,res);
res.add(root.val);
}
//方法二:迭代
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack =new LinkedList<>();
TreeNode last = null;
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
if(root.right != null && root.right != last){
stack.push(root);
root=root.right;
}else{
res.add(root.val);
last =root;
root = null;
}
}
return res;
}
思路二:
主要思想:
先遍历左子树,再遍历右子树,最后取根节点的值
步骤:(对主要思想里边的步骤逆序处理)
- 步骤一:取根节点的值,插入list最后边
- 步骤二:遍历右子树
- 步骤三:遍历左子树
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack =new LinkedList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);//把根节点放入栈中
res.add(0,root.val);//步骤一,在index=0处插入根结点的值
root = root.right;//步骤二,遍历右子树
}
root = stack.pop();
root=root.left;//步骤三,遍历左子树
}
return res;
}
思路三:
还可以在前序遍历的基础上修改进行一个反转。将其中的left、right分别变成right、left,中-->右-->左,反转后变成左右中
Collections.reverse(list);
102. 二叉树的层序遍历
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(root == null){
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> level=new ArrayList<>();
int levelSize = queue.size();
for(int i = 0;i<levelSize; i++){
root = queue.poll();
level.add(root.val);
if(root.left != null){
queue.offer(root.left);
}
if(root.right != null){
queue.offer(root.right);
}
}
res.add(level);
}
return res;
}
- 时间复杂度:O(n),每个点进队出队各一次。
- 空间复杂度: O(n),队列中元素的个数不超过 n 个。
104. 二叉树的最大深度
思路一:递归
根节点的最大深度等于左子树和右子树 最大深度+1
public int maxDepth(TreeNode root) {
if(root == null ){
return 0;
}
int leftDepth=maxDepth(root.left);
int rightDepth=maxDepth(root.right);
return Math.max(leftDepth,rightDepth)+1;
}
- 时间复杂度为 O(n)。
- 空间复杂度为 O(n),在递归过程中调用了额外的栈空间,栈的大小取决于二叉树的高度,二叉树最坏情况下的高度为 n。
思路二:树的层序遍历 / 广度优先搜索
public int maxDepth(TreeNode root) {
if(root == null ){
return 0;
}
Queue<TreeNode> queue =new LinkedList<>();
queue.offer(root);
int res =0;
while(!queue.isEmpty()){
int levelSize = queue.size();
for(int i=0; i< levelSize; i++){
root = queue.poll();
if(root.left != null){
queue.offer(root.left);
}
if(root.right != null){
queue.offer(root.right);
}
}
res+=1;
}
return res;
}
- 时间复杂度为 O(n)。
- 空间复杂度为 O(n)。