当前位置: 首页 > news >正文

数据结构-链式二叉树

文章目录

  • 一、链式二叉树
    • 1.1 链式二叉树的创建
    • 1.2 根、左子树、右子树
    • 1.3 二叉树的前中后序遍历
      • 1.3.1前(先)序遍历
      • 1.3.2中序遍历
      • 1.3.3后序遍历
    • 1.4 二叉树的节点个数
    • 1.5 二叉树的叶子结点个数
    • 1.6 第K层节点个数
    • 1.7 二叉树的高度
    • 1.8 查找指定的值(val)
    • 1.9 二叉树的销毁
  • 二、层序遍历
    • 2.1 二叉树的层序遍历
    • 2.2 层序遍历判断二叉树是否是完全二叉树
  • 三、二叉树的性质(补充)
    • 3.1选择题

一、链式二叉树

因为二叉树的度是确定的,所以可以用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成: 数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,其结构如下:

typedef int BTDataType;
//二叉链
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left; //指向当前结点的左孩子struct BinaryTreeNode* right; //指向当前结点的右孩子BTDataType val; //当前结点的值域
}BTNode;

1.1 链式二叉树的创建

二叉树的创建方式比较复杂, 为了更好的步入到二叉树的内容中,我们先手动创建一棵链式二叉树。

比如我们要创建如下一个二叉树:
在这里插入图片描述

//创建节点
BTNode* BuyBTNode(int val)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->val = val;newnode->left = NULL;newnode->right = NULL;return newnode;
}
//创建链式二叉树
BTNode* CreateBTree()
{BTNode* node1 = BuyBTNode(1);BTNode* node2 = BuyBTNode(2);BTNode* node3 = BuyBTNode(3);BTNode* node4 = BuyBTNode(4);BTNode* node5 = BuyBTNode(5);BTNode* node6 = BuyBTNode(6);BTNode* node7 = BuyBTNode(7);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;node5->left = node7;return node1;
}

1.2 根、左子树、右子树

由于这里是链式二叉树,所以后面我们看到一个二叉树,就要把树分成三个部分:根、左子树、右子树。回顾二叉树的概念:二叉树分为空树和非空二叉树,非空二叉树由根结点、根结点的左子树、根结点的右子树组成的。
在这里插入图片描述根结点的左子树和右子树分别又是由子树的根结点、子树结点的左子树、子树结点的右子树组成的。因此二叉树的定义是递归式的, 后序链式二叉树的操作中基本都是按照该概念实现的。

1.3 二叉树的前中后序遍历

二叉树的操作离不开树的遍历,那二叉树的遍历有哪些方式呢?比如我们要遍历下面这个二叉树:
在这里插入图片描述二叉树的遍历分为三种:前/中/后序遍历。下面我们分别讲解三种遍历。

1.3.1前(先)序遍历

▲ 前序遍历(Preorder Traversal(DLR)亦称先序遍历): 访问根结点的操作发生在遍历其左右子树之前

访问顺序为: 根结点、左子树、右子树

void PreOrder(BTNode* root)
{if (root == NULL){printf("# ");return;}printf("%c ",root->val);PreOrder(root->left);PreOrder(root->right);
}

先看运行结果:
在这里插入图片描述
在这里插入图片描述打印的结果为什么是上面的样子呢?这里涉及到函数的递归,所以要先理解前序遍历的顺序是根、左子树、右子树。也就是从根节点开始遍历,根遍历完了就到左子树,那左子树又可以分为根、左子树、右子树,又会按照根左右的顺序遍历;要等到左子树完完全全遍历完了,才会遍历到右子树。这里的难点就是: 理解递归递推的终止条件和每一层函数调用完毕后会返回上一层调用它的函数处继续往后执行(回归)。

前序遍历的递归过程如下:
在这里插入图片描述

1.3.2中序遍历

◆中序遍历(lnorder Traversal(LDR)): 访问根结点的操作发生在遍历其左右子树之中(间)

访问顺序为: 左子树、根结点、右子树

void InOrder(BTNode* root)
{if (root == NULL){printf("# ");return;}InOrder(root->left);printf("%c ", root->val);InOrder(root->right);
}

打印结果如下:
在这里插入图片描述
在这里插入图片描述中序遍历就是从左子树开始遍历,左子树遍历完了就遍历根,最后再遍历右子树。而其中的子树又可以分为左子树、根、右子树,又会按照左根右的顺序遍历。

1.3.3后序遍历

▼后序遍历(Postorder Traversal(LRD)): 访问根结点的操作发生在遍历其左右子树之后

访问顺序为: 左子树、右子树、根结点

void PostOrder(BTNode* root)
{if (root == NULL){printf("# ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->val);
}

打印结果如下:
在这里插入图片描述
在这里插入图片描述后序遍历就是根要在左右子树之后访问。前\中\后续遍在历代码中的递归调用虽然只是换了个位置,但是递归调用的过程是不相同的,如不理解函数的逐层调用过程,就需要画图层层递进地来深刻剖析递归。

1.4 二叉树的节点个数

通过递归计算二叉树的节点个数:

int BTreeSize(BTNode* root)
{if (root == NULL)return 0;return BTreeSize(root->left) + BTreeSize(root->right) + 1;
}

这里要理解递归的过程是什么样, 以下图的举例来理解这里的递归:
在这里插入图片描述这里递推的结束条件是:子树为空才会回归上一层; 而空树就没有节点嘛!所以return 0。在返回上一层的时候可以看到是左右子树都递归完毕而且还要加一个1之后才返回上一层,加的1其实就是加上根节点的数量。

1.5 二叉树的叶子结点个数

通过递归计算二叉树的叶子节点个数:

int BTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}

没有孩子节点的节点就是叶子节点,所以递推结束的条件就是:该节点的左右子树为空就返回1,即把该叶子节点的数量计上。而一开始的判空其实针对对根节点而言的。如果树都为空,那就没有叶子节点啦。举例递归过程:
在这里插入图片描述

1.6 第K层节点个数

二叉树的第K层节点个数:

int BTreeLevelKSize(BTNode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;return BTreeLevelKSize(root->left, k - 1)+ BTreeLevelKSize(root->right, k - 1);
}

计算二叉树第K层的节点,这里需要加入一个参数k才能实现,因为我们不知道递归什么时候会到达第k层,所以传一个参数k,让它每递推一次就递减1,若根节点为第一层,则k递减到1就到达第k层了(递推终止条件)。当然还有一个递推终止条件就是还没到达第k层就已经出现了空节点,所以还没到达第k层时就遇到了空就返回。
在这里插入图片描述

1.7 二叉树的高度

通过递归计算二叉树的高度/深度:

int BTreeDepth(BTNode* root)
{if (root == NULL)return 0;int leftDepth = BTreeDepth(root->left);int rightDepth = BTreeDepth(root->right);return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

计算二叉树的高度也就是计算该二叉树有多少层?那就是我们要找到一个沿着其祖先路径最长的那个节点。所以左右子树之间需要通过比较返回较长的那个节点才行。每返回一层要加个1因为每个节点相较于其孩子节点都是一个根,只要不为空就要算一节长度。
在这里插入图片描述注意:如果上面的代码简化写成下面的样子:

return BTreeDepth(root->left) > BTreeDepth(root->right)? BTreeDepth(root->left)+1 : BTreeDepth(root->right)+1;

这样写时间效率上会非常差。试想:如果二叉树的节点数量庞大,即层数很多时;左右子树相比较就要进行两个函数的递归调用,递归调用又是三目表达式,那时间消耗就会很大;而等比较出了大小以后,才决定三目表达式的返回值;而返回值又是一个函数的递归调用,递推进入下一层以后还是面临同样的三目表达式,同样的事情一遍又一遍地重复,就会消耗非常非常多的时间。所以这里不建议这么写,最好的方式就是将函数的返回值记录下来,这样回归的时候将记录下来的值返回去,就不会造成上面的情况。

1.8 查找指定的值(val)

在二叉树中查找指定的值x, 如果找到则返回该节点的地址, 否则返回NULL。

BTNode* BTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->val == x)return root;BTNode* ret1 = BTreeFind(root->left, x);if (ret1 != NULL)return ret1;return BTreeFind(root->right, x);
}

举如下一个例子:比如我们要找x == 3的节点,则函数的递归过程如下:
在这里插入图片描述递归的难点就是返回值不是一下子返回到最上面(最外面)的函数,而是返回上一层调用它的函数处。

1.9 二叉树的销毁

销毁二叉树的销毁就比较简单了, 递归的过程采用后续遍历释放节点:

void BTreeDestroy(BTNode* root)
{if (root == NULL)return;BTreeDestroy(root->left);BTreeDestroy(root->right);free(root);
}

链式二叉树适合于数据的存储,但并不适用于数据的增删查改。所以这里只是了解链式二叉树的性质,以及怎样通过递归去求二叉树的节点、叶子结点、高度等。

二、层序遍历

2.1 二叉树的层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推;自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

实现层序遍历最好的方法是借助数据结构: 队列

在这里插入图片描述可以看到上图通过队列就可以实现二叉树的层序遍历,每出队一个元素就让该元素的左右孩子入队。代码实现如下:(队列的性质是先进先出)

void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root != NULL)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);printf("%c ", top->val);QueuePop(&q);if (top->left != NULL){QueuePush(&q, top->left);}if (top->right != NULL){QueuePush(&q, top->right);}}QueueDestroy(&q);
}

注意:这里队列中每个节点存放的值(data)是二叉树节点的地址。所以队列出队只是将队列的头结点释放掉了,而并没有把节点里存放的二叉树节点指针删掉。上面的代码中可以看到已经提前将队头节点的值(二叉树节点的指针)存放在top里面了。

2.2 层序遍历判断二叉树是否是完全二叉树

有了层序遍历这种方法,我们其实就可以用此方法判断一个二叉树是否是完全二叉树:
在这里插入图片描述在这里插入图片描述

bool IsCompleteBTree(BTNode* root)
{Queue q;QueueInit(&q);if (root != NULL)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//遇到有空指针的地方就跳出循环if (front == NULL)break;QueuePush(&q,front->left);QueuePush(&q,front->right);}//只管出队 //在出队的过程中若有不为空的指针说明不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

三、二叉树的性质(补充)

1.若规定根结点所在层数为1, 则一棵非空二叉树的第i层上最多有2i-1个结点.
2.若规定根结点所在层数为1,则深度(层数)为h的二叉树的最大结点数是2h-1
3.对任何一棵二叉树, 如果度为0的叶结点个数为n0, 度为2的分支结点个数为n2, 则有: n0=n2+1

解释一下第三个性质:
在这里插入图片描述在这里插入图片描述

3.1选择题

(1) 某完全二叉树按层次输出(同一层从左到右)的列为ABCDEFGH,该完全二叉树的前序序列为 (A)
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA

(2) 某二叉树的先序遍历和中序遍历如下: 先序遍历: EFHIGJK; 中序遍历: HFIEJKG. 则二叉树根节点为 (A)
A E
B F
C G
D H

(3) 一棵二叉树的中序遍历序列为: badce,后序遍历序列为: bdeca,则该二叉树的前序历列为 (D)
A adbce
8 decab
C debac
D abcde
记住:前序序列能确定根节点在最左边,后续遍历能确定根节点在最右边,再结合中序遍历就能分割出整体的左、根、右。将该二叉树画出来。

(4) 某二叉树的后序遍历序列与中序遍历序列相同,均为ABCDEF,则按层次输出(同一层从左到右)为 (A)
A FEDCBA
B CBAFED
C DEFCBA
D ABCDEF

(5) 某二叉树共有399个结点,其中有199个度为2的结点,则该二又树中的叶子结点数为 (B)
A 不存在这样的二叉柯
B 200
C 198
D 199
通过二叉树性质可知:该二叉树的叶子节点个数为199+1 == 200

(6) 下列数据结构中,不适合采用顺序存储结构的是 (AC)
A 非完全二叉树
B 堆
C 队列
D 栈

(7) 在具有2n个结点的完全二叉树中中,叶子结点个数为(A)
A n
B n+1
C n-1
D n/2

在这里插入图片描述

(8) 一棵完全二又树的结点个数为531个,那么这棵树的高度为 (B)
A 11
B 10
C 8
D 12

在这里插入图片描述在这里插入图片描述

相关文章:

数据结构-链式二叉树

文章目录 一、链式二叉树1.1 链式二叉树的创建1.2 根、左子树、右子树1.3 二叉树的前中后序遍历1.3.1前(先)序遍历1.3.2中序遍历1.3.3后序遍历 1.4 二叉树的节点个数1.5 二叉树的叶子结点个数1.6 第K层节点个数1.7 二叉树的高度1.8 查找指定的值(val)1.9 二叉树的销毁 二、层序…...

【git-hub项目:YOLOs-CPP】本地实现01:项目构建

目录 写在前面 项目介绍 最新发布说明 Segmentation示例 功能特点 依赖项 安装 克隆代码仓库 配置 构建项目 写在前面 前面刚刚实现的系列文章: 【Windows/C++/yolo开发部署01】 【Windows/C++/yolo开发部署02】 【Windows/C++/yolo开发部署03】 【Windows/C++/yolo…...

250213-RHEL8.8-外接SSD固态硬盘

It seems that the exfat-utils package is still unavailable, even after enabling the RPM Fusion repository. This could happen if the repository metadata hasn’t been updated or if the package isn’t directly available in the RPM Fusion repository for RHEL 8…...

如何本地部署DeepSeek?

DeepSeek:智能时代的得力助手 在人工智能技术飞速发展的今天,DeepSeek 作为一款由国内顶尖团队研发的 AI 工具,凭借其卓越的性能和丰富的功能,逐渐在众多同类产品中脱颖而出,成为众多用户在工作和学习中的得力助手。 …...

leetcode:627. 变更性别(SQL解法)

难度:简单 SQL Schema > Pandas Schema > Salary 表: ----------------------- | Column Name | Type | ----------------------- | id | int | | name | varchar | | sex | ENUM | | salary | int …...

51单片机(国信长天)矩阵键盘的基本操作

在CT107D单片机综合训练平台上,首先将J5处的跳帽接到1~2引脚,使按键S4~S19按键组成4X4的矩阵键盘。在扫描按键的过程中,发现有按键触发信号后(不做去抖动),待按键松开后,在数码管的第一位显示相应的数字:从左至右&…...

封装一个sqlite3动态库

作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、项目案例 二…...

Transformer以及BERT阅读参考博文

Transformer以及BERT阅读参考博文 Transformer学习: 已有博主的讲解特别好了: 李沐:Transformer论文逐段精读【论文精读】_哔哩哔哩_bilibili知乎:Transformer模型详解(图解最完整版) - 知乎 个人杂想&…...

AI学习记录 - 最简单的专家模型 MOE

代码 import torch import torch.nn as nn import torch.nn.functional as F from typing import Tupleclass BasicExpert(nn.Module):# 一个 Expert 可以是一个最简单的, linear 层即可# 也可以是 MLP 层# 也可以是 更复杂的 MLP 层(active function 设…...

急停信号的含义

前言: 大家好,我是上位机马工,硕士毕业4年年入40万,目前在一家自动化公司担任软件经理,从事C#上位机软件开发8年以上!我们在开发C#的运动控制程序的时候,一个必要的步骤就是确认设备按钮的急停…...

单调队列queue

1.单调队列(Monotonic Queue) 单调队列是一种特殊的队列,它的元素按照单调性(递增或递减)的顺序排列。简单来说,单调队列会维护一个元素单调递增或递减的顺序,在队列中元素会根据当前队列的元素…...

【漫话机器学习系列】091.置信区间(Confidence Intervals)

置信区间(Confidence Intervals)详解 1. 引言 在统计学和数据分析中,我们通常希望通过样本数据来估计总体参数。然而,由于抽样的随机性,我们不可能得到精确的总体参数,而只能通过估计值(如均值…...

UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0x99

UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0x99 这个错误通常发生在你尝试使用 GBK 编码来解码一个包含非GBK编码字符的文件时。GBK 是一种用于简体中文的字符编码方式,它不支持所有可能的 Unicode 字符。 解决方法 明确文件的正确编码:首…...

DeepSeek应用——与word的配套使用

目录 一、效果展示 二、配置方法 三、使用方法 四、注意事项 1、永久化使用 2、宏被禁用 3、office的生成失败 记录自己学习应用DeepSeek的过程...... 这个是与WPS配套使用的过程,office的与这个类似: 一、效果展示 二、配置方法 1、在最上方的…...

递归乘法算法

文章目录 递归乘法题目链接题目详解解题思路:代码实现: 结语 欢迎大家阅读我的博客,给生活加点impetus!! 让我们进入《题海探骊》,感受算法之美!! 递归乘法 题目链接 在线OJ 题目…...

【免费】2004-2020年各省废气中废气中二氧化硫排放量数据

2004-2020年各省废气中废气中二氧化硫排放量数据 1、时间:2004-2020年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区、年份、废气中二氧化硫排放量(万吨) 4、范围:31省 5、指标说明:二氧化硫排放量指…...

CNN-LSSVM卷积神经网络最小二乘支持向量机多变量多步预测,光伏功率预测

代码地址:CNN-LSSVM卷积神经网络最小二乘支持向量机多变量多步预测,光伏功率预测 CNN-LSSVM卷积神经网络最小二乘支持向量机多变量多步预测,光伏功率预测 一、引言 1、研究背景和意义 光伏发电作为可再生能源的重要组成部分,近…...

【油猴脚本/Tampermonkey】DeepSeek 服务器繁忙无限重试(20250213优化)

目录 一、 引言 二、 逻辑 三、 源代码 四、 添加新脚本 五、 使用 六、 BUG 七、 优化日志 1.获取最后消息内容报错 一、 引言 deepseek每次第一次提问就正常,后面就开始繁忙了,有一点阴招全使我们身上。 greasyfork登不上,不知道…...

单调栈及相关题解

单调递增栈:栈中数据入栈单调递增序列(栈底到栈顶是单调递增); 单调递减栈:栈中数据入栈单调递减序列(栈底到栈顶是单调递减)。 单调递增栈: 维护单调递增栈:遍历数组中每一个元素,执行入栈:每次入栈前先…...

每日温度问题:如何高效解决?

给定一个整数数组 temperatures,表示每天的温度,要求返回一个数组 answer,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。 问题分析 我们需要计算…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

Cursor实现用excel数据填充word模版的方法

cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...