二叉树基础知识

我们在 MySQL 为什么用 B+ 树实现索引 提到了二叉树的相关知识,本文就来详细分析一下二叉树。

概述

维基百科定义

在计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2 的节点)的树结构。通常分支被称作 “左子树” 或 “右子树”。二叉树的分支具有左右次序,不能随意颠倒。

我的理解,一棵二叉树是 n 个有限节点的集合,该集合或者为空,或者是由一个根节点加上两棵互不相交的、分别称为左子树和右子树的二叉树组成。

特点:每个节点至多只有两棵子树(二叉树中不存在度大于2的节点)。

5 种基本形态

二叉树是递归定义的,其节点有左右子树之分,逻辑上二叉树有五种基本形态:

  1. 空二叉树——如图(a);
  2. 只有一个根节点的二叉树——如图(b);
  3. 只有左子树——如图(c);
  4. 只有右子树——如图(d);
  5. 既有左子树,又有右子树——如图(e)。

在这里插入图片描述

特殊的二叉树

满二叉树

除最后一层无任何子节点外,每一层上的所有节点都有两个子节点二叉树,这种二叉树就叫做满二叉树。

在这里插入图片描述

完全二叉树

一棵二叉树至多只有最下面的一层上的节点的度数可以小于 2,并且最下层上的节点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。

完全二叉树除去最后一层节点为满二叉树。

在这里插入图片描述

还有一些常用的比如:平衡二叉树、二叉搜索树、红黑树等等。

术语

  1. 节点:包含一个数据元素及若干指向子树分支的信息
  2. 节点的度:一个节点拥有子树的数目
  3. 叶子节点:也称终端节点,没有子树的节点或者度为零的节点
  4. 分支节点:也称非终端节点,度不为零的节点
  5. 树的度:树中所有节点的度的最大值
  6. 节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层
  7. 树的深度:也称树的高度,树中所有节点的层次最大值
  8. 有序树:如果树中各棵子树的次序都是有先后次序,称为有序树
  9. 无序树:反之,如果树中各棵子树的次序没有先后次序,则称该树为无序树
  10. 森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成 。

术语解释

节点

节点是数据结构中的基础,是构成复杂数据结构的基本组成单位。

节点 A 在图中表示为:

在这里插入图片描述

节点的度

一个节点拥有子树的数目。

在这里插入图片描述

叶子节点

也称终端节点,没有子树的节点或者度为零的节点。

在这里插入图片描述

图中 D 和 C 都是叶子节点。

分支节点

也称非终端节点,度不为零的节点。

在这里插入图片描述

上图中 A 和 B 都是分支节点。

树的度

树中所有节点的度的最大值。

在这里插入图片描述

上图中,树的度为 2。

节点的层次

从根节点开始,假设根节点为第 1 层,根节点的子节点为第 2 层,依此类推,如果某一个节点位于第 L 层,则其子节点位于第 L+1 层。

在这里插入图片描述

二叉树性质

性质1

在二叉树的第 i 层上至多有 2^(i-1) 个节点(i>=1)。

在这里插入图片描述

上图中:

  1. 第1层:1个:2^(1-1) = 2^0 = 1;
  2. 第2层:2个:2^(2-1) = 2^1 = 2;
  3. 第3层:4个:2^(3-1) = 2^2 = 4。

性质2

深度为 k 的二叉树最多有 2^k-1个节点(k>=1)。(这里注意是 2 的 k 次幂再减 1。)

  • 如果有一层,最多1 = 2^1 - 1个节点
  • 如果有两层,最多1+2 = 2^2 - 1个节点
  • 如果有三层,最多1+2+4 = 2^3 - 1个节点

通过数据归纳法的论证,可以得出如果有 k 层,节点数最多为2^k-1

性质3

若在任意一棵二叉树中,有 n0 个叶子节点,有 n2 个度为 2 的节点,则必有 n0 = n2 + 1

在这里插入图片描述

如图:

  • 度为 0 的节点是:C、D(2个)
  • 度为 1 的节点是:B(1个)
  • 度为 2 的节点是:A(1个)
  • 2 = 1+1

性质4

具有 n 个节点的完全二叉树深为 (log2n)+1。

由满二叉树的定义我们可以知道,深度为 k 的满二叉树的节点数一定是(2^k)-1,因为这是最多的节点个数。

那么对于 n = (2^k)-1 推到得到的满二叉树的深度为 k=log2(n+1)。比如节点数为 15 的满二叉树,深度为 4。

证明:设完全二叉树的深度为 h,则根据性质 2 和完全二叉树的定义有:

它的节点数一定小于等于同样深度的满二叉树的节点数2^h - 1,但一定多于2^(h-1)- 1

2^(h-1) - 1 < n <= 2^h - 12^(h-1) <= n < 2^h

取对数 h-1 < log2n <= h,又 h 是整数, 因此有 h = (log2n)+1。

性质5

若对一棵有 n 个节点的完全二叉树进行顺序编号(1 ≤ i ≤ n),那么,对于编号为i(i≥1)的节点:

  • 当 i=1 时,该节点为根,它无双亲节点 。
  • 当 i>1 时,该节点的双亲节点的编号为 i/2 。
  • 若 2i≤n,则有编号为 2i 的左节点,否则没有左节点 。
  • 若 2i+1≤n,则有编号为 2i+1 的右节点,否则没有右节点 。

常见的三种遍历

二叉树遍历分为三种:前序、中序、后序,中序遍历最为重要。为啥叫这个名字?是根据根节点的顺序命名的。

先看一个简单的例子,如下是一个正常的满节点:

在这里插入图片描述

  1. 前序顺序是ABC(根节点排最先,然后同级先左后右);
  2. 中序顺序是BAC(先左后根最后右);
  3. 后序顺序是BCA(先左子树,后右子树,再根节点)。

代码实现二叉树遍历

使用递归的方式,实现下图二叉树的前序、中序、后序遍历。
在这里插入图片描述

首先创建一棵树:

public class Node {  
    private int data;  
    private Node leftNode;  
    private Node rightNode;  
    public Node(int data, Node leftNode, Node rightNode){  
        this.data = data;  
        this.leftNode = leftNode;  
        this.rightNode = rightNode;  
    }  
    public int getData() {  
        return data;  
    }  
    public void setData(int data) {  
        this.data = data;  
    }  
    public Node getLeftNode() {  
        return leftNode;  
    }  
    public void setLeftNode(Node leftNode) {  
        this.leftNode = leftNode;  
    }  
    public Node getRightNode() {  
        return rightNode;  
    }  
    public void setRightNode(Node rightNode) {  
        this.rightNode = rightNode;  
    }  
}

递归实现遍历:

public class BinaryTree {
    /**
     * 注意必须逆序建立,先建立子节点,再逆序往上建立,因为非叶子节点会使用到下面的节点,而初始化是按顺序初始化的,不逆序建立会报错
     *
     * @return
     */
    public Node init() {
        Node J = new Node(8, null, null);
        Node H = new Node(4, null, null);
        Node G = new Node(2, null, null);
        Node F = new Node(7, null, J);
        Node E = new Node(5, H, null);
        Node D = new Node(1, null, G);
        Node C = new Node(9, F, null);
        Node B = new Node(3, D, E);
        Node A = new Node(6, B, C);
        //返回根节点
        return A;
    }

    public void printNode(Node node) {
        System.out.print(node.getData());
    }

    /**
     * 先序遍历
     *
     * @param root
     */
    public void theFirstTraversal(Node root) {
        printNode(root);
        if (root.getLeftNode() != null) {
            //使用递归进行遍历左孩子
            theFirstTraversal(root.getLeftNode());
        }
        if (root.getRightNode() != null) {
            //递归遍历右孩子
            theFirstTraversal(root.getRightNode());
        }
    }

    /**
     * 中序遍历
     *
     * @param root
     */
    public void theInOrderTraversal(Node root) {
        if (root.getLeftNode() != null) {
            theInOrderTraversal(root.getLeftNode());
        }
        printNode(root);
        if (root.getRightNode() != null) {
            theInOrderTraversal(root.getRightNode());
        }
    }

    /**
     * 后序遍历
     *
     * @param root
     */
    public void thePostOrderTraversal(Node root) {
        if (root.getLeftNode() != null) {
            thePostOrderTraversal(root.getLeftNode());
        }
        if (root.getRightNode() != null) {
            thePostOrderTraversal(root.getRightNode());
        }
        printNode(root);
    }

    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        Node root = tree.init();
        System.out.println("先序遍历");
        tree.theFirstTraversal(root);
        System.out.println("");
        System.out.println("中序遍历");
        tree.theInOrderTraversal(root);
        System.out.println("");
        System.out.println("后序遍历");
        tree.thePostOrderTraversal(root);
        System.out.println("");
    }
}

打印结果:

先序遍历
631254978
中序遍历
123456789
后序遍历
214538796

通过上述的介绍,详细对于二叉树有了初步的认识。

如果你还想看更多优质原创文章,欢迎关注我的公众号「ShawnBlog」。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值