深入理解二叉树:遍历、构建与性质探索的代码实现


在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路
🌅 有航道的人,再渺小也不会迷途。


在这里插入图片描述

文章目录

    • 前言
    • 一、二叉树的存储结构
    • 二、二叉树链式结构的实现
    • 三、二叉树的前、中、后续遍历(三种遍历)
    • 四、二叉树的层次遍历
    • 五、二叉树节点个数以及高度等
      • 5.1 二叉树节点个数
      • 5.2 二叉树叶子节点个数
      • 5.3 二叉树的高度
      • 5.4 二叉树第k层节点个数
      • 5.5 二叉树查找值为x的节点
    • 六、根据所给数组构建一颗二叉树
    • 七、二叉树的销毁
    • 八、判断二叉树是否是完全二叉树

前言

二叉树的相关概念和结构在上一章节已经有详细介绍,传送门:二叉树:数据结构中的灵魂

一、二叉树的存储结构

BTDataType 表示二叉树的节点存储元素的类型,BTNode 表示二叉树的节点,leftright 分别表示节点的左右孩子节点,data 表示节点存储的元素。

typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;

在这里插入图片描述


二、二叉树链式结构的实现

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
// 申请节点
BTNode* BuyTreeNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));assert(node);node->data = x;node->left = NULL;node->right = NULL;return node;
}//建树
BTNode* CreateTree()
{BTNode* node1 = BuyTreeNode(1);BTNode* node2 = BuyTreeNode(2);BTNode* node3 = BuyTreeNode(3);BTNode* node4 = BuyTreeNode(4);BTNode* node5 = BuyTreeNode(5);BTNode* node6 = BuyTreeNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式在本文后重点讲解。

在看二叉树基本操作前,再回顾下二叉树的概念,二叉树是

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
    在这里插入图片描述

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。


三、二叉树的前、中、后续遍历(三种遍历)

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

在这里插入图片描述

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

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

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

下面请看二叉树三种遍历方法的代码实现:

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

下面主要分析前序递归遍历,中序与后序图解类似。

前序遍历递归图解:
在这里插入图片描述
在这里插入图片描述


四、二叉树的层次遍历

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

在这里插入图片描述
二叉树的层次遍历通常是通过队列来实现的(这是因为我们需要利用队列先进先出的特性来保存之前被访问过的节点的孩子节点),这是一种广度优先搜索(BFS)的策略。下面是一个基本的实现思路

  1. 首先,我们需要一个队列来保存待处理的节点。一开始,队列中只有根节点
  2. 然后,进入一个循环,条件是队列不为空。在循环中,我们首先取出队列的第一个节点,并访问它(比如打印节点的值)。
  3. 接着,如果这个节点有左孩子,就把左孩子加入队列的末尾。然后,如果这个节点有右孩子,就把右孩子也加入队列的末尾。
  4. 最后,把刚才取出的节点从队列中移除,然后回到步骤2,继续处理队列中的下一个节点。

这个过程会一直持续到队列为空,也就是所有的节点都已经被访问过了。

辅助队列代码:

// 链式结构:表示队列 
typedef struct BinaryTreeNode* QDataType; //元素类型,此处类型是二叉树节点的指针
//队列的节点
typedef struct QListNode
{QDataType data;struct QListNode* next;
}QNode;
// 队列的结构 
typedef struct Queue
{QNode* front; //对头QNode* rear; //队尾int size; //队列元素个数
}Queue;// 初始化队列 
void QueueInit(Queue* q)
{assert(q);q->front = q->rear = NULL;q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc");return;}newnode->data = data;newnode->next = NULL;if (q->front == NULL){q->front = q->rear = newnode;}else{q->rear->next = newnode;q->rear = newnode;}q->size++;//队列元素加一
}
// 队头出队列 
void QueuePop(Queue* q)
{assert(q);//队列不为空assert(q->front);QNode* cur = q->front;q->front = q->front->next;//队列只有一个元素的情况,要考虑队尾的指针,防止野指针if (q->front == NULL)q->rear = NULL;free(cur);q->size--;//队列元素减一
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{assert(q);//队列不为空assert(q->front);return q->front->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{assert(q);return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{assert(q);return q->front == NULL;
}
// 销毁队列 
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->front;while (cur)//当cur为空时结束{QNode* next = cur->next;free(cur);cur = next;}q->front = q->rear = NULL;q->size = 0;
}

二叉树层次遍历代码:

// 非递归遍历二叉树
// 层序遍历(利用队列“先进先出”-->出去一个节点就立即带入此节点的左右节点进队)
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%c ", front->data);if (front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}QueueDestroy(&q);
}
// 一层一层访问节点
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);int levelSize = 1; //控制层数,第一层数据个数为1while (!QueueEmpty(&q)){// 一层一层出数据while (levelSize--){BTNode* front = QueueFront(&q); QueuePop(&q); printf("%c ", front->data); if (front->left) QueuePush(&q, front->left); if (front->right) QueuePush(&q, front->right); }printf("\n");levelSize = QueueSize(&q);}QueueDestroy(&q);
}

五、二叉树节点个数以及高度等

5.1 二叉树节点个数

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL) // 空树return 0;return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

5.2 二叉树叶子节点个数

// 二叉树叶子节点个数
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);
}

5.3 二叉树的高度

// 二叉树的高度
int BinaryTreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = BinaryTreeHeight(root->left); // 左子树的高度int rightHeight = BinaryTreeHeight(root->right); //右子树的高度return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

5.4 二叉树第k层节点个数

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL) // 空树return 0;if (k == 1) // 第一层return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

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

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}return NULL;
}

六、根据所给数组构建一颗二叉树

例: 通过前序遍历的数组 “ABD##E#H##CF##G##” 构建二叉树,“#”表示的是空格,空格字符代表空树。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) //这里pi的类型为int*是为了递归时确保值的完整性
{assert(*pi >= 0 && *pi < n);if (a[(*pi)] == '#'){(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode));if (root == NULL){perror("malloc error");return;}root->data = a[(*pi)++];root->left = BinaryTreeCreate(a, n, pi);root->right = BinaryTreeCreate(a, n, pi);return root;
}

该函数接受三个参数:a 是指向前序遍历数组的指针,n 是数组的长度,pi 是一个指向整数的指针,用于记录当前处理的元素索引。

  1. 函数首先使用断言 assert(*pi >= 0 && *pi < n) 确保索引 *pi 在合法范围内。然后判断当前元素是否为特殊字符 '#',如果是,则表示当前节点为空,将索引 *pi 加一后返回 NULL
  2. 如果当前元素不是特殊字符,则创建一个新的二叉树节点 root,并为其分配内存空间。如果内存分配失败,则打印错误信息并返回。
  3. 接下来,将当前元素的值赋给 root 节点的数据域 root->data,并将索引 *pi 加一。然后递归调用 BinaryTreeCreate 函数来构建 左子树右子树,分别赋值给 root->leftroot->right
  4. 最后,返回根节点 root。

在这里插入图片描述


七、二叉树的销毁

由于在遍历二叉树时,前序和中序都需要保存根节点,而后续遍历不用,故使用后续遍历递归销毁二叉树最简单。

// 二叉树销毁(利用后序遍历销毁,前中序遍历都需要保存根节点)
void BinaryTreeDestory(BTNode** root)
{assert(root);if (*root == NULL)return;BinaryTreeDestory(&(*root)->left); BinaryTreeDestory(&(*root)->right); free(*root);*root = NULL;
}

八、判断二叉树是否是完全二叉树

思路:利用队列来进行层序遍历,并在遍历过程中检查是否存在未被访问的节点。

  1. 初始化队列:首先,创建一个队列 q,并将根节点 root 加入队列,如果根节点存在的话。
  2. 层序遍历:然后,通过层序遍历的方式访问二叉树的每个节点。遍历过程中,如果遇到 NULL 节点,就跳出循环。
  3. 检查空节点:完成层序遍历后,再次检查队列中的节点。如果队列不为空队首节点不为 NULL,则说明存在非空节点未被访问,即该二叉树不是完全二叉树。函数返回 false
  4. 清理队列:如果上述检查中没有发现非空节点未被访问,则说明该二叉树是完全二叉树。在返回 true 之前,销毁队列以释放内存。
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);int levelSize = 1; //控制层数,第一层数据个数为1while (!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){QueueDestroy(&q); return false;}}QueueDestroy(&q);return true;
}

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

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

相关文章

【数据结构 07】AVL树

目录 一、二叉搜索树 二、AVL树 2.1 左单旋 2.2 右单旋 2.3 左右双旋 2.4 右左双旋 三、AVL.h 四、test.cpp 一、二叉搜索树 二叉搜索树&#xff0c;又称二叉排序树&#xff08;Binary Search Tree&#xff09;&#xff0c;相比于普通二叉树&#xff0c;BST的特性有&a…

UE5 C++ 读取本地图片并赋值到UI上

目录 结果图 节点样式 主要代码 调试代码 结果图 节点样式 主要代码 &#xff08;注释纯属个人理解&#xff0c;可能存在错误&#xff09; // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h&q…

Java面向对象详解

面向对象和面向过程的区别&#xff1a; 面向对象和面向过程都是对软件分析、设计和开发的一种思想&#xff0c;它指导着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言&#xff0c;Java是一种典型的面向对象语言。 面向过程适合简单、不需要协作的事务…

Optimism的挑战期

1. 引言 前序博客&#xff1a; Optimism的Fault proof 用户将资产从OP主网转移到以太坊主网时需要等待一周的时间。这段时间称为挑战期&#xff0c;有助于保护 OP 主网上存储的资产。 而OP测试网的挑战期仅为60秒&#xff0c;以简化开发过程。 2. OP与L1数据交互 L1&#xf…

探索智能巡检机器人深度学习的奥秘

机器人深度学习&#xff08;Robot Deep Learning&#xff09;是指利用深度学习技术&#xff0c;使机器人能够从大量数据中学习和提取特征&#xff0c;进而实现自主感知、决策和行动的能力。通过深度学习算法&#xff0c;机器人可以从传感器获取的数据中自动学习模式和规律&…

微信开放平台第三方开发,实现代小程序认证申请

大家好&#xff0c;我是小悟 微信小程序认证整体流程总共分为五个环节&#xff1a;认证信息填写、平台初审、管理员验证、供应商审核和认证成功。 服务商可以代小程序发起认证申请。平台将对认证基础信息进行初步校验。通过后&#xff0c;平台将向管理员微信下发模板消息&…

Redis(十)SpringBoot集成Redis

文章目录 连接单机mvnYMLController.javaRedisConfig.java 连接集群YML问题复现 RedisTemplate方式 连接单机 mvn <!--Redis--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</art…

SRC实战 | 信息泄露挖掘

本文由掌控安全学院 - 叴龙 投稿 1. 信息搜集 首先老语法先搜集一波&#xff0c;毕竟没有钓鱼和sg的能力&#xff0c;只能找注册站去挖挖了。 web.title”XX大学”&&web.body”忘记密码”&&web.body”注册” 2. 漏洞挖掘 这里找到一个可以注册网站接口&…

蓝桥杯 第 1 场 小白入门赛

目录 1.蘑菇炸弹 2.构造数字 3.小蓝的金牌梦 4.合并石子加强版 5.简单的LIS问题 6.期望次数 1.蘑菇炸弹 我们直接依照题目 在中间位置的数进行模拟即可 void solve(){cin>>n;vector<int> a(n1);for(int i1;i<n;i) cin>>a[i];int ans0;for(int i2;i…

XSS haozi靶场通关笔记

XSS靶场地址&#xff1a;alert(1) 靶场的要求是输出一个内容为1的弹窗&#xff1b;这个靶场限制了输入位置只能是input code&#xff1b;而且浏览器发送内容时会自动进行url编码&#xff1b;所以重点考察的是代码的分析和基础payload构造&#xff1b;一切完成在当前页面&#…

【数据结构】链表(单链表实现+测试+原码)

1.链表 1.1 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 现实中&#xff1a;链表就像是一列动车&#xff0c;一节连着一节 数据结构中的链表 注意: 1.从上图可看出…

React16源码: React中LegacyContext的源码实现

LegacyContext 老的 contextAPI 也就是我们使用 childContextTypes 这种声明方式来从父节点为它的子树提供 context 内容的这么一种方式遗留的contextAPI 在 react 17 被彻底移除了&#xff0c;就无法使用了那么为什么要彻底移除这个contextAPI的使用方式呢&#xff1f;因为它…

知识点积累系列(一)golang语言篇【持续更新】

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 知识点积累 系列文章的第一篇&#xff0c;记录golang语言相关的知识点 1.结构体的mapstructure是什么 mapstructure:"default" mapstructure是一个Go语言的库&#xff0c;用于将一个map中的值映射到…

C语言王道第八周一题

Description 初始化顺序表&#xff08;顺序表中元素为整型&#xff09;&#xff0c;里边的元素是 1,2,3&#xff0c;然后通过 scanf 读取一个元素&#xff08;假如插入的是 6&#xff09;&#xff0c;插入到第 2 个位置&#xff0c;打印输出顺序表&#xff0c;每个 元素占 3 个…

添加了gateway之后远程调用失败

前端提示500&#xff0c;后端提示[400 ] during [GET] to [http://userservice/user/1] 原因是这个&#xff0c;因为在请求地址写了两个参数&#xff0c;实际上只传了一个参数 解决方案&#xff1a;加上(required false)并重启所有相关服务

【程序员英语】【美语从头学】初级篇(入门)(笔记)Lesson13(买东西)(餐厅点餐事宜;询问有无座位;食物如何调理:牛排、咖啡等;菜单等相关)

《美语从头学初级入门篇》 注意&#xff1a;被 删除线 划掉的不一定不正确&#xff0c;只是不是标准答案。 文章目录 Lesson 13 At the Restaurant 在餐厅会话A会话B笔记餐厅询问有无座位&#xff1b;餐厅电话订座其他餐厅询问有无座位的问法 吸烟区与非吸烟区&#xff08;smo…

[力扣 Hot100]Day18 矩阵置零

题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 出处 思路 在原数组上直接操作势必会出现“冗余”的0&#xff0c;即原本[i,j]处不是0&#xff0c;例如由于i行的其他位置有0导致[i,j]…

Shell中的AWK

1.awk的工作原理 逐行读取文本&#xff0c;默认以空格或tab键为分隔符进行分隔&#xff0c;将分隔所得的各个字段保存到内建变量中&#xff0c;并按模式或者条件执行编辑命令。awk倾向于将一行分成多个"字段"然后再进行处理。awk信息的读入也是逐行读取的&#xff0…

day38_MySQL

今日内容 0 复习昨日 1 引言 2 数据库 3 数据库管理系统 4 MySQL 5 SQL语言 0 复习昨日 1 引言 1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。文件&…

删除有序数组中的重复项[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个非严格递增排列的数组nums&#xff0c;请你原地删除重复出现的元素&#xff0c;使每个元素只出现一次&#xff0c;返回删除后数组的新长度。元素的相对顺序应该保持一致。然后返回nums中唯一元素的个数。 考虑nums的唯一元素…