二叉树中的链式结构实现

news2024/11/28 10:49:56

树的概念与结构

线性表:是一种具有n个相同特性的数据元素的有限序列。线性表逻辑上是线性结构,也就是连成的一条直线,但一条直线上的数据元素并不是一定要物理结构连续的。

讲到二叉树之前,我们要先了解一下什么是树,首先树也是一种数据结构,只不过与栈和队列不同,树并不是线性表的一种,因为树的结构组成并不是直线型的,是有分支的。

 树的概念

树是一种非线性的数据结构,由n(n>=0)有限的节点构成的具有层次关系的结构。而结构十分像一棵倒着的树,节点A就像树根一样(根节点)没有前驱节点,而A下面就类似于一个个树枝(子树)一样,而树枝又会分支,分支又有分支...是否会联想到递归了你。

 注意:树的结构中,子树是不存在交集的(除了根节点,每个节点有且只有一个父节点),否则就不是树的结构

 而以上的三种结构就都不是树结构。

树中的常见名词 

有关树的名词可以联想到家里的亲戚关系 

 节点的度:一个节点含有的子树(分支)的个数称为该节点的度; 如上图:A的度为6
叶节点或终端节点:就像是树叶一样(不再分支),即度为0的节点称为叶节点; 如上图:B、C、H、I 等节点为叶节点(终端节点)
非终端节点或分支节点:与上面相反,有分支的节点,即度不为0的节点; 如上图:D、E、F、G 等节点为分支节点
双亲节点或父节点:若一个树含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:与上面相反,一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
(亲)兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的 节点的度 称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的(所有)子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先,E也是Q的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:多棵互不相交的树的集合称为森林(并查集

树的结构表示 

树的表示方式就不会像单链表一样简单,树中要存放数据,和节点之间的关系,而节点之间的关系并不像单链表一样,因为树并不是线性结构,树的分支并不是一定只有一个,可能没有,也可能有多个,那么我们要创建多个指针吗,如果不知道树的高度呢,就算知道了会不会太复杂呢。所以我们一般会有更加简便的定义方式:孩子兄弟表示法。即创建树的节点存放实际数据,孩子指针,兄弟指针。

typedef int DataType;
struct Node
{
    DataType data; // 结点中的数据域
    struct Node* firstChild1; // 第一个孩子结点
    struct Node* pNextBrother; // 指向其下一个兄弟结点
};

 这样是不是就将每一个节点都串联起来了呢,没有的时候就指向空。

二叉树的概念

概念

1. 二叉树中每个节点的度都小于等于2。

2. 二叉树的子树分左右,次序不能颠倒,所以二叉树称为有序数。

二叉树的链式结构

前面谈到的是完全二叉树,因为结构特殊,所以我们是通过顺序表的方式存储的,但是对于一般的二叉树是并不适合的,那样会存在空间的大大浪费,并且也不方便。所以我们还是回归自然,用链表来存储二叉树。

typedef struct TreeNode
{
	int val;
	struct TreeNode* left;
	struct TreeNode* right;

}TN;

每一个节点基本的结构就是存放的数据加左右两个子节点,但是二叉树的左右子节点又可以看作是新的二叉树,这样是不是很像递归在自己调用自己呢。

链式二叉树中的问题一般都是用递归的方式实现,所以我们就要了解一下递归,递归其实是和函数放到一起的,但是函数就实现一次,而递归是实现多次相同功能的函数,而且想要实现递归的精髓就要知道两点

  • 找到递归执行的停止条件(即找到向下创建函数栈帧的最后一次)
  • 将问题转换成子问题(即找到等价问题进行转换)

二叉树前序中序后序遍历

我们知道完全二叉树是用顺序表来存储的,所以我们遍历顺序表,用下标就可以十分轻松的实现打印二叉树,可是链表又该如何打印数据呢?这里我们就有几种递归实现的方式。

1. 前序遍历(也称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。即:根 左 右
2. 中序遍历——访问根结点的操作发生在遍历其左右子树之中(间)。即:左 根 右
3. 后序遍历——访问根结点的操作发生在遍历其左右子树之后。即:左 右 根

这里的左节点与右节点并不一定是单纯的左右节点,他可能也看作是左子树、右子树。所以我们在不知道二叉树的图形结构式,遍历二叉树就要用到上面的方法。 

二叉树前序遍历

假设我们实现上面结构的二叉树并且想要打印出来:

typedef struct TreeNode//创建节点类型
{
	int val;
	struct TreeNode* left;
	struct TreeNode* right;

}TN;
TN* Malloc(int x)//动态开辟节点并初始化
{
	TN* new = (TN*)malloc(sizeof(TN));
	assert(new);
	new->left = new->right = NULL;
	new->val = x;

	return new;
}
void Print(TN* node)//前序遍历
{
	if (node == NULL)
		printf("N ");
	else
	{
		printf("%d ", node->val);
		Print(node->left);
		Print(node->right);
	}
}
int main()
{
	TN* node1 = Malloc(1);
	TN* node2 = Malloc(2);
	TN* node3 = Malloc(3);
	TN* node4 = Malloc(4);
	TN* node5 = Malloc(5);
	TN* node6 = Malloc(6);

	node1->right = node4;
	node1->left = node2;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	Print(node1);//前序打印(根左右)

	return 0;
}

递归部分分析图:

二叉树的中序后序遍历

其实二叉树的的中序和后序遍历就是将打印顺序改变一下就行了:

void Print(TN* node)//中序遍历
{
	if (node == NULL)
		printf("N ");
	else
	{
        Print(node->left);
		printf("%d ", node->val);
		Print(node->right);
	}
}
void Print(TN* node)//后序遍历
{
	if (node == NULL)
		printf("N ");
	else
	{
        Print(node->left);
        Print(node->right);
		printf("%d ", node->val);
	
	}
}

二叉树的层序遍历

层序遍历就是从二叉树的根节点开始出发遍历,然后一层一层的的从左至右遍历每一个节点,而对于二叉树存数据时也是从根节点开始再向根的左右节点插入数据,即存数据和拿数据是相同的顺序,即:先进先出。恰恰符合队列的存储形式,所以就可以通过队列来实现层序遍历。

求二叉树节点个数

int GetNodeSize(TN* node)//求节点的个数
{
	if (node==NULL)//结束条件,而且一般二叉树问题都要考虑执行到NULL的情况
		return 0;
	else
		return GetNodeSize(node->left) + GetNodeSize(node->right) + 1;
        //左子树的节点个数+右子树的节点个数+自生的一个节点
}

求二叉树的叶子结点个数

int GetLeafSize(TN* node)//求叶子结点的个数
{
	if (node == NULL)//终止条件
		return 0;
	if (node->left == node->right && node->left == NULL)//是叶子结点
		return 1;
	else
		return GetLeafSize(node->left) + GetLeafSize(node->right);
        //等价成 左子树的叶子结点个数+右子树的叶子结点个数
}

求二叉树的高度

int GetTreeHeight(TN* node)//求二叉树的高度
{
	if (node == NULL)//终止
		return 0;
	
	//return GetTreeHeight(node->left) > GetTreeHeight(node->right) ?
	//	1 + GetTreeHeight(node->left) : 1 + GetTreeHeight(node->right);//这样实现会先判断大小再计算值,导致重复实现递归
	//上述递归调用太多,导致反复调用,成等比增长,所以就会导致花费时间更久

	int left = GetTreeHeight(node->left);//左子树的高度
	int right = GetTreeHeight(node->right);//右子树的高度
	return left > right ? left + 1 : right + 1;//返回更大的高度

}

 

求二叉树第K层节点个数

int GetKNode(TN* node,int k)
{
	//停止条件
	if (k == 1 && node != NULL)//因为传过来的节点就看作第一层,所以k==1就是所求
		return 1;
	if (node == NULL)
		return 0;
	return GetKNode(node->left, k - 1) + GetKNode(node->right, k - 1);
    //等价转换成 左子树的k-1层节点数+右子树的k-1层节点数
}

 

查找二叉树中值为x的节点

TN* GetPointNode(TN* node, int x)
{
	if (node == NULL)
		return NULL;
	if (node->val == x)//相同就直接返回节点指针
		return node;

	//if (GetPointNode(node->left, x) != NULL)
	//	return GetPointNode(node->left, x);
	//if (GetPointNode(node->right, x) != NULL)
	//	return GetPointNode(node->right, x);
	//消耗过大
    
    //以下是该节点既不是空也不是指定节点的情况,继续向下找:
	TN* leftnode = GetPointNode(node->left, x);//记录下来
	if (leftnode != NULL)//判断左子树是否存在指定节点
		return leftnode;

	TN* rightnode = GetPointNode(node->right, x);
	if (rightnode != NULL)//右子树
		return rightnode;
	return NULL;//该节点处没找到并且左右节点也不是

}

 

 二叉树的构建

typedef struct TN
{
    char val;
    struct TN* left;
    struct TN* right;

}TN;
TN* Malloc(char c)
{
    TN* newnode = (TN*)malloc(sizeof(TN));
    assert(newnode);
    newnode->val=c;
    newnode->left=newnode->right=NULL;
    return newnode;
}
void GetTree(char* p,TN** node,int* i)//按照前序存储数据
{
    //这里也可以不用考虑越界的情况,因为只要数据给的是二叉树的结构就没问题
    if(p[*i]=='#')
    {
        (*i)++;
        return;
    }
    TN* newnode = Malloc(p[*i]);
    *node = newnode;
    (*i)++;
    GetTree(p,&(*node)->left,i);
    GetTree(p,&(*node)->right,i);

}
int main() {
    
    char arr[100];
    gets(arr);//存放输入的字符
    TN* node=NULL;//开辟头结点
    int i=0;//数组下标
    GetTree(arr,&node,&i);//想要改变数据就要传地址

    return 0;
}

二叉树的销毁

void Destroy(TN* node)
{
	//如果按照前序遍历就会导致后面的数据找不到了
	//所以就考虑从后续遍历,即从后面开始销毁数据
	if (node == NULL)
		return;
	Destroy(node->left);
	Destroy(node->right);
	free(node);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RocketMQ】RocketMQ标签、过滤及消息重复消费

【RocketMQ】RocketMQ标签、过滤及消息重复消费 文章目录 【RocketMQ】RocketMQ标签、过滤及消息重复消费1. 标签(Tag)1.1 示例 2. 键(Keys)2.1 示例 3. 消息重复消费3.1 示例 参考文档: 官方文档 1. 标签(Tag) Topic 与 Tag 都是业务上用来归类的标识&#xff0c…

Vue学习2

文章目录 引入vue的分析render修改脚手架的默认配置修改步骤 refpropsmixin局部:全局总结 插件(install)总结 scoped案例总结浏览器本地存储保存读取删除清空 组件自定义事件绑定传递数据的两种方式解绑坑this使用原生的总结 全局事件总线消息…

攻防世界 mfw(Git源码泄露与命令执行漏洞)

目录 Git 源码泄露: 1、strpos() 函数 2、assert()函数 3、file_exists() 函数 4、die() 函数 代码审计: 命令执行漏洞: 打开链接 在About里发现网站是使用Git、PHP、Bootstrap搭建的 使用dirsearch扫一下 从结果可以看出确实存在.git …

在Centos7.9中安装postgresql15最新版本_参考官网说明安装---PostgreSQL工作笔记002

现在我要实现利用nifi同步,postgresql中的增量数据,也就是如果postgresql中出现增删改数据的时候,数据要自动同步到我们远程的mysql数据库中. 又难到我了...首先:去安装postgresql在centos7.9中,之所以在centos7.9中又安装了一遍,因为,我的大体思路,是利用postgresql的逻辑复制…

搭建flask后端和微信小程序前端

目录 一、准备工作 (1)我的前端代码 (2)我的后端代码 (3)后端运行成功的截图 (4)前端运行成功的截图 (5)整体运行成功的截图 二、部署后端 &#xff08…

【C++】什么是函数模板/类模板?

文章目录 一、函数模板1.什么是函数模板?2.函数模板格式3.函数模板原理4.函数模板实例化(1)隐式实例化(2)显示实例化 二.类模板1.类模板定义格式2.类模板的实例化 总结 一、函数模板 1.什么是函数模板? 函…

VBA之正则表达式(42)-- 提取代码中变量名称

实例需求:待处理代码段如下所示,现在需要提取其中的变量名称。 Public pFactor As Integer Sub TestCode() Dim reg As New RegExp, a As Workbook Dim ms As VBScript_RegExp_55.MatchCollection Dim m As VBScript_RegExp_55.Match Dim i, j Dim x1, y…

记一次udp服务性能优化经历

目录 概述磁盘io网络io减少重复计算减少内存复制减少互斥锁 概述 手上有个go项目,接收udp信息(主要是syslog和snmp trap)并查询设备信息,将信息结构化(设备ip名称,匹配了什么规则之类的)后发送…

生态系统NPP及碳源、碳汇模拟(土地利用变化、未来气候变化、空间动态模拟)

前言 由于全球变暖、大气中温室气体浓度逐年增加等问题的出现,“双碳”行动特别是碳中和已经在世界范围形成广泛影响。碳中和可以从碳排放(碳源)和碳固定(碳汇)这两个侧面来理解。陆地生态系统在全球碳循环过程中有着…

综述:图像分割

综述 图像分割(segmentation、cut)指的是将数字图像划分成多个图像子区域的过程。 在实际场景中具有诸多重要应用 在广义的图像分割中,传统方法和深度方法对于分割有不同的定义。 传统方法:对于图像进行区域划分,核心问题在于:区…

饿了么太狠:面个高级Java,抖这多硬活、狠活(饿了么面试真题)

前言: 在40岁老架构师尼恩的(50)读者社群中,经常有小伙伴需要面试饿了么、 头条、美团、阿里、京东等大厂。有很多的小伙伴,完成了人生的逆袭,拿到了高端的offer。 最近一个6年经验的小伙伴,年…

linux进程间通信(共享内存)

共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进 程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可 以将同一段物理内存连接到他们自己的地址空间中&#xff0…

win10 编译 openssl

环境:系统win10 编译器:VS2015 准备: 一、openssl下载 官网:www.openssl.org 安装Perl,安装NASM. cmd下运行perl --version得出下面信息就说明安装perl安装成功. nasm --version得出下面信息,说明nasm安装成功 我以vs2015为例: 打开这个终端,之所以打开这个是因…

适合每一个对高光谱技术感兴趣,并想用python进行实践的人

总结了高光谱遥感技术领域的基础原理与核心概念,采用编程语言复现经典数据处理和应用方法,追踪了最新的技术突破,在消化理解、触类旁通之后,用即使是遥感“小白”也容易接受的方式分享给你。 高光谱遥感学习的第一季:提…

HTTP的缓存机制是什么?

HTTP 缓存机制是一种在 Web 开发中常用的技术,它旨在提高性能和减少网络流量。通过缓存,可以避免不必要的网络请求,减少服务器负载,并加快页面加载速度。下面是关于 HTTP 缓存机制的详细介绍。 HTTP 缓存机制的基本原理是将 Web …

LNMP网站框架搭建(yum方式)

目录 一、Nginx的yum安装 1)搭建nginx相关的yum源 2)刷新yum仓库,安装启动nginx服务 二、mysql的 yum 安装 1)卸载一切与mysql有关的包 2)wget mysql相关的yum源 附加:第二种方式(与上…

单卡轻松打造 ChatGPT 竞争者“原驼”,QLoRA 革新大语言模型微调技术

出品人:Towhee 技术团队 作者:顾梦佳 由 OpenAI 推出的聊天机器人ChatGPT 爆火,带动 AI 受到了前所未有的关注。随之市面上也涌现出了各类开源的大语言模型(LLM),其中 LLaMA “羊驼系列”最受关注、最具潜力…

Vue实现订单确认界面禁止浏览器返回操作导致重复提交订单的问题

哈喽 大家好啊 最近遇到一个问题,就是在提交订单成功后的页面,然后用户去浏览器返回,就导致又提交了一次 然后就想到了如果提交成功页面,就阻止浏览器返回操作 主要实现如下: 1.在mounted的钩子函数: 2.…

每日一练 | 华为认证真题练习Day50

1、SWA和SWB的MAC地址表中,MAC地址、VLAN、端口对应关系正确的有?(多选) 2、PPP帧格式中的Flag字段的取值为? A. 0xFF B. 0x7E C. 0xEF D. 0x8E 3、ICMP报文不包含端口号,所以无法使用NAPT。 A. 对 B…

[ Term ] 你真的了解 UTC 时间吗?它和 GMT 时间的区别是什么?

什么是 GMT 和 UTC,他们之间的区别是什么? GMT(Greenwich Mean Time)和UTC(Coordinated Universal Time)是两个不同的时间标准,但它们非常相似并且通常被混淆使用,那他们之间的区别在…