数据结构之二叉树

前面几篇讲了数据结构的线性结构,对于输入数据,链表的线性访问时间太慢。这时我们可以使用一种新的数据结构——树,它的大部分操作的运行时间平均为O(log N)。


树结构

树是由多个结点和边组成的且不存在任何环型的一种数据结构,它与线性结构的区别在于线性结构的存储是“一对一”的关系,而树结构是“一对多”的关系,一个根结点下可以有多个子结点。树结构的示意图如下
在这里插入图片描述

树的术语

  • 结点:树中的元素为树的结点。如上图中元素1、元素2都是结点。
  • 根结点:树的最顶端结点1为树的根结点。
  • 父结点:指向其他结点的结点,为这些结点的父结点。如上图中结点1为结点2和结点3的父结点。
  • 子结点:被某个结点指向的结点,为该结点的子结点。如上图中结点2和结点3为节点1的子结点。
  • 结点的度:一个结点的子结点数量为该结点的度。如上图中结点1的度为2、结点2的度为3、结点4的度为0。
  • 叶子结点:度为0的结点为叶子结点。
  • 兄弟节点:具有相同的父结点的所有结点为兄弟结点。如上图中结点2和结点3为兄弟结点。
  • 结点的层次:根节点的层次为1,其他节点的层次为父节点层次+1。
  • 树的深度:树中所有结点的最大层次为该树的深度。如上图中树的深度为3。

二叉树

二叉树是树结构中使用最多的一种。

二叉树的定义:树中每个结点最多只有两个子结点,即任何一个结点的度都小于等于2。

如下图中,左边的为二叉树,右边的不是二叉树。
在这里插入图片描述
左边树中每个结点的子结点个数都小于等于2,满足二叉树的定义,因此左边树是一个二叉树。

右边树根结点1有三个子结点2、6、3,不满足二叉树的定义,所以右边树不是一个二叉树。


左子结点和右子结点

因为二叉树最多只有两个子结点,因此我们通常把左侧的子结点称为左子结点,右侧的子结点称为右子结点
在这里插入图片描述

完美二叉树

完美二叉树 又叫 满二叉树

定义:在一个二叉树中除了最后一层的结点外,其余每个结点都有两个子结点。

如图是一个满二叉树。
在这里插入图片描述
如上图为一个满二叉树,除最后一层的结点4、5、6、7外,其他结点都有两个子结点。

二叉树的性质

  • 在二叉树中第 i 层的最多结点个数为2i-1(i >= 1)
  • 深度为 K 的二叉树最多有2k - 1个结点(k >= 1)
  • 在非空二叉树中,n表示叶子结点的个数,m表示度为2的结点数,则:n = m + 1

二叉树的存储

二叉树存储数据,可以使用数组存储链表存储

数组存储
在这里插入图片描述
上图使用数组存储,按照从上到下、从左到右的顺序给二叉树结点标上数组下标。之后会发现无法找到各个结点之间的对应关系。

我们可以将二叉树补全为一颗满二叉树,然后按照从上到下、从左到右的顺序给二叉树的结点标上数组下标,如图
在这里插入图片描述
我们可以发现各个节点之间好像可以找出对应关系了,即父节点下标 * 2 + 1 为其左子结点的下标值;父节点的下标 * 2 + 2 为其右子结点的下标值。

链表存储

链表存储是二叉树最常用的一种存储方法,把每个节点封装为一个对象,定义leftright两个属性,分别指向其左子结点和右子结点。如图
在这里插入图片描述
我们可以通过结点的leftright属性就可以找到其子结点。

树的遍历

树的遍历一共分为三种:先序遍历、中序遍历、后续遍历


先序遍历

先序遍历的顺序:访问根结点 => 访问左子结点 => 访问右子结点,即根左右。在访问左子结点或右子结点时,仍按照这个规则继续访问。

对下图进行先序遍历
在这里插入图片描述
先序遍历:

  • 先访问根结点35,再访问左子结点,最后访问右子结点
  • 左子结点是一颗二叉树,按照根左右的顺序,左子结点二叉树访问顺序为:20、18、47
  • 右子结点也为一颗二叉树,按照根左右的顺序,右子结点二叉树访问顺序为:30、35、27
  • 综合上述,上图中二叉树的先序遍历顺序为:35、20、18、47、30、35、27

如下图为先序遍历的一个访问顺序,虚线为访问顺序
在这里插入图片描述


代码实现

private void preOrder(Node node) {
    if(node != null){
        //先访问根结点
        System.out.println("编程小马:" + node.data);
        //递归遍历左子结点
        preOrder(node.left);
        //递归遍历右子结点
        preOrder(node.right);
    }
}

参数node为树的根结点。

先序遍历:先访问根结点,在递归遍历左子结点,最后递归遍历右子结点。


中序遍历

中序遍历的顺序:访问左子结点 => 访问跟结点 => 访问右子结点,即左根右。在访问左子结点或右子结点时,仍按照这个规则继续访问。

对下图进行先序遍历
在这里插入图片描述中序遍历:

  • 先访问左子结点,左子结点为一颗二叉树,按照中序遍历规则遍历左子结点的二叉树为:18、20、47
  • 再访问根结点35
  • 最后访问右子结点,右子结点也是一颗二叉树,按照中序遍历规则遍历右子结点的二叉树为:35、30、27
  • 综合上述,上图中二叉树的中序遍历顺序为:18、20、47、35、35、30、27

如下图为先序遍历的一个访问顺序,虚线为访问顺序
在这里插入图片描述


代码实现

private void inOrder(Node node) {
    if(node != null){
        inOrder(node.left);
        System.out.println("编程小马:" + node.data);
        inOrder(node.right);
    }
}

参数node为树的根结点。

中序遍历:先递归遍历左子结点,在访问根结点,最后递归遍历右子结点。


后序遍历

后序遍历的顺序:访问左子结点 => 访问右子结点 => 访问根结点,即左右根。在访问左子结点或右子结点时,仍按照这个规则继续访问。

对下图进行先序遍历
在这里插入图片描述
后序遍历:

  • 先访问左子结点,左子结点为一颗二叉树,按照中序遍历规则遍历左子结点的二叉树为:18、47、20
  • 在访问右子结点,右子结点也是一颗二叉树,按照中序遍历规则遍历右子结点的二叉树为:35、27、30
  • 再访问根结点35
  • 综合上述,上图中二叉树的中序遍历顺序为:18、47、20、35、27、30、35

如下图为先序遍历的一个访问顺序,虚线为访问顺序
在这里插入图片描述


代码实现

private void postOrder(Node node) {
    if(node != null){
        postOrder(node.left);
        postOrder(node.right);
        System.out.println("编程小马:" + node.data);
    }
}

参数node为树的根结点。

后序遍历:先递归遍历左子结点,在递归遍历右子结点,最后访问根结点。


通过上面的介绍,对二叉树也有了一些了解,可以访问二叉查找数对二叉树更深的学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值