【恋上数据结构与算法 第一季】二叉树 BinaryTree


持续学习&持续更新中…

学习态度:脚踏实地


树形结构

在这里插入图片描述

在这里插入图片描述

以文件夹的分类为例,如果我们将各种不同类型的文件按照其类型分别放在不同的文件夹中,可以大大提高我们寻找文件的速度。

树形结构无论是考试还是面试都很重要。

以后的课程都离不开树形结构。

树的基本概念01

在这里插入图片描述

节点
  • 节点:图示中的每一个圆圈(元素)都是节点。
  • 根节点:图示中的圆圈1就是根节点,一棵树最多只有一个根节点
  • 父节点:圆圈1就是圆圈2、3、4、5、6的父节点;圆圈2就是圆圈21和圆圈22的父节点。
  • 子节点:圆圈2、3、4、5、6就是圆圈1的子节点;圆圈21和圆圈22就是圆圈2的子节点。
  • 兄弟节点:同一个节点下的所有圆圈(元素)都是兄弟节点,例如圆圈2、3、4、5、6都是兄弟节点。也就是说,兄弟节点拥有相同的父节点。
子树
图示子树:

在这里插入图片描述

左右子树:

在这里插入图片描述
上图中,红笔圈出来的部分就分别是圆圈2的左右子树。

树的基本概念02

在这里插入图片描述

层数

在这里插入图片描述

节点的深度

圆圈31的深度是3;

在这里插入图片描述

圆圈2的深度是2;
在这里插入图片描述

节点的高度

在这里插入图片描述

有序树、无序树、森林

该课程不学习森林相关知识

在这里插入图片描述

二叉树的基本概念

在这里插入图片描述

二叉树是有序树
  • 这里的有序是指区分左右子树:左子树和右子树不是同一颗树。

  • 二叉树的特点:二叉树是(严格)有序的;二叉树是(严格)区分左右子树的。

二叉树的几种形态

在这里插入图片描述

二叉树的性质

在这里插入图片描述

二叉树的性质—演算

在这里插入图片描述

声明:

  • 令二叉树的边数为T
  • 节点总数为n
  • 度为0(叶子结点)的节点个数为n0,度为1的节点个数为n1,度为2的节点个数为n2

边数的计算方式一:

  • 节点的边数和节点的度数相同:

    • 也就是说:
    • 如果某个节点的度为2那么这个节点就有两条边
    • 如果某个节点的度为1那么这个节点就有一条边
    • 如果某个节点的度为0(叶子结点)那么这个节点就没有边
  • 得到:T = n1 + 2 * n2

边数的计算方式二:

  • 如果反过来思考一下节点的边数,会有新的发现:除了根节点以外的每一个节点,其头顶都会有一条边。

  • 得到:T = n - 1 = n0 + n1 + n2 - 1

那么可以有如下演算:

  • n1 + 2 * n2 = n0 + n1 + n2 - 1
  • 2 * n2 = n0 + n2 - 1
  • n2 = n0 - 1

真二叉树

在这里插入图片描述

满二叉树

在这里插入图片描述

在这里插入图片描述

完全二叉树

在这里插入图片描述

在这里插入图片描述

节点从上往下、从左到右排布的二叉树就是完全二叉树。如果排满了,就是满二叉树。

如果对完全二叉树从上往下、从左到右,按照顺序开始编号的话,这个完全二叉树的编号都会和它对应的满二叉树的编号完全一致。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

完全二叉树的性质01

在这里插入图片描述

n和h关系的推导

下面的公式是如何得出的?

  • 总结点数量:n

  • 完全二叉树高度:h

    h - 1 <= log2n < h
    
    h = floor(log2n) + 1
    

分析:

  • 首先,h肯定是一个整数(树的高度)。n是总结点数量。

  • log2n求出来不一定是一个整数,有可能是一个浮点数。

当log2n求出来是浮点数的时候
  • 假设当log2n求出来是4.8:
    在这里插入图片描述

  • 要满足h - 1 <= log2n < h,那么此时,h求出来一定是5;

  • 假设当log2n求出来是7.4的时候,此时h求出来一定是8。

  • 由此可以推出:h = log2n向上取整或者h = log2n向下取整 + 1

当log2n求出来是整数的时候
  • 假设当log2n求出来是5:
    在这里插入图片描述

  • 要满足h - 1 <= log2n < h,那么此时,h求出来一定是6;

  • 假设当log2n求出来是7的时候,此时h求出来一定是8。

  • 由此可以推出:h = log2n + 1

n和h的关系推导总结
  • 上述推导出了三个公式:

    1. h = log2n向上取整 (log2n为浮点数)
    2. h = log2n向下取整 + 1 (log2n为浮点数)
    3. h = log2n + 1(log2n为整数)
  • log2n为整数时:h = log2n + 1

  • 而log2n为浮点数时,有两个公式,很明显,我们应该使用公式2,因为公式2和公式3(log2n为整数时)都有共同的+1操作。

  • 又由于(在Java、C中)整数向下取整还是该整数,所以我们可以最终使用公式2作为n与h的关系公式。

完全二叉树的性质02

对节点从0开始编号(常用)

在这里插入图片描述

对节点从1开始编号

在这里插入图片描述

完全二叉树—面试题

面试题:求完全二叉树叶子结点的个数

在这里插入图片描述

在这里插入图片描述

推导n0的总结公式:

非叶子结点的推导过程和n0的推导过程类似,这里只推导一下n0总结的公式。

在这里插入图片描述

向上取整和向下取整一般来说是可以互相切换的,但是编程中默认就是向下取整,因此一般都使用向下取整。

在这里插入图片描述

二叉树的遍历

前序遍历:先遍历root,后遍历left、right。(root、left、right)

中序遍历:先遍历left,中间遍历root,最后遍历right。(left、root、right)

后序遍历:先遍历left,然后遍历right,最后遍历root。(left、right、root)

在这里插入图片描述

前序遍历

在这里插入图片描述

中序遍历

在这里插入图片描述

后序遍历

在这里插入图片描述

层序遍历

在这里插入图片描述

层序遍历代码:

    // 层序遍历
    public void leverOrderTraversal(BinarySearchTree.Visitor<E> visitor) {
        traversalCheck(visitor);
        final java.util.Queue<BinarySearchTree.Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        BinarySearchTree.Node<E> node;
        while (!queue.isEmpty()) {
            node = queue.poll();
            visitor.visit(node.element);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        System.out.println();
    }

前中后序遍历—递归实现

前序遍历
// 前序遍历
    public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        preorderTraversal(root, visitor);
        System.out.println();
    }

    private void preorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
        if (node == null || visitor == null) return;

        visitor.visit(node.element);

        preorderTraversal(node.left, visitor);
        preorderTraversal(node.right, visitor);
    }
中序遍历
 // 中序遍历
    public void inorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        inorderTraversal(root, visitor);
        System.out.println();
    }

    private void inorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
        if (node == null) return;

        inorderTraversal(node.left, visitor);

        visitor.visit(node.element);

        inorderTraversal(node.right, visitor);
    }
后序遍历
  // 后序遍历
    public void postorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        postorderTraversal(root, visitor);
        System.out.println();
    }

    private void postorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
        if (node == null) return;

        postorderTraversal(node.left, visitor);
        postorderTraversal(node.right, visitor);

        visitor.visit(node.element);
    }

前中后序遍历—非递归实现

前序遍历

方法一:

在这里插入图片描述

// 非递归前序遍历 (方法一实现)
    public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        if (visitor == null) return;
        Stack<Node<E>> stack = new LinkedListStack<>();
        Node<E> node = root;
        while (true) {
            if (node != null) {
                visitor.visit(node.element);
                // 如果有右子节点,就将右子节点入栈
                if (node.right != null) stack.push(node.right);
                node = node.left; // 一路往左走
            } else {
                // node为null时,准备访问先前节点的右子节点
                if (!stack.isEmpty()) {
                    // 让拿出的节点继续执行 node != null 时的操作
                    node = stack.pop();
                } else {
                    return;
                }
            }
        }
    }

方法二:

在这里插入图片描述

    // 非递归前序遍历 (方法二实现)
    public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        if (visitor == null || root == null) return;
        Stack<Node<E>> stack = new LinkedListStack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node<E> node = stack.pop();
            visitor.visit(node.element);
            if (node.right != null) stack.push(node.right);
            if (node.left != null) stack.push(node.left);
        }
    }
中序遍历

在这里插入图片描述

// 非递归中序遍历
    public void inorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        if (null == visitor) return;
        Stack<Node<E>> stack = new LinkedListStack<>();
        Node<E> node = root;
        while (true) {
            if (node != null) {
                stack.push(node);
                node = node.left;
            } else {
                if (!stack.isEmpty()) {
                    node = stack.pop();
                    visitor.visit(node.element);
                    node = node.right;
                } else {
                    return;
                }
            }
        }
    }
后序遍历

在这里插入图片描述

// 非递归后序遍历
    public void postorderTraversal(BinarySearchTree.Visitor<E> visitor) {
        if (null == visitor || root == null) return;
        Node<E> prev = null;
        Stack<Node<E>> stack = new LinkedListStack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node<E> node = stack.top();
            if (node.isLeaf() || (prev != null && prev.parent == node)) {
                Node<E> leaf = stack.pop();
                visitor.visit(leaf.element);
                prev = leaf;
            } else {
                if (node.right != null) stack.push(node.right);
                if (node.left != null) stack.push(node.left);
            }
        }
    }

前驱结点(predecessor)

在这里插入图片描述

// 寻找前驱节点
    protected Node<E> predecessor(Node<E> node) {
        if (node == null) return null;

        Node<E> p = node.left;
        if (p != null) {
            while (p.right != null) {
                p = p.right;
            }
            return p;
        }

        while (node.parent != null && node.parent.right != node) {
            node = node.parent;
        }
        return node.parent;
    }

后继节点(successor)

在这里插入图片描述

// 后继节点
    protected Node<E> successor(Node<E> node) {
        if (node == null) return null;

        Node<E> s = node.right;
        if (s != null) {
            while (s.left != null) {
                s = s.left;
            }
            return s;
        }

        while (node.parent != null && node != node.parent.left) {
            node = node.parent;
        }
        return node.parent;
    }

遍历的应用

在这里插入图片描述

根据遍历结果重构二叉树

在这里插入图片描述

在这里插入图片描述

图示:前序+中序重构二叉树(后序+中序同理):

在这里插入图片描述

计算二叉树的高度

非递归实现:


    public int height() {
        if (root == null) return 0;

        final Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        int height = 0;
        int levelSize = 1;

        while (!queue.isEmpty()) {

            Node<E> 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;

    }

递归实现:

    递归实现高度计算
    public int height() {
        return height(root);
    }

    private int height(Node<E> node) {
        if (node == null) return 0;

        return Math.max(height(node.left), height(node.right)) + 1;
    }

判断一棵树是否是完全二叉树

在这里插入图片描述

    public boolean isComplete() {
        if (root == null) return true;

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

        while (!queue.isEmpty()) {
            Node<E> node = queue.poll();

            if (leaf) { // 要求后面的都是叶子结点
                if (!node.isLeaf()) return false;
                continue;
            }

            if (node.hasTwoChildren()) {
                queue.offer(node.left);
                queue.offer(node.right);
            } else if (node.left == null && node.right != null) {
                return false;
            } else {
                // left != null right == null
                // left == null right == null
                leaf = true;
                if (node.left != null) queue.offer(node.left);
            }
        }

        return true;
    }

国外二叉树的叫法

在这里插入图片描述

向上取整、向下取整

向下取整就是指省略小数部分的意思。(只取整数部分)

向上取整就是指取大于该小数的最小整数。(整数部分 + 1)

在这里插入图片描述

参考

小码哥李明杰老师课程: 恋上数据结构与算法 第一季.

小码哥李明杰老师博客: M了个J.


本文完,感谢您的关注支持!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值