1、理论基础

1.1 二叉树的种类

满二叉树

如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
image.png

完全二叉树

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
image.png

二叉搜索树

前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
image.png

平衡二叉树

又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
image.png

1.2 二叉树的存储方式

链式存储

image.png

  1. public class TreeNode {
  2. int val;
  3. TreeNode left;
  4. TreeNode right;
  5. TreeNode(){}
  6. TreeNode(int val){
  7. this.val = val;
  8. }
  9. TreeNode(int val, TreeNode left, TreeNode right){
  10. this.val = val;
  11. this.left = left;
  12. this.right = right;
  13. }
  14. }

数组存储

image.png
如果父节点的数组下标是 i,那么它的左孩子就是 i 2 + 1,右孩子就是 i 2 + 2。

2、二叉树的遍历

2.1 深度优先遍历

2.1.1 前序遍历

  • 递归

    • 确定递归函数的参数和返回值
    • 确定终止条件
    • 确定单层递归的逻辑
      1. void preorderTraversal(TreeNode root, List<Integer> list){
      2. if ( root == null )
      3. return ;
      4. list.add(root.val);
      5. preorderTraversal(root.left, list);
      6. preorderTraversal(root.right, list);
      7. }
  • 迭代

    • 迭代树时,要分清访问顺序,和处理顺序,例如中序遍历的时候访问到了根节点,但是不处理它(不加入结果),访问到左叶子结点才处理叶子结点
    • 前序遍历是中左右,处理顺序也是中左右,每次先处理的是中间节点,那么入栈顺序就应该是中右左,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子
    • 后序遍历是左右中,反转一下是中右左,现在只要把前序遍历的左右子结点入栈顺序改一下,就可以,得到的结果再反转回去即可
    • 中序遍历是左中右,访问顺序确实中左右,访问顺序和处理顺序不一致
      1. List<Integer> preorderTraversal(TreeNode root){
      2. List<Integer> result = new ArrayList<>();
      3. if (root == null)
      4. return result;
      5. ArrayDeque<TreeNode> stack = new ArrayDeque<>();
      6. stack.addLast(root);
      7. while ( !stack.isEmpty() ){
      8. TreeNode node = stack.pollLast();
      9. result.add(node.val);
      10. if (node.right != null){
      11. stack.addLast(node.right);
      12. }
      13. if (node.left != null){
      14. stack.addLast(node.left);
      15. }
      16. }
      17. return result;
      18. }

      2.1.2 中序遍历

      1. void inorderTraversal(TreeNode root, List<Integer> list){
      2. if ( root == null )
      3. return;
      4. inorderTraversal(root.left, list);
      5. list.add(root.val);
      6. inorderTraversal(root.right, list);
      7. }
      1. List<Integer> inorderTraversal(TreeNode root){
      2. List<Integer> result = new ArrayList<>();
      3. if (root == null)
      4. return res;
      5. ArrayDeque<TreeNode> stack = new ArrayDeque<>();
      6. TreeNode cur = root; //定义一个指针用来访问节点
      7. //这里不是立刻添加root结点到栈中
      8. //因为没有立刻添加,所以栈一开始可能是空的,得加个条件进循环
      9. while ( cur!=null || !stack.isEmpty()){ //当指针为空或者栈为空时结束循环
      10. if ( cur!=null ){ //没有访问到叶子结点
      11. stack.addLast(cur);
      12. cur = cur.left;
      13. }else{
      14. cur = stack.pollLast();//当cur为空时,左边访问到底了,弹出元素即为左叶子结点
      15. result.add(cur.val);
      16. cur = cur.right;//访问右子树
      17. }
      18. }
      19. return result;
      20. }

      2.1.3 后序遍历

      1. void postorderTraversal(TreeNode root, List<Integer> list){
      2. if ( root == null )
      3. return;
      4. postorderTraversal(root.left, list);
      5. postorderTraversal(root.right, list);
      6. list.add(root.val);
      7. }
      1. List<Integer> postorderTraversal(TreeNode root){
      2. List<Integer> result = new ArrayList<>();
      3. if (root == null)
      4. return result;
      5. ArrayDeque<TreeNode> stack = new ArrayDeque<>();
      6. stack.addLast(root);
      7. while (!stack.isEmpty()){
      8. TreeNode node = stack.pollLast();
      9. result.add(node.val);
      10. if (node.left!=null)
      11. stack.addLast(node.left);
      12. if (node.right!=null)
      13. stack.addLast(node.right);
      14. }
      15. Collections.reverse(result);
      16. return result;
      17. }

      2.2 广度优先遍历

      2.2.1 层次遍历

  • 递归

    1. public List<List<Integer>> resList = new ArrayList<>();
    2. void levelOrderTraversal(TreeNode root, Integer deep){
    3. if (root == null)
    4. return;
    5. deep++;
    6. if (resList.size() < deep){
    7. List<Integer> item = new ArrayList<>();
    8. resList.add(item);
    9. }
    10. resList.get(deep-1).add(root.val);
    11. levelOrderTraversal(root.left,deep);
    12. levelOrderTraversal(root.right,deep);
    13. }
  • 迭代

      public List<List<Integer>> resList = new ArrayList<>();
      void levelOrderTraversal(TreeNode root){
          if (root == null) 
              return;
          ArrayDeque<TreeNode> queue = new ArrayDeque<>();
          queue.addLast(root);
          while (!queue.isEmpty()){
              List<Integer> item = new ArrayList<>();
              int len = queue.size();
              while ( len > 0 ){ //这里不能直接用queue.size(),因为queue长度在不断变化
                  TreeNode node = queue.pollFirst();
                  item.add(node.val);
                  if (node.left!=null)
                      queue.addLast(node.left);
                  if (node.right!=null)
                      queue.addLast(node.right);
                  len--;
              }
              resList.add(item);
          }
      }
    

    3、二叉树的高度与深度

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。

  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:
image.png
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)