数据结构:二叉树的链式结构及相关算法详解

目录

一.链式结构的实现

1.二叉树结点基本结构,初始化与销毁:

二.链式结构二叉树的几种遍历算法

1.几种算法的简单区分:

2.前序遍历:

3.中序遍历:

4.后序遍历:

5.层序遍历(广度优先遍历BFS):

三.链式二叉树的几种使用

1.计算树的结点个数:

2.计算树的叶子结点个数:

3.计算树的第k层结点个数:

4.计算树的深度:

5.查找结点为X的结点:

6.判断二叉树是否为完全二叉树:


一.链式结构的实现

1.二叉树结点基本结构,初始化与销毁:

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

(2)二叉树结点的初始化与树的创建:

#include"Tree.h"
//结点的初始化
BTNode* buyNode(char x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));node->data = x;node->left = node->right = NULL;return node;
}
//树结构的创建
BTNode* creatBinaryTree()
{BTNode* nodeA = buyNode('A');BTNode* nodeB = buyNode('B');BTNode* nodeC = buyNode('C');BTNode* nodeD = buyNode('D');BTNode* nodeE = buyNode('E');BTNode* nodeF = buyNode('F');nodeA->left = nodeB;nodeA->right = nodeC;nodeB->left = nodeD;nodeC->right = nodeE;nodeC->right = nodeF;
}

(3)链式二叉树的销毁:

(使用后序遍历,我在下文会写出具体操作)

void BinaryTreeDestory(BTNode** root)
{//这里因为要改变根节点,应该传入的是根节点的地址,所以得拿二级指针接收//递归出口if ((*root) == NULL){return;}//自叶向根方向的释放//如果先释放的话,就找不到叶子节点了BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;
}


二.链式结构二叉树的几种遍历算法

1.几种算法的简单区分:

举一个普遍的例子具体说明一下,如图:

2.前序遍历:

简单说明一下前序遍历的基本逻辑:

假设一个树:   

    A
   / \
  B   C
 / \
D   E

那么其遍历过程为:
访问根节点  A 。
递归遍历左子树(以  B  为根):
访问  B 
递归遍历左子树(以  D  为根):
访问  D 
递归遍历右子树(以  E  为根):
访问  E 
递归遍历右子树(以  C  为根):
访问  C 

//前序遍历
void preOrder(BTNode* root)
{//头 左 右//递归出口if (root == NULL){printf("NULL");return;}printf("%c",root->data);preOrder(root->left);preOrder(root->right);
}

3.中序遍历:

void inOrder(BTNode* root)
{//左 头 右//递归出口if (root == NULL){printf("NULL");return;}inOrder(root->left); //注意别调用错了,调用中序的printf("%c", root->data);inOrder(root->right);
}

4.后序遍历:

void postOrder(BTNode* root)
{//递归出口if (root == NULL){printf("NULL");return;}postOrder(root->left);postOrder(root->right);printf("%c", root->data);
}

总结:如上述所示,前中后序遍历的代码共同点也是相当明显了,主要就是递归顺序左右子树的顺序不同而影响的代码输出顺序不同,其根本上来说就是函数栈帧的不断进行嵌套式的创建与销毁,如果挨个遍历可能会显得比较复杂,但通过代码所示的这种递归算法,前中后序的遍历实现也显得十分简洁明了

但还有一点尤其需要注意:就是不同于层序遍历,我上面所说的三种遍历都属于深度优先遍历(DFS),而层序遍历却属于广度优先遍历,关于这两大类遍历的优劣我会在实现层序遍历后作详细介绍

5.层序遍历(广度优先遍历BFS):

思路:

借助数据结构:队列,先通过根节点入队列,再循环判断队列是否为空,不为空则取队头然后出队头,并将队头结点的左右孩子入队列

(由于后续层序遍历的实现需要用到好些队列的知识,所有我先将队列的一些简单用法附在下面,需要的可以稍微看看)

//定义结点结构
typedef int QDataTpe;
typedef struct QueueNode
{struct QueneNode* next;QDataTpe data;
}QueueNode;//定义队列的结构
typedef struct Queue
{QueueNode* phead;//队头QueueNode* ptail;//队尾int size;
}Queue;BTNode* buyNode(char x);//队列初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}//入队(从队尾入)
void QueuePush(Queue* pq, QDataTpe x)
{assert(pq);//申请一个结点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc failed");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空,newnode是对头也是队尾if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else //队列非空,尾插{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}pq->size++;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == 0;
}//出队(从队头出)
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));//只有一个结点的情况下,要把队尾和队头的两个指针都考虑到if (pq->phead == pq->ptail){free(pq->ptail);pq->phead = pq->ptail = NULL;}QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;  pq->size--;
}//取队头数据
QDataTpe QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
//取队尾数据
QDataTpe QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}//队列的销毁
void QueueDestory(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;
}//取队列元素个数
int QueueSize(Queue* pq)
{return pq->size;
}
//层序遍历
void LeveIOrder(BTNode* root)
{Queue q;//创建一个队列QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头出队头,打印结点值BTNode* top = QueueFront(&q);QueuePop(&q);printf("%c", top->data);//将队头结点的非空左右孩子结点入队列if (top->left){QueuePush(&q, top->left);}if (top->right){QueuePush(&q, top->right);}QueuDestroy(&q);}}

小结: 
DFS 优点(深度优先遍历)
节点少时效率高,节省内存
适合需要“全探索”或“找到任意解即可”的场景(如迷宫路径)

 
DFS 缺点
可能陷入无限循环(需记录已访问节点)
不保证最短路径(除非剪枝优化)


BFS 优点 (广度优先遍历)
保证找到最短路径(无权图中)
层级分明,易于理解

 
BFS 缺点
内存占用大(需存储整个层级节点)
不适合大规模数据或深层结构(如树深度极大)


三.链式二叉树的几种使用

1.计算树的结点个数:

法一:把size作为一个函数的形参,然后把这个树遍历一遍,每遍历一个节点就size(节点个数)加一,但需要注意的是,需要传入size的地址才能改变size的值

void BinaryTreeSize(BTNode* root,int* size)
{if (root == NULL){return;}(*size)++;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right, size);
}

法二:递归,  节点个数=左子树节点个数+右子树节点个数,所以我们以此为基础递归就可以了

int BinaryTreeSize(BTNode* root)
{//节点个数=左子树节点个数+右子树节点个数//递归出口if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

2.计算树的叶子结点个数:

叶子结点:即没有左右孩子结点的结点4

int BinaryTreeLeafSize(BTNode* root)
{//递归出口if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3.计算树的第k层结点个数:

如上图,当k=1时就是最底层结点,因此从最底层往上嵌套遍历就可以实现

int BinaryTreeLeafSize(BTNode* root)
{//递归出口if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

4.计算树的深度:

int BinaryTreeDeep(BTNode* root)
{//计算树的深度if (root == NULL){return 0;}int lefDep = BinaryTreeDeep(root->left);int rigDep = BinaryTreeDeep(root->right);return 1 + (lefDep > rigDep ? lefDep : rigDep);
}

5.查找结点为X的结点:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//递归出口if (root == NULL){return 0;}if (root->data == x){return root;}//代码走到这里证明根节点并不是我们要找的结点//接下来是左右子树各自分开递归搜查BTNode* left = BinaryTreeFind(root->left, x);if (left)//由于函数最后返回NULL,所以如果这个if条件可以进入就足以说明找到了需要的结点{return root;}BTNode* right = BinaryTreeFind(root->right, x);if (right){return root;}return NULL;
}

6.判断二叉树是否为完全二叉树:

// 判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,出队头BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}//队头结点的左右孩子入队列QueuePush(&q, top->left);QueuePush(&q, top->right);}//队列不为空,继续取队头出队头//1)队头存在非空结点----非完全二叉树//2)队头不存在非空结点----完全二叉树while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

第一个while是为了验证根结点,如果根结点为空,直接返回false,如果根结点不为空,就将树里的结点循环入队列然后继续将子结点入队列并且判断其是否为空,同样的第一次循环的的结束条件是当出现空为止,因此,当来到第二次循环时,树的结点里说明已经第一次循环出了空结点,而当树是完全二叉树时,第二次循环之后队列里应该全是空结点,因此,只要当第二次循环里出现非空结点时,就可以判断其时非完全二叉树(这题思路比较复杂,可以多想一会)

欧克,本次关于链式二叉树的知识点就到此为止了,相关的题目我也会在未来附上

ok,全文终

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

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

相关文章

动态规划/贪心算法

一、动态规划 动态规划 是一种用于解决优化问题的算法设计技术,尤其适用于具有重叠子问题和最优子结构性质的问题。它通过将复杂问题分解为更简单的子问题,并保存这些子问题的解以避免重复计算,从而提高效率。 动态规划的核心思想 最优子结…

【实战篇】【深度解析DeepSeek:从机器学习到深度学习的全场景落地指南】

一、机器学习模型:DeepSeek的降维打击 1.1 监督学习与无监督学习的"左右互搏" 监督学习就像学霸刷题——给标注数据(参考答案)训练模型。DeepSeek在信贷风控场景中,用逻辑回归模型分析百万级用户数据,通过特征工程挖掘出"凌晨3点频繁申请贷款"这类魔…

软考中级-数据库-3.2 数据结构-数组和矩阵

数组 一维数组是长度固定的线性表,数组中的每个数据元素类型相同。n维数组是定长线性表在维数上的扩张,即线性表中的元素又是一个线性表。 例如一维数组a[5][a1,a2,a3,a4,a5] 二维数组a[2][3]是一个2行2列的数组 第一行[a11,a12,a13] 第二行[a21,a22,a23…

android亮灭屏流程分析

前言 亮灭涉及的东西非常多,因此单独写一个文档,进行详细说明,亮灭屏包括的东西不只是亮灭屏,还包括亮度调节、屏幕状态变化等东西。本文仅作学习使用,不涉及商业,侵权请联系删除。 framework层的学习链接…

V4L2框架基础

一、V4L2视频设备驱动基础 1.V4L2是专门为Linux设备设计的整合视频框架(其主要核心在Linux内核,相当于Linux操作系统上层的视频源捕获驱动框架)。为上层访问系统底层的视频设备提供一个统一的标准接口。V4L2驱动框架能够支持多种类型&#x…

C# 多线程

概述 进程和线程 进程:指在系统中运行的一个应用程序。 线程:进程中的一个执行任务。一个进程至少有一个线程,一个进程可以有多个线程,多个线程可共享数据。 多线程 多线程:在一个程序中同时运行多个线程&#xff0…

突破光学成像局限:全视野光学血管造影技术新进展

全视野光学血管造影(FFOA)作为一种实时、无创的成像技术,能够提取生物血液微循环信息,为深入探究生物组织的功能和病理变化提供关键数据。然而,传统FFOA成像方法受到光学镜头景深(DOF)的限制&am…

Deepgram推出Nova-3 Medical,AI语音转录助力医疗行业

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

centOS 环境 安装redis方法

一、准备centOS环境 参考文章:Hyper-V 安装CentOS7_代码草率了的博客-CSDN博客 二、redis官网 地址:Download | Redis 演示版本为?redis-5.0.14.tar.gz 三、redis源码编译 登录后创建soft目录 进入目录使用wget下载所需资源包 命令:w…

[51 单片机] --串口编程

1,通讯方式基本概念 1,按照 --> 数据传送方式串行通讯:使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度,串行通信的特点:传输线少,长距离传送时成本…

Golang的微服务服务发现机制

## 1. Golang微服务服务发现机制 微服务架构已经成为当今软件开发的主流趋势,它能将复杂的单体应用拆分成小而独立的服务单元,实现更快的开发、部署和扩展。在微服务架构中,服务发现是非常重要的一环,它能够实现服务之间的自动发现…

Python 创建地形图

原始地 DEM。 火山口湖 (OR) 区域的起始 DEM。数据来自 NASA DEM 本身非常美丽,但我们先进行分层。 将自定义色彩图应用于 DEM 对于我在 ArcGIS Pro 版本中所做的初始高程样式着色,我使用了“高程 #7”。在 matplotlib 中可用的标准颜色图中&#xff…

《Operating System Concepts》阅读笔记:p180-p187

《Operating System Concepts》学习第 20 天,p180-p187 总结,总计 8 页。 一、技术总结 1.forke-join A strategy for thread creation in which the main parent thread creates (forks) one or more child threads and then waits for the children…

文心4.5,大模型下半场的野心之作

2025年开年,全球大模型竞赛进入白热化阶段。2月28日,百度宣布其文心大模型4.5将于3月16日正式上线,强调其原生多模态与深度思考能力,并计划于6月30日开源。这一动作不仅标志着百度技术路线的重大转向,更被视为中国大模…

transformer架构解析{前馈全连接层,规范化层,子层(残差)连接结构}(含代码)-4

目录 前言 前馈全连接层 学习目标 什么是前馈全连接层 前馈全连接层的作用 前馈全连接层代码实现 规范化层 学习目标 规范化层的作用 规范化层的代码实现 子层(残差)连接结构 学习目标 什么是子层(残差)连接结构 子层连…

Django视图与URLs路由详解

在Django Web框架中,视图(Views)和URLs路由(URL routing)是Web应用开发的核心概念。它们共同负责将用户的请求映射到相应的Python函数,并返回适当的响应。本篇博客将深入探讨Django的视图和URLs路由系统&am…

串口通讯基础

第1章 串口的发送和接收过程 1.1 串口接收过程 当上位机给串口发送(0x55)数据时,MCU的RX引脚接受到(0x55)数据,数据(0x55)首先进入移位寄存器。数据全部进入移位寄存器后,一次将(0x55)全部搬运…

kakfa-3:ISR机制、HWLEO、生产者、消费者、核心参数负载均衡

1. kafka内核原理 1.1 ISR机制 光是依靠多副本机制能保证Kafka的高可用性,但是能保证数据不丢失吗?不行,因为如果leader宕机,但是leader的数据还没同步到follower上去,此时即使选举了follower作为新的leader&#xff…

基于Linux系统的物联网智能终端

背景 产品研发和项目研发有什么区别?一个令人发指的问题,刚开始工作时项目开发居多,认为项目开发和产品开发区别不大,待后来随着自身能力的提升,逐步感到要开发一个好产品还是比较难的,我认为项目开发的目的…

STM32——DMA详解

目录 一:DMA简介 二:DMA基本结构 三:DMA实现过程 1.框图 2.DMA进行转运的条件 四:函数 一:DMA简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设存储器或者存储器和存储器之间的高速数据传输&…