【数据结构初阶】链式二叉树接口实现超详解

文章目录

  • 1. 节点定义
  • 2. 前中后序遍历
    • 2. 1 遍历规则
    • 2. 2 遍历实现
    • 2. 3 结点个数
      • 2. 3. 1 二叉树节点个数
      • 2. 3. 2 二叉树叶子节点个数
      • 2. 3. 3 二叉树第k层节点个数
    • 2. 4 二叉树查找值为x的节点
    • 2. 5 二叉树层序遍历
    • 2. 6 判断二叉树是否是完全二叉树
  • 3. 二叉树性质


1. 节点定义

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

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;				//数据struct BinaryTreeNode* left;	//左孩子struct BinaryTreeNode* right;	//右孩子
}BTNode;

链式二叉树的创建方式比较复杂,为了更好地对接口进行调试,我们先手动创建一棵链式二叉树进行测试:

//创建节点
BTNode* BuyBTNode(int val)
{BTNode * newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = val;newnode->left = NULL;newnode->right = NULL;return newnode;
}
BTNode * CreateTree()
{BTNode * n1 = BuyBTNode(1);BTNode * n2 = BuyBTNode(2);BTNode * n3 = BuyBTNode(3);BTNode * n4 = BuyBTNode(4);BTNode * n5 = BuyBTNode(5);BTNode * n6 = BuyBTNode(6);BTNode * n7 = BuyBTNode(7);//手动将他们连接起来成为一棵二叉树n1->left = n2;n1->right = n4;n2->left = n3;n4->left = n5;n4->right = n6;n5->left = n7;return n1;
}

接下来就可以用这棵二叉树对接口进行测试。

2. 前中后序遍历

二叉树不同于之前的顺序结构,不能直接进行依次遍历,必须按照一定的规则进行遍历
注:堆也是一种二叉树,但是堆不能进行遍历,所以没有这方面的考虑。
另外二叉树的链式结构是一个递归的结构,几乎所有的接口实现都需要用到递归的思想。
1

2. 1 遍历规则

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal,亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
    访问顺序为:根结点、左子树、右子树
  2. 中序遍历(lnorder Traversal):访问根结点的操作发生在遍历其左右子树中间
    访问顺序为:左子树、根结点、右子树
  3. 后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后
    访问顺序为:左子树、右子树、根结点

我们以前序遍历解释一下怎么遍历:
2
从根节点1开始遍历,先输出根节点数据1,然后来到左孩子2,输出2,来到2的左孩子3,输出3,来到3的左孩子NULL,回退至3,来到3的右孩子NULL,再回退,3节点遍历完毕,回退至2,来到2的右节点NULL,回退至2,2节点遍历完毕,回退至1,来到1的右孩子4,先输出4,再来到4的左孩子4,输出4,遍历左右俩孩子都为空,回退至上面的4,来到4的右孩子5,输出5后,俩孩子都为空,再最终回退至1,根节点遍历完成,该二叉树遍历完成。
前序遍历结果:123456
中序遍历结果:321546
后序遍历结果:315641

2. 2 遍历实现

在实现时,我们可以把每一个孩子都看成一个二叉树的根节点,以方便递归遍历。

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{if (!root)return;printf("%d ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (!root)return;BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);printf("%d ", root->data);
}
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (!root)return;BinaryTreePrevOrder(root->left);printf("%d ", root->data);BinaryTreePrevOrder(root->right);
}

2

2. 3 结点个数

2. 3. 1 二叉树节点个数

int BinaryTreeSize(BTNode* root);

二叉树中只有结点,没有把数据个数存储起来,那么我们要怎么获取二叉树中元素的个数呢?
有两种思路,一个是创建全局变量,然后通过任意一种遍历方式遍历二叉树,每遍历一次都让这个变量++,那么就能得到节点的个数了,但是这样会带来两个问题:

  1. 全局变量每次都要归0,但是由于是递归进行遍历的,所以需要写一个子函数用于递归
  2. 在函数内部调用全部变量可能会存在危险,因为这个变量是任何函数都能访问并修改的

基于这样的原因,我们不采用这个方式。
而是通过计算这个节点本身+左子树节点的个数+右子树节点的个数的方式进行递归。

int BinaryTreeSize(BTNode* root)
{if (!root)return 0;return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);	//1就是该节点本身,再加上左右两棵子树
}

2. 3. 2 二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root);

叶子节点的特点就是root->left == NULL && root->right == NULL,所以只要加上一个判断是否要+1,其他的就和计算节点个数的思路是一样的。

int BinaryTreeLeafSize(BTNode* root)
{//如果是叶子节点,就没必要继续向下传递了,直接回归就可以了if (!root)return 0;if (root->left == NULL && root->right == NULL)return 1;return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2. 3. 3 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k);

在传递时增加一个参数k,来判断递归到哪一层了,如果是目标层数,就+1回归,其他与计算二叉树叶子节点个数思路一致。

int BinaryTreeLevelKSize(BTNode* root, int k)
{//同理,到目标层数之后就不需要继续向下传递了if (!root)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

2. 4 二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

在二叉树中遍历并对比数据,如果找到了就返回这个节点的指针,没找到就返回NULL

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (!root)return NULL;if (root->data == x)return root;//需要分别判断左右子树中有没有这个数据//左BTNode* leftfind = BinaryTreeFind(root->left,x);if (leftfind)return leftfind;//右BTNode* rightfind = BinaryTreeFind(root->right,x);if (rightfind)return rightfind;//没找到,返回空return NULL;
}

2. 5 二叉树层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历
设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
实现层序遍历需要额外借助队列这一数据结构。
1
层序遍历就是一层一层地从左到右地进行遍历,比如上面这棵二叉树的层序遍历就是:1 2 3 4 5 6 7

大致思路为:创建一个数据类型为BTNode*的队列,先将根节点入队列,然后分别将它的左右孩子入队列,接着出队列一次。然后把队头的左右孩子(如果不为NULL)入队列,再出队列一次,每次出队列前都要把它的值打印出来,循环以上过程直到队列为空。

注:这里的队列不再实现,可以看这篇博客。

void BinaryTreeLevelOrder(BTNode* root)
{//二叉树判空if (!root)printf("null\n");//创建队列并初始化Queue qu;QueueInit(&qu);//把根节点入队列QueuePush(&qu,  root);while (!QueueEmpty(&qu)){//将队头的左右子树入队列,并将队头数据打印,再出队列一次BTNode* tmp = QueueFront(&qu);QueuePop(&qu);printf("%d ", tmp->data);if (tmp->left)QueuePush(&qu, tmp->left);if (tmp->right)QueuePush(&qu, tmp->right);}//销毁队列QueueDestroy(&qu);printf("\n");
}

2. 6 判断二叉树是否是完全二叉树

bool BinaryTreeComplete(BTNode* root);

完全二叉树的特点是第X层没有完全放满时,X+1层不能有节点,且没有满的层的节点必须是从左往右排布的。

我们可以借助层序遍历来判断二叉树是不是完全二叉树。
步骤为:就算节点为空也要入队列,直到出队列时遇到了空节点时,开始连续出队列,如果队列中剩下的元素中有非空的节点,就说明不是完全二叉树

我们以这棵二叉树为例:
2
当轮到4的左孩子(尽管不存在,但先这么理解一下)出队列时,开始不再入队列,只出队列并判断是否为空,这时队列中还有4的右节点7,那就判断出来了二叉树不是完全二叉树。

如果在第一次出队列的为空节点的之后的节点都是空节点(比如上面这个二叉树没有节点7),那就是完全二叉树。

bool BinaryTreeComplete(BTNode* root)
{assert(root);Queue qu;QueueInit(&qu);QueuePush(&qu, root);while (!QueueEmpty(&qu)){BTNode* tmp = QueueFront(&qu);//如果出队列的是空节点,就停止这个循环if (!tmp)break;QueuePop(&qu);QueuePush(&qu, tmp->left);QueuePush(&qu, tmp->right);}//这个循环只出不入while (!QueueEmpty(&qu)){//如果队列中还有空节点,就说明不是完全二叉树if (QueueFront(&qu)){QueueDestroy(&qu);return false;}QueuePop(&qu);}QueueDestroy(&qu);return true;
}

3. 二叉树性质

  1. 对任何一棵二叉树,如果度为 0 的叶结点个数为n0,度为 2 的分支结点个数为n2 ,则有
    n0 = n2+1
    2
    证明:
    假设一个二叉树有 a 个度为 2 的节点,b 个度为 1 的节点,c 个叶节点,则这个二叉树的边数是 2a+b
    另一方面,由于共有 a+b+c 个节点,所以边数等于 a+b+c-1
    结合上面两个公式:
    即 2a+b = a+b+c-1,即 a=c-1

题目练习

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
    A 不存在这样的二叉树
    B 200
    C 198
    D 199
    有199个度为2的节点,那么在二叉树中,度为0的节点有199+1也就是200个,选B。

  2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为()
    A n
    B n+1
    C n-1
    D n/2
    设叶子节点有x个,那么度为2的节点为x-1个。有2n个节点,所以度为1的节点有2n-2x+1条,而完全二叉树中度为1的节点只有0或1个,但是这个二叉树的结点个数为偶数(由于根节点有1个,而其他的除了最后一层的结点个数都是偶数),所以度为1的结点个数为1。解方程2n-2x+1=1就可以得出x=n。选A。

  3. 一棵完全二叉树的结点数位为531个,那么这棵树的高度为()
    A 11
    B 10
    C 8
    D 12
    完全二叉树每一层的节点个数为2n-1个,那么根据等比数列的求和公式,完全二叉树前n层的节点个数为2n-1,解方程2n-1>=531可得,n为10,选B。

  4. 一个具有767个结点的完全二叉树,其叶子结点个数为()
    A 383
    B 384
    C 385
    D 386
    设叶子节点有x个,那么度为2的节点为x-1个,度为1的节点为768-2x个,通过第2题的分析我们可知度为1的节点为0个,就可以算出x=384,选B。

数据结构初阶的二叉树就到这里,想必你会发现这个二叉树我们没有实现插入删除的接口,因为二叉树的插入删除使用C语言实现过于复杂,会在高阶数据结构中讲解。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=10p1y5y691zy4

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431044.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenStack Yoga版安装笔记(十三)neutron安装

1、官方文档 OpenStack Installation Guidehttps://docs.openstack.org/install-guide/ 本次安装是在Ubuntu 22.04上进行,基本按照OpenStack Installation Guide顺序执行,主要内容包括: 环境安装 (已完成)OpenStack…

畅阅读微信小程序

畅阅读微信小程序 weixin051畅阅读微信小程序ssm 摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用j…

leetcode24. 两两交换链表中的节点,递归

leetcode24. 两两交换链表中的节点 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 示例 1: 输入:he…

Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】

Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】 目录 Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】 一、简单介绍 二、装饰者模式(Decorator Pattern) 1、什么时候使用装…

Python3爬虫教程-HTTP基本原理

HTTP基本原理 1,URL组成部分详解2,HTTP和HTTPS3,HTTP请求过程4,请求(Request)请求方法(Request Method)请求的网址(Request URL)请求头(Request H…

aws s3 存储桶 前端组件上传简单案例

写一个vue3 上传aws oss存储的案例 使用到的插件 npm install aws-sdk/client-s3 注意事项 : 1. 本地调试 , 需要设置在官网设置跨域 必须!!! 否则调试不了 ,前端代理是不起作用的 ,因为是插…

【AIGC】ChatGPT RAG提取文档内容,高效制作PPT、论文

目录 一、理解 RAG 技术 二、利用 ChatGPT 的 RAG 技术提取文档内容 三、高效制作 PPT 四、高效撰写论文 五、最佳实践与建议 六、工具推荐 随着人工智能生成内容(AIGC)的快速发展,利用先进的技术工具如 ChatGPT 的 RAG(Ret…

spring boot项目对接人大金仓

先确认一下依赖 第一 是否引入了mybatis-plus多数据源&#xff0c;如果引入了请将版本保持在3.5.0以上 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>${dynam…

Java 中创建线程几种方式

目录 概述 一. 继承Thread类 1. 特点 2. 注意事项 3. 代码示例 二. 实现Runnable接口 1. 特点 2. 注意事项 3. 代码示例 三. 实现Callable接口 1. 特点 2. 注意事项 3. 代码示例 概述 在Java中&#xff0c;线程&#xff08;Thread&#xff09;是程序执行的最小单…

已解决sublime text 3 注册激活

问题&#xff1a;未激活 解决方法&#xff1a; 安装sublime3后&#xff0c;将Patch.exe文件放入sublime 安装文件下 运行Patch.exe&#xff0c;复制粘贴注册码到 preference->enter license&#xff1b;操作如下 点击“Use license”,提示如下图表示激活成功&#xff1a; 重…

Mac使用gradle编译springboot-2.7.x源码

1 开发环境&#xff1a; JDK8 ideaIU-2024.2.2 gradle-7.6.3 代理网络 2 下载springboot源码 代码仓库网址 git clone -b 2.7.x https://github.com/spring-projects/spring-boot.git3 安装gradle gradle下载网址 https://services.gradle.org/distributions/ 安装此文件指…

你的提交信息还在拖后腿?看这里,提升代码质量的绝招!

文章目录 前言一、什么是约定式提交&#xff1f;二、创建新仓库三、将代码推送到远程仓库的步骤1.检查当前远程仓库2.添加代码到暂存区3. 进行约定式提交4. 推送代码到远程仓库5. 完成推送 总结 前言 在当今软件开发领域&#xff0c;Git已经成为最广泛使用的版本控制系统之一。…

按之字形顺序打印二叉树

题目链接&#xff1a;[编程题]按之字形顺序打印二叉树 题目简单描述&#xff1a; 给定一个二叉树&#xff0c;返回该二叉树的之字形层序遍历&#xff0c;&#xff08;第一层从左向右&#xff0c;下一层从右向左&#xff0c;一直这样交替&#xff09; 思路&#xff1a; 层序…

shell文件操作

1. 使用Makefile将前面所写的项目&#xff0c;升级优化 答&#xff1a;系统刚重装 文件缺失 恕难从命

一次RPC调用过程是怎么样的?

注册中心 RPC&#xff08;Remote Procedure Call&#xff09;翻译成中文就是 {远程过程调用}。RPC 框架起到的作用就是为了实现&#xff0c;调用远程方法时&#xff0c;能够做到和调用本地方法一样&#xff0c;让开发人员更专注于业务开发&#xff0c;不用去考虑网络编程等细节…

neo4j小白入门

1.建立几个学校的节点 1.1创建一个节点的Cypher命令 create (Variable:Lable {Key1:Value,Key2,Value2}) return Variable 1.2创建一个学校的节点 create (n:School{name:清华大学,code: 10003,establishmentDate:date ("1911-04-29")})return n 1.3一次创建几个…

出现conda不是内部或外部命令,也不是可运行的程序或批处理文件。的解决办法

发现是我的环境变量不对&#xff0c;需要改成conda.exe所在的目录下 如果不知道自己conda.exe在哪的 可以下载个everything这个软件 找东西很快 找到后 点击环境变量-系统变量-Path-新建-&#xff08;你的conda.exe所在目录&#xff1a;绝对路径&#xff09; 完成上述操作…

如何通过蜂巢(容器安全)管理内部部署数据安全产品与云数据安全产品?

本文将探讨内部部署和云数据安全产品之间的主要区别。在思考这个问题之前&#xff0c;首先了解内部部署和云数据安全产品之间的主要区别。 内部部署数据安全产品意味着管理控制台位于企业客户的内部部署&#xff0c;而德迅云安全则在云中托管云数据安全产品。德迅云安全供应商通…

DERT目标检测—End-to-End Object Detection with Transformers

DERT&#xff1a;使用Transformer的端到端目标检测 论文题目&#xff1a;End-to-End Object Detection with Transformers 官方代码&#xff1a;https://github.com/facebookresearch/detr 论文题目中包括的一个创新点End to End(端到端的方法&#xff09;简单的理解就是没有使…

MISC - 第四天(OOK编码,audacity音频工具,摩斯电码,D盾,盲文识别,vmdk文件压缩)

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天继续讲解MISC知识点 FLAG 附件是一张图片&#xff0c;尝试binwalk无果 使用StegSolve工具Data Extract查看时 发现PK字段&#xff0c;是大多数压缩包的文件头点击Save Bin保存zip文件 解压缩失败使用修复软件:htt…