【(C语言)数据结构奋斗100天】二叉树(上)

【(C语言)数据结构奋斗100天】二叉树(上)

🏠个人主页:泡泡牛奶

🌵系列专栏:数据结构奋斗100天

本期所介绍的是二叉树,那么什么是二叉树呢?在知道答案之前,请大家思考一下下列问题:

  1. 什么是二叉树的根节点?
  2. 什么是二叉树的左子树和右子树?
  3. 什么是二叉树的父节点和子节点?
  4. 二叉树的遍历有哪些方法?

让我们带着这些问题,开始今天的学习吧( •̀ ω •́ )✧

一、树和二叉树

1. 什么是树?

树是一种非线性的数据结构,是(n>=0)个节点的有限集合。当n=0时,称为空树。在任意一颗非空树中,我们可以看到节点的排列就像一颗倒着的树,根朝上,叶子朝下

367-3671401_bianry-tree-hd-png-download

在现实生活中有哪些像上面根朝上,叶子朝下的树型结构呢🤔?

wp7002672

例如一个家族里组成的 “家谱” ,就是一颗树。

再例如一个阶级关系就是一颗 “树”。

1634653354_378339_url

除开人与人的关系,还有很多抽象的东西也可以成为一颗 “树”,如一本书的目录,如操作系统的文件系统。

d0c50-linux2bfile2bsystem2bhierarchy

以上的共同点都是由一个 “根” 衍生出许多的 “枝干”,再从每一个 “枝干” 衍生出更多的 “叶子”。

在数据结构中,我们对树有以下称呼:

image-20230207195022422
  • 父节点/双亲节点:某节点的上一个节点为父节点,例如:C是F的父节点
  • (孩)子节点:某一节点指向的下一个节点为子节点,例如:F是C的子节点
  • 兄弟节点:父节点相同的节点互为兄弟节点,例如:D、E
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟,例如:D、F互为堂兄弟节点
  • 节点的:一个节点含有的子树的个数称为该节点的度,例如:B的度为2,C的度为1
  • 叶子节点度为0,或者无子节点的称为叶子节点,例如:G、E、F
  • 树的度:一颗树中的最大的节点的度,例如:上图树的度为2
  • 节点的层次根节点第一层, 依次类推
  • 树的高度/深度:树节点的最大层次,如上图树的高度为4
  • 节点的祖先:根节点到此节点的经过的所有节点都为祖先,例如:A是所有节点的祖孙
  • 子孙:只要在关系在某一节点的下面都为子孙
  • 森林:有(m > 0)的树所组成的集合称为森林

小结:

树的关系可以由现实生活中的亲戚关系来表示

2. 什么是二叉树?

什么是 n叉树

n叉树,故名思意,就是树的分叉最多可以有n个

8QA22

那么二叉树就是,最多有2个孩子节点的树。如下图所示。

image-20230207201745581

二叉树节点的两个孩子,一个被称为左孩子,一个被称为右孩子。这两个孩子的顺序是固定的。

3. 二叉树的类型

单二叉树的类型来说,其实有很多种,普通二叉树、满二叉树、完全二叉树、搜索二叉树、AVL树、红黑树……而今天我们的主题就是满二叉树和完全二叉树

一颗二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这就是满二叉树,如下图。

满二叉树的总节点数为 S n = 2 n − 1 S_n = 2^{n}-1 Sn=2n1 (n为层数)

image-20230207212512657

对一个有n个节点的二叉树,按层级需要编号,则所有节点编号从1到n。如果这颗树的所有节点和同样深度的满二叉树的编号为1到n的节点位置相同,则这个二叉树为完全二叉树。如下图。(简单来说,最后一层连续排布,但不满就是完全二叉树)

image-20230207215651564

4. 二叉树的规律性质

image-20230207205933509
  • 这是一个公比为2的等比数列,首项为1。即 a 1 = 1 a_1=1 a1=1,公比 q = 2 q=2 q=2
  • 第n层节点的数量为 2 n − 1 2^{n-1} 2n1,前n层节点的数量总和为 S n = 2 n − 1 S_n=2^n-1 Sn=2n1
  • 对于任意一棵二叉树,满足 n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n=n0+n1+n2,其中 n n n 为节点总数, n 0 n_0 n0 表示度数为0的节点个数, n 1 n_1 n1 表示度数为1的节点个数, n 2 n_2 n2 表示度数为2的节点个数。
  • 存在 n 2 = n 0 − 1 n_2=n_0-1 n2=n01 的关系。
  • 计算层数 n n n 的公式为 n = log ⁡ 2 ( N + 1 ) n=\log_2(N+1) n=log2(N+1),其中 N N N 为节点总数。

上面这些规律往往会在刷题中使用到

二、二叉树的存储结构

链式结构和顺序结构是二叉树的两种常见存储方式。

1. 顺序结构

顺序存储结构使用数组作为基础存储单元,它适用于表示完全二叉树。除了堆,其他情况使用数组存储会则会造成大量空间浪费。顺序存储结构的二叉树在物理结构上是一个数组,逻辑结构上是一棵二叉树。

2019-05-16-10

用数组来存储,我们通常会通过下标来访问元素,观察节点规律,我们不难发现:

  • 若父节点为 father,子节点为 child,则满足:

    f a t h e r = c h i l d − 1 2 father = \frac{child - 1}{2} father=2child1

  • 若左右子节点分别为 leftright,并且左右孩子都存在(即 l e f t , r i g h t ≥ n left, right \ge n left,rightn),则满足:

    l e f t = f a t h e r ∗ 2 + 1 left = father * 2 + 1 left=father2+1

    r i g h t = f a t h e r ∗ 2 + 2 right = father * 2 + 2 right=father2+2

2. 链式结构

链式结构使用指针作为底层容器,通过指向子节点的指针来表示树的逻辑结构。这种方式在存储灵活性和空间利用效率方面优于顺序结构。通用方法是链表中每个节点由三个域组成:数据域和左右指针域。

typedef int treeType;//将int重定义为type_t
typedef struct TreeNode
{
    treeType val;			//表示数据
    TreeNode* left;	//表示左孩子
    TreeNode* right;	//表示右孩子
}TreeNode;

总的来说,选择使用链式结构还是顺序结构要根据具体的存储需求来决定。

三、二叉树的遍历

当我们介绍数组、链表时,为什么没有着重研究他们的遍历过程呢?

二叉树的遍历又有什么特殊之处?

在计算机程序中,遍历线性结构的数组或链表是一件简单的事情。相反,二叉树是一种典型的非线性数据结构,遍历时需要将非线性关系的节点转化为线性序列。

二叉树的遍历可以根据节点的直接位置关系分为两类:

  1. 深度优先遍历(前序遍历、中序遍历、后续遍历)
  2. 广度优先遍历(层序遍历)

下面就来看看这些不同的遍历方式吧ο(=•ω<=)ρ⌒☆

1. 深度优先遍历

  1. 前序遍历

二叉树的前序遍历,遍历顺序为根节点、左子树、右子树,当遇到空指针NULL时返回。

二叉树1

代码示例如下:

void PreOrder(TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }
    //先取出当前节点元素,再对其进行相关操作
    printf("%d", root->val);
    
    InOrder(root->left);//遍历左节点
    InOrder(root->right);//遍历右节点
}
  1. 中序遍历

二叉树的中序遍历,遍历顺序为左子树、根节点、右子树,当遇到空指针NULL时返回。

二叉树2

代码示例如下:

void InOrder(TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }
    
    InOrder(root->left);//遍历左子树
    
    printf("%d", root->val);//取出元素,进行操作
    
    InOrder(root->right);//遍历右子树
}
  1. 后序遍历

二叉树的后序遍历,遍历顺序为左子树、右子树、根节点,当遇到空指针NULL时返回。

二叉树3

代码示例如下:

void PostOrder(TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }
    
    PostOrder(root->left);//遍历左子树
    PostOrder(root->right);//遍历右子树
    printf("%d", root->val);//取出元素,进行操作
}

2. 广度优先遍历

广度优先遍历,按层次遍历,从根节点开始,按从上至下,从左至右的顺序遍历二叉树的所有节点。

使用广度优先算法,我们通常需要借用队列这个数据结构来实现(不知道队列的,或者没看过这篇文章的,赶紧取看看🚀传送门

接下来我们就会使用队列来完成我们所需要的操作。

二叉树4

这是上一期的队列节点结构,我们需要把QueueType的类型改为TreeNode*来存放我们的节点:

typedef TreeNode* QueueType;//改为TreeNode*

typedef struct QueueNode
{
    QueueType value;
    struct QueueNode* next;
}QueueNode;

而这也就是为什么我们需要对类型typedef的原因,可以方便我们做修改φ(゜▽゜*)♪

遍历代码示例如下:

void LevelOrder(TreeNode* root)
{
    Queue q;
    queue_init(&q);
    
    queue_push(&q, root);
    while (!queue_empty(&q))
    {
        TreeNode* cur = queue_front(&q);
        queue_pop(&q);
        if (cur == NULL)
        {
            continue;
        }
        printf("%d", cur->val);//取出元素,进行操作
        queue_push(&q, cur->left);
        queue_push(&q, cur->right);
    }
    
    //不要忘了释放Queue
    queue_destory(&q);
}

在刷题过程中,单纯只是这样,多半会有些不够,接下来我们就把代码进一步优化吧:

void LevelOrder(TreeNode* root)
{
    Queue q;
    queue_init(&q);
    
    int level = 0; //表示当前层数<------------
    queue_push(&q, root);
    while (!queue_empty(&q))
    {
        TreeNode* cur = queue_front(&q);
        queue_pop(&q);
        
//--------------------------------------------------
        int level_size = queue_size(&q);//获取当前层的元素个数
        for (int i = 0; i<level_size; ++i)
        {
            printf("%d", cur->val);//取出元素,进行操作
            
            //对当前节点的左右子节点进行判断,若为空便没有必要入队列
            if (cur->left != NULL)
            {
                queue_push(&q, cur->left);
            }
            if (cur->right != NULL)
            {
                queue_push(&q, cur->right);
            }
        }
//---------------------------------------------------
        
        ++level;//层数+1 <------------
    }
    
    //不要忘了释放Queue
    queue_destory(&q);
}

当每一层入完队列之后,队列内的元素个数一定是下一层的元素个数(可以简单的画一个图来看看,这里就给大家布置一个小问题,让你们自己来理解吧(*^-^*)

四、小结

二叉树是树形结构的一种,其中每个节点最多有两个子节点。二叉树有多种遍历方法,包括深度优先遍历和广度优先遍历。

深度优先遍历有三种:

  • 前序遍历
  • 中序遍历
  • 后序遍历

分别对应的遍历顺序为:

  • 根节点、左子树、右子树

  • 左子树、根节点、右子树

  • 左子树、右子树、根节点

广度优先遍历的遍历顺序是从上到下、从左到右

点赞是对博主的鼓励和支持!🤗 希望你们看完这篇博客后,不妨给博主一个大大的三连👍,表示对内容的赞赏!😊 当然,如果你是真心被内容打动,那么点个四连甚至五连都是博主最大的动力!💪 让我们一起用简单幽默的语气,为点赞助力!ο(=•ω<=)ρ⌒☆

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡泡牛奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值