数据结构 | 二叉树(基本概念、性质、遍历、C代码实现)

1.树的基本概念

树是一种 非线性 的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根结点没有前驱结点 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm
其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此, 树是递归定义 的。
结点的度:一个结点含有的子树的个数称为该结点的度;
叶结点:度为0的结点称为叶结点;
分支结点:度不为0的结点; 
父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点;
子结点:一个结点含有的子树的根结点称为该结点的子结点;
兄弟结点:具有相同父结点的结点互称为兄弟结点;
树的度:一棵树中,最大的结点的度称为树的度; 
结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
树的高度或深度:树中结点的最大层次; 

2.二叉树的基本概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成
注意:二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

2.1特殊的二叉树

满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

完全二叉树

特征:前n-1层都是满的,最后一层可以不满,但最后一层从左到右必须是连续的。
ps: 满二叉树是一种特殊的完全二叉树。

3.二叉树的性质

1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有 n0=n2 +1
4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度h=log2(n+1)
5. 对于完全二叉树:
双亲序号:(i-1)/2  (i为子节点序号)
左孩子序号:2i+1  (i为双亲结点序号)
右孩子序号:2i+2 (i为双亲结点序号)
练习:一个具有767个结点的完全二叉树,其叶子结点个数为()
A 383
B 384
C 385
D 386
答案:B
详解:设有度为2节点n2个, 度为1节点n1个,度为0节点n0个,
767=n2+n1+n0
n2=n0-1
由上面两式可得:
767=n1+2n0-1
768=n1+2n0
由于2n0必为偶数,768为偶数,可得:n1为偶数
且完全二叉树中n1只能是1或0
因此n1=0
768=2n0
n0=384

4 .二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1. 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2.链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是
链表中每个结点由三个域组成,数据域左右指针域,左右指针分别用来给出该结点左孩子和右孩子所
在的链结点的存储地址 。链式结构又分为二叉链和三叉链。本文主要针对二叉链。

5.二叉树的遍历

5.1 前序、中序以及后序遍历

1. 前序遍历——根节点 左子树 右子树
2. 中序遍历——左子树  根节点 右子树
3. 后序遍历——左子树 右子树  根节点

5.2 层序遍历

层序遍历:一层一层地往下遍历

6.二叉树代码实现

思路

前序/中序/后序遍历

递归思想:将当前的大问题拆解成小问题

以前序遍历为例:

当前问题——打印根,打印左子树,打印右子树

子问题——如图

递归返回条件——root==NULL

前序遍历代码

//前序遍历 根节点 左节点 右节点
void BinaryTreePrevOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}printf("%d ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}

中序遍历代码

void BinaryTreeInOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}BinaryTreeInOrder(root->left);printf("%d ", root->data);BinaryTreeInOrder(root->right);
}

后序遍历代码

void BinaryTreePostOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%d ", root->data);
}

节点个数/叶子节点个数/树高/第k层叶子数

1.节点个数

递归思想:

情况1:空,0个

情况2:不为空,左子树+右子树+1

2.叶子节点个数

情况1:空,返回0

情况2:只有一个结点,返回1

情况3:左子树+右子树

3.树的高度

情况1:空,返回0

情况2:左子树和右子树高度中大的值+1

4.第k层叶子数

情况1:空,返回0

情况2:非空,k==1,返回1

情况3:非空,k>1,左子树第k-1层+右子树第k-1层

int BinaryTreeSize(BTNode* root) {if (root == NULL) {return 0;}if (root->left == NULL && root->right == NULL) {return 1;}return BinaryTreeSize(root->left) + BinaryTreeSize(root->right)+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);
}int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ?leftHeight + 1 : rightHeight + 1;
}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);
}

查找值为x的节点

递归思想

情况1:空,返回NULL

情况2:不为空,根值为x,返回根节点

情况3:不为空,根值不为x,查找左子树,有则返回

             左子树中无,查找右子树,有则返回

             右子树中也无,返回空

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {BTNode* ret = NULL;if (root == NULL) {return NULL;}if (root->data == x) {ret = root;return ret;}if (BinaryTreeFind(root->left, x) != NULL) {ret = BinaryTreeFind(root->left, x);}if (BinaryTreeFind(root->right, x) != NULL) {ret = BinaryTreeFind(root->right, x);}
}

层序遍历/完全二叉树

层序遍历

1.根进队列

2.节点出队列时,该节点的子节点(非空)进队列

3.当队列为空时,循环结束

完全二叉树

1.进行层序遍历,空也进队列

2.遇到第一个空节点,开始判断,后面全空就是完全二叉树,后面有非空就不是完全二叉树

void BinaryTreeLevelOrder(BTNode* root) {if (!root) {return;}Queue q;QueueInit(&q);QueuePush(&q, root);while (QueueSize(&q) > 0) {BTNode* head = QueueFront(&q);if (head->left) {QueuePush(&q, head->left);}if (head->right) {QueuePush(&q, head->right);}printf("%d", head->data);QueuePop(&q);}QueueDestroy(&q);
}bool BinaryTreeComplete(BTNode* root) {if (!root) {return;}Queue q;QueueInit(&q);QueuePush(&q, root);while (QueueSize(&q) > 0) {BTNode* head = QueueFront(&q);if (head == NULL) {break;}QueuePush(&q, head->left);QueuePush(&q, head->right);QueuePop(&q);}while(!QueueEmpty(&q)){BTNode* head = QueueFront(&q);if (head) {QueueDestroy(&q);return false;}QueuePop(&q);}QueueDestroy(&q);return true;
}

代码汇总

binarytree.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate();
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

binarytree.c

#define _CRT_SECURE_NO_WARNINGS
#include "binarytree.h"
#include "queue.h"BTNode* BuyNode(BTDataType x) {BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL) {perror("malloc fail!");}newnode->left = NULL;newnode->right = NULL;newnode->data = x;return newnode;
}BTNode* BinaryTreeCreate() {BTNode* Node1 = BuyNode(1);BTNode* Node2 = BuyNode(2);BTNode* Node3 = BuyNode(3);BTNode* Node4 = BuyNode(4);BTNode* Node5 = BuyNode(5);BTNode* Node6 = BuyNode(6);BTNode* Node7 = BuyNode(7);Node1->left = Node2;Node1->right = Node3;Node2->left = Node4;Node2->right = Node5;Node3->left = Node6;//Node6->left = Node7;return Node1;//返回根节点
}
//前序遍历 根节点 左节点 右节点
void BinaryTreePrevOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}printf("%d ", root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}void BinaryTreeInOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}BinaryTreeInOrder(root->left);printf("%d ", root->data);BinaryTreeInOrder(root->right);
}void BinaryTreePostOrder(BTNode* root) {if (root == NULL) {printf("N ");return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%d ", root->data);
}int BinaryTreeSize(BTNode* root) {if (root == NULL) {return 0;}if (root->left == NULL && root->right == NULL) {return 1;}return BinaryTreeSize(root->left) + BinaryTreeSize(root->right)+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);
}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);
}int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ?leftHeight + 1 : rightHeight + 1;
}BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {BTNode* ret = NULL;if (root == NULL) {return NULL;}if (root->data == x) {ret = root;return ret;}if (BinaryTreeFind(root->left, x) != NULL) {ret = BinaryTreeFind(root->left, x);}if (BinaryTreeFind(root->right, x) != NULL) {ret = BinaryTreeFind(root->right, x);}
}void BinaryTreeLevelOrder(BTNode* root) {if (!root) {return;}Queue q;QueueInit(&q);QueuePush(&q, root);while (QueueSize(&q) > 0) {BTNode* head = QueueFront(&q);if (head->left) {QueuePush(&q, head->left);}if (head->right) {QueuePush(&q, head->right);}printf("%d", head->data);QueuePop(&q);}QueueDestroy(&q);
}bool BinaryTreeComplete(BTNode* root) {if (!root) {return;}Queue q;QueueInit(&q);QueuePush(&q, root);while (QueueSize(&q) > 0) {BTNode* head = QueueFront(&q);if (head == NULL) {break;}QueuePush(&q, head->left);QueuePush(&q, head->right);QueuePop(&q);}while(!QueueEmpty(&q)){BTNode* head = QueueFront(&q);if (head) {QueueDestroy(&q);return false;}QueuePop(&q);}QueueDestroy(&q);return true;
}void BinaryTreeDestory(BTNode* root) {if (root==NULL) {return;}BinaryTreeDestory(root->left);BinaryTreeDestory(root->right);free(root);
}

在实现层序遍历时,会使用到队列。但由于C语言中没有现成的数据结构队列可以直接使用,需要自己实现。

queue.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef struct BinaryTreeNode* QDataType;typedef struct QListNode{struct QListNode* next;QDataType data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

queue.c

#define _CRT_SECURE_NO_WARNINGS
#include "queue.h"
// 初始化队列 
void QueueInit(Queue* q) {assert(q);q->phead = q->ptail = NULL;q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data) {assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {perror("malloc fail!");exit(1);}else {newnode->data = data;newnode->next = NULL;if (q->ptail == NULL) {q->phead = q->ptail = newnode;q->size++;}else {q->ptail->next =newnode;q->ptail = newnode;q->size++;}}
}
// 队头出队列 
void QueuePop(Queue* q) {assert(q);assert(q->size != 0);if (q->phead->next == NULL) {free(q->ptail);q->ptail = q->phead = NULL;q->size--;}else {QNode* next = q->phead->next;free(q->phead);q->phead = next;q->size--;}
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q) {assert(q);assert(q->size > 0);return q->phead->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {assert(q);assert(q->size > 0);return q->ptail->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {assert(q);return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q) {assert(q);return !QueueSize(q);
}
// 销毁队列 
void QueueDestroy(Queue* q) {assert(q);while (q->size) {QueuePop(q);}q->phead = NULL;q->ptail = NULL;
}

7.堆及堆排序及TopK问题

详见我的另一篇文章~(TopK问题待更)

数据结构 | 详解二叉树——堆与堆排序

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

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

相关文章

五分钟“手撕”链表

为了提高大家的学习效率&#xff0c;我把代码放开头&#xff0c;供查阅。 目录 一、链表的实现代码 二、什么是链表 三、链表的分类 四、链表的常见操作 插入 删除 五、Java自带的LinkedList 两个构造方法 一些常用方法 六、LinkedList的遍历 七、ArrayList和Linke…

华媒舍:10种欧洲地区媒体发稿推广技巧

1.了解欧洲地区媒体自然环境必须掌握欧洲地区媒体的发稿推广方法&#xff0c;首先要对欧洲地区媒体自然环境有一定的了解。包含不一样国家的主力媒体&#xff0c;他的阅读者人群、销售市场遮盖及其报导风格等。仅有熟悉媒体自然环境&#xff0c;才能更好的制订营销推广策略。 …

Web----网络通讯部分

一、TCP和UDP的区别 TCP是一种面向连接的协议&#xff0c;它在传输数据之前会建立一条专用的通信连接。这意味着在数据传输过程中&#xff0c;两台计算机之间会有一条稳定的数据传输通道。因此&#xff0c;TCP可以保证数据传输的可靠性&#xff0c;但会带来一定的延迟。 UDP是…

Android VSYNC双Buffer与三Buffer渲染线程RenderThread(5)

Android VSYNC双Buffer与三Buffer渲染线程RenderThread&#xff08;5&#xff09; 手机自带的卡顿丢帧分析工具&#xff0c;柱状图&#xff1a; 帧的大体绘制过程&#xff1a; 帧绘制中的重要概念&#xff1a;BufferQueue 首先看一下 BufferQueue&#xff0c;BufferQueue 是一个…

Visual Studio Code 开发esp8266流程2Arduino 配置 nodemcu

http://arduino.esp8266.com/stable/package_esp8266com_index.json Arduino: Library Manager

第二十五章CSS中的技巧(导航栏、下拉列表)

1.CSS精灵 1.什么是CSS精灵 英文叫法 CSS sprites&#xff0c;通常被解释为“CSS图像拼合”或“CSS贴图定位”;其实就是把网页中一些背景图片整合到一张图片文件中&#xff0c;再利用css“background-image”&#xff0c; “background-repeat”,“background-position”的组…

中国地质大学(武汉):23考研多专业接受调剂,24新增上机考试!中国地质大学(武汉)计算机考研考情分析!

中国地质大学&#xff08;武汉&#xff09;计算机学院成立于1985年&#xff0c;其前身为地矿部武汉计算站。经过近二十年的努力&#xff0c;计算机学院不断发展壮大。现设有计算机应用、计算机软件、网络与系统结构、信息安全四个教研室&#xff1b;拥有湖北省计算机应用技术重…

最大回撤概念与计算

一、最大回撤&#xff0c;是指的最大下跌的值&#xff1a; 1、即所有下跌趋势中&#xff0c;净值最低的点&#xff0c;与历史净值最高点直接的差值。 2、最大回撤取绝对值显示 二、如果有时间限制&#xff0c;则计算对应时间段内的最大回撤。 示意图如下&#xff1a; 三、举…

【Java面试】七、SpringMvc的执行流程、SpringBoot自动装配原理

文章目录 1、SpringMVC的执行流程1.1 视图阶段1.2 前后端分离阶段 2、SpringBoot自动配置原理3、框架常用的注解3.1 Spring的注解3.2 SpringMvc的注解3.3 SpringBoot的注解 4、面试 1、SpringMVC的执行流程 1.1 视图阶段 旧项目中&#xff0c;未前后端分离时&#xff0c;用到…

OAK相机如何将 YOLOv10 模型转换成 blob 格式?

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是Ashely。 专…

Microsoft Fabric 是什么?

最近半个月没有更新内容&#xff0c;原因是什么呢&#xff1f; 原因是花了两周的时间备考了一下"Microsoft Certified: Fabric Analytics Engineer Associate"的考试认证。 非常幸运考试通过了。 那什么是Microsoft Fabric 呢&#xff1f; Microsoft Fabric 是一个…

运筹学_4.整数规划

文章目录 引言4.1 分枝定界方法求解整数规划问题整数规划的分类整数规划解法概述分支定界法 4.2 0-1整数规划0-1整数规划的数学模型隐枚举法求解0-1规划问题 4.3 指派问题(分配问题)的匈牙利解法指派问题的数学模型指派问题的匈牙利解法 引言 规划中的决策变量(全部或部分)限制…

【备战蓝桥杯】蓝桥杯省一笔记:算法模板笔记(Java)

蓝桥杯 0、快读快写模板1、回文判定2、前缀和3、差分4、二分查找5、快速幂6、判断素数7、gcd&lcm8、进制转换9、位运算10、字符串常用API11、n的所有质因子12、n的质因子个数13、n的约数个数14、n阶乘的约数个数15、n的约数和16、阶乘 & 双阶乘17、自定义升序降序18、动…

2024盘古石初赛(服务器部分)

赛后总结 这次初赛就有20道服务器部分赛题&#xff0c;做的情况一般&#xff0c;错了5道题这样&#xff0c;主要原因就是出在第二个网站服务器没有重构起来 今天来复现一下 这次的服务器部分我直接用仿真仿起来就开找了 第一台IM前期配置 先把网配置好&#xff0c;然后ssh…

如此简单,一文带你玩转接口自动化上(Python + Pytest + Requests + Allure )

一. 前言 哈喽大伙们好&#xff0c;好久不见距离上次更新博客已经有一年之久了&#xff0c;这将近一年的时间小编主要的时间都花在了实习和24届校招上面了&#xff0c;最终也是收获满满&#xff0c;选择了一个还不错的offer&#xff0c;感谢一路走来的自己和身边朋友的帮助&…

基于Three.js实现的3D立方体动画

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于Three.js实现的3D立方体动画 应用场景 该代码段适用于需要在网页中创建交互式3D场景的场景。例如&#xff0c;可以用于展示产品、创建游戏或制作视觉效果。 基本功能 此代码段使用Three.js库创建了一个…

【机器学习】随机森林:深度解析与应用实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 随机森林&#xff1a;深度解析与应用实践引言1. 随机森林基础1.1 什么是随机森林…

Android更新优化 - 增量更新是如何节省用户时间和流量的

增量更新和全量更新 我想玩过大型手游的人都知道&#xff0c;手游的安装包非常大&#xff0c;因为资源图片众多。而你每次更新都把所有文件都更新下来&#xff0c;是非常耗时的&#xff0c;对吧。耗时是一个方面&#xff0c;有些人在户外开的是移动网络&#xff0c;动不动就几…

计算机组成原理·海明编码及其实验

前言&#xff1a;海明编码这一块在刚开始的时候没有弄懂&#xff0c;后面通过做实验、复习慢慢摸清了门道。在学习计算机组成原理的过程中&#xff0c;实验实践是很重要的&#xff0c;它会让你去搞清楚事情背后的原理&#xff0c;逼着你学会你没听懂的东西。这篇文章会从海明码…

Check Point 安全网关任意文件读取漏洞复现(CVE-2024-24919)

Check Point 安全网关任意文件读取漏洞复现(CVE-2024-24919) 1.漏洞描述 Check Point Security Gateways 是 Check Point Sofware 提供的一系列 网络安全Q解决方案。这些解决方案包括下一代防火墙(NGFW)、数据中心安全网关和 A1驱动的量子网关&#xff0c;旨在为企业提供针对…