image.png

二叉搜索树(Binary Search Tree)

image.png

  • 二叉搜索树是二叉树的一种,英文简称BST,又被称为二叉查找树、二叉排序树
  • 任意一个节点的值都大于其左子树所有节点的值
  • 任意一个节点的值都小于其右子树所有节点的值
  • 它的左右子树分别都是一棵二叉搜索树
  • 二叉搜索树大大提高了搜索数据的效率
  • 二叉搜索树存储的元素必须具备可比较性,比如 int、double
  • 自定义的数据类型,需要指定比较方式
  • 二叉搜索树存储的元素不能是null类型

二叉搜索树的接口设计

  • int size();//元素的数量
  • boolean isEmpty();//是否是空的
  • void clear();//清空所有元素
  • void add(E element);//添加元素
  • void remove(E element);//删除元素
  • boolean contains(E element);//是否包含指定元素

    二叉搜索树的成员变量设计

    1. public class BinarySearchTree<E> {
    2. private Node<E> root;
    3. private int size;
    4. private Comparator<E> comparator;
    5. public BinarySearchTree(Comparator<E> comparator) {
    6. this.comparator = comparator;
    7. }
    8. public BinarySearchTree() {
    9. }
    10. /**
    11. * if(e1>e2) return 1,
    12. * if(e1<e2) return -1,
    13. * if(e1==e2) return 0
    14. *
    15. * @param e1
    16. * @param e2
    17. * @return
    18. */
    19. private int compare(E e1, E e2) {
    20. if (comparator != null) {
    21. return this.comparator.compare(e1, e2);
    22. } else {
    23. return ((Comparable<E>) e1).compareTo(e2);
    24. }
    25. }
    26. private static class Node<E> {
    27. E element;
    28. Node<E> parent;
    29. Node<E> left;
    30. Node<E> right;
    31. public Node(E element, Node<E> parent) {
    32. this.element = element;
    33. this.parent = parent;
    34. }
    35. }
    36. }

    元素的比较方案设计

  1. 允许外界传入一个Comparator接口,自定义比较方案
  2. 如果没有传入Comparator接口,强制元素实现Comparable接口

神奇的网站

添加元素

二叉树的遍历

  • 遍历时数据结构的常见操作,就是把所有元素访问一遍
  • 线性数据结构的遍历比较简单
    • 正序遍历
    • 逆序遍历
  • 根据访问节点的顺序不同,二叉树的遍历有以下几种:
    • 前序遍历(Preorder Traversal)根左右
    • 中序遍历(Inorder Traversal)左根右
    • 后序遍历(Postorder Traversal)左右根
    • 层序遍历(Level Order Traversal)从上到下、从左到右依次访问每个节点

前序遍历

image.png

递归方式

  1. /**
  2. * 前序遍历,递归方式
  3. */
  4. public void preorderTraversal() {
  5. preorderTraversal(root);
  6. }
  7. public void preorderTraversal(Node<E> node) {
  8. if (node == null) {
  9. return;
  10. }
  11. //先访问这个节点
  12. System.out.println(node.element);
  13. //然后访问这个节点的左子树
  14. preorderTraversal(node.left);
  15. //再访问这个节点的右子树
  16. preorderTraversal(node.right);
  17. }

非递归方式(使用栈)

image.png
image.png

  1. /**
  2. * 前序遍历、非递归方式、(使用栈第一种方式)
  3. */
  4. public void preorderTraversal2() {
  5. Node<E> node = root;
  6. Stack<Node> stack = new Stack<>();
  7. if (node == null) {
  8. return;
  9. }
  10. stack.push(node);
  11. while (!stack.isEmpty()) {
  12. node = stack.pop();
  13. System.out.println(node.element);
  14. if (node.right != null) {
  15. stack.push(node.right);
  16. }
  17. if (node.left != null) {
  18. stack.push(node.left);
  19. }
  20. }
  21. }
  22. /**
  23. * 前序遍历、非递归方式、(使用栈第二种)
  24. */
  25. public void preorderTraversal3() {
  26. Node<E> node = root;
  27. Stack<Node> stack = new Stack<>();
  28. while (node != null) {
  29. System.out.println(node.element);
  30. if (node.right != null) {
  31. stack.push(node.right);
  32. }
  33. node = node.left;
  34. if (node == null && !stack.isEmpty()) {
  35. node = stack.pop();
  36. }
  37. }
  38. }

中序遍历

image.png

递归方式

  1. /**
  2. * 中序遍历,左 根 右,递归
  3. */
  4. public void inorderTraversal() {
  5. inorderTraversal(root);
  6. }
  7. private void inorderTraversal(Node<E> node) {
  8. if (node == null) {
  9. return;
  10. }
  11. //左
  12. inorderTraversal(node.left);
  13. // 根
  14. System.out.println(node.element);
  15. //右
  16. inorderTraversal(node.right);
  17. }

非递归方式(使用栈)

image.png

  1. /**
  2. * 中序遍历,左 根 右,非递归,使用栈,先进后出,后进先出 右 中 左
  3. */
  4. public void inorderTraversal2() {
  5. if (root == null) {
  6. return;
  7. }
  8. Node<E> node = root;
  9. Stack<Node> stack = new Stack<>();
  10. while (!stack.isEmpty() || node != null) {
  11. if (node != null) {
  12. stack.push(node);
  13. node = node.left;
  14. } else {
  15. Node pop = stack.pop();
  16. System.out.println(pop.element);
  17. node = pop.right;
  18. }
  19. }
  20. }

后序遍历

递归方式

  1. /**
  2. * 后序遍历,左右根 ,递归
  3. */
  4. public void postOrderTraversal() {
  5. postOrderTraversal(root);
  6. }
  7. private void postOrderTraversal(Node<E> node) {
  8. if (node == null) {
  9. return;
  10. }
  11. postOrderTraversal(node.left);
  12. postOrderTraversal(node.right);
  13. System.out.println(node.element);
  14. }

非递归方式(使用栈)

image.png

  1. /**
  2. * 后序遍历,左右根,使用栈
  3. */
  4. public void postOrderTraversal1() {
  5. if (root == null) {
  6. return;
  7. }
  8. Node pre = null;
  9. Node<E> node = root;
  10. Stack<Node> stack = new Stack<>();
  11. stack.push(node);
  12. while (!stack.isEmpty()) {
  13. Node cur = stack.peek();
  14. if (cur.right == null && cur.left == null || (pre != null && (pre == cur.left || pre == cur.right))) {
  15. stack.pop();
  16. System.out.println(cur.element);
  17. pre = cur;
  18. } else {
  19. if (cur.right != null) {
  20. stack.push(cur.right);
  21. }
  22. if (cur.left != null) {
  23. stack.push(cur.left);
  24. }
  25. }
  26. }
  27. }

层序遍历(使用队列)

image.png

  1. /**
  2. * 层序遍历,使用队列
  3. */
  4. public void levelOrderTraversal() {
  5. if (root == null) {
  6. return;
  7. }
  8. Node<E> node = root;
  9. Queue<Node> queue = new LinkedList<>();
  10. queue.offer(node);
  11. while (!queue.isEmpty()) {
  12. node = queue.poll();
  13. System.out.println(node.element);
  14. if (node.left != null) {
  15. queue.offer(node.left);
  16. }
  17. if (node.right != null) {
  18. queue.offer(node.right);
  19. }
  20. }
  21. }

二叉树前驱节点

  • 前驱节点:中序遍历时的前一个节点
  • 如果是二叉搜索树,前驱节点就是比它小的前一个节点

image.png

  1. /**
  2. * 前驱节点
  3. *
  4. * @param node
  5. * @return
  6. */
  7. private Node<E> predecessor(Node<E> node) {
  8. if (node == null) {
  9. return null;
  10. }
  11. if (node.left != null) {
  12. //这种情况,前驱在左子树的最右子树
  13. Node<E> pre = node.left;
  14. while (pre.right != null) {
  15. pre = pre.right;
  16. }
  17. return pre;
  18. } else if (node.parent != null) {
  19. while (node != null && node.parent != null) {
  20. if (node == node.parent.right) {
  21. return node.parent;
  22. }
  23. node = node.parent;
  24. }
  25. return null;
  26. } else {
  27. // node.left ==null && node.parent == null
  28. return null;
  29. }
  30. }

二叉树后继节点

image.png

  1. /**
  2. * 后继节点
  3. *
  4. * @param node
  5. * @return
  6. */
  7. private Node<E> successor(Node<E> node) {
  8. if (node == null) {
  9. return null;
  10. }
  11. if (node.right != null) {
  12. //这种情况,后继节点在右子树的最左子树
  13. Node<E> pre = node.right;
  14. while (pre.left != null) {
  15. pre = pre.left;
  16. }
  17. return pre;
  18. } else if (node.parent != null) {
  19. while (node != null && node.parent != null) {
  20. if (node == node.parent.left) {
  21. return node.parent;
  22. }
  23. node = node.parent;
  24. }
  25. return null;
  26. } else {
  27. // node.right ==null && node.parent == null
  28. return null;
  29. }
  30. }

练习-反转二叉树

  1. /**
  2. * @description: 反转二叉树
  3. * https://leetcode-cn.com/problems/invert-binary-tree/
  4. * @author: huangguoqiang
  5. * @create: 2021-07-06 17:37
  6. **/
  7. public class InvertBinaryTree226 {
  8. public TreeNode invertTree(TreeNode root) {
  9. if (root == null) {
  10. return root;
  11. }
  12. Queue<TreeNode> queue = new LinkedList<>();
  13. queue.offer(root);
  14. TreeNode temp = null;
  15. while (!queue.isEmpty()) {
  16. TreeNode node = queue.poll();
  17. if (node.left != null) {
  18. queue.offer(node.left);
  19. }
  20. if (node.right != null) {
  21. queue.offer(node.right);
  22. }
  23. temp = node.left;
  24. node.left = node.right;
  25. node.right = temp;
  26. }
  27. return root;
  28. }
  29. private class TreeNode {
  30. int val;
  31. TreeNode left;
  32. TreeNode right;
  33. TreeNode() { }
  34. TreeNode(int val) { this.val = val; }
  35. TreeNode(int val, TreeNode left, TreeNode right) {
  36. this.val = val;
  37. this.left = left;
  38. this.right = right;
  39. }
  40. }
  41. }

练习-计算二叉树的高度

 /**
     * 计算二叉树的高度,非递归,层序遍历法
     *
     * @return
     */
    public int height1() {

        if (root == null) {
            return 0;
        }
        int height = 0;
        int levelSize = 1;
        Node<E> node = root;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(node);
        while (!queue.isEmpty()) {
            node = queue.poll();
            levelSize--;
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (levelSize == 0) {
                levelSize = queue.size();
                height++;
            }
        }
        return height;
    }

    /**
     * 计算二叉树的高度,递归
     *
     * @return
     */
    public int height() {
        return height(root);
    }

    private int height(Node<E> node) {
        if (node == null) {
            return 0;
        }
        return 1 + Math.max(height(node.left), height(node.right));
    }

练习-是否是完全二叉树

  /**
     * 是否是完全二叉树
     *
     * @return
     */
    public boolean isCompleteBinaryTree2() {
        if (root == null) {
            return false;
        }

        boolean isLeaf = false;
        Node<E> node = root;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(node);

        while (!queue.isEmpty()) {
            node = queue.poll();

            if (isLeaf && !(node.left == null && node.right == null)) {
                return false;
            }

            if (node.left != null) {
                queue.offer(node.left);
            } else if (node.right != null) {
                // node.left == null && node.right != null ,这种一定不是
                return false;
            }

            if (node.right != null) {
                queue.offer(node.right);
            } else {
                // (node.left != null && node.right == null) || (node.left == null && node.right == null)
                //这两种情况下,剩下的节点必须是叶子节点 才能是完全二叉树
                isLeaf = true;
            }
        }
        return true;
    }

    /**
     * 是否是完全二叉树
     *
     * @return
     */
    public boolean isCompleteBinaryTree() {
        if (root == null) {
            return false;
        }
        Node<E> node = root;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(node);

        while (!queue.isEmpty()) {
            node = queue.poll();

            if (node.left != null && node.right != null) {
                queue.offer(node.left);
                queue.offer(node.right);
            } else if (node.left == null && node.right != null) {
                return false;
            } else if ((node.left != null && node.right == null)
                    || (node.left == null && node.right == null)) {
                //这两种情况下,剩下的节点必须都是 叶子节点才是 完全二叉树
                if (node.left != null) {
                    queue.offer(node.left);
                }
                while (!queue.isEmpty()) {
                    node = queue.poll();
                    if (node.left != null || node.right != null) {
                        return false;
                    }
                }
                return true;

            }
        }
        return false;
    }