跳到主要内容
数据结构:二叉树详解与堆实现 | 极客日志
C 算法
数据结构:二叉树详解与堆实现 综述由AI生成 树和二叉树的基本概念、性质及存储结构,重点讲解了堆(Heap)的初始化、插入、删除操作及其在 TOP-K 问题中的应用。同时涵盖了二叉树的链式结构实现,包括前序、中序、后序及层序遍历,以及计算结点个数、高度、查找结点和判断完全二叉树等常用算法。内容包含 C 语言代码示例,适合学习数据结构基础。
kaikai 发布于 2026/3/30 更新于 2026/5/21 26 浏览1. 树
树是一种非线性的数据结构,它是由 n 个有限结点组成的一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树。
有一个特殊的结点,称为根结点 ,根节点没有前驱结点。
除了根结点之外,其余结点被分为 M 个互不相交的集合,其中每一个集合又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或者多个后继。因此,树是递归定义的。
树形结构中,⼦树之间不能有交集,否则就不是树形结构。
例如:
1.1 各种概念
父结点/双亲结点 :若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点。
子结点/孩子结点 :⼀个结点含有的⼦树的根结点称为该结点的⼦结点。
结点的度 :有几个孩子,就为几度。
树的度 :最大结点的度就为树的度。
叶子结点/终端结点 :度为 0 的结点就为叶子结点。
分支结点/非终端结点 :度不为 0 的结点。
兄弟结点 :具有相同的父结点。
结点的层次 :有几层层次就为多少。
树的高度/深度 :树的最大层次。
结点的祖先 :从根结点到该结点所经分支上的所有结点。
路径 :⼀条从树中任意节点出发,沿⽗节点 - ⼦节点连接,达到任意节点的序列。
子孙 :以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。
森林 :由多棵互不相交的树组成的。
1.2 树的表示
用孩子兄弟表示法:
struct TreeNode {
struct Node * child ;
struct Node * brother ;
int data;
};
用图片来进行分析:
1.3 应用 ⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。
2. 二叉树
2.1 概念与结构 一棵二叉树是结点的一个有限集合,该集合由一个根结点加上两棵别称为左子树和右子树的二叉树组成。
二叉树不存在度大于 2 的结点
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
2.2 特殊的二叉树
2.2.1 满二叉树 每一层的结点都达到最大值,则这个二叉树就是满二叉树。结点的总数就是 2^k - 1
2.2.2 完全二叉树 完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
则该结构的特点是:假设二叉树的层次为 K 层,则除了第 K 层外,每层结点的个数达到最大结点数,第 K 层不一定达到最大结点数。
2.3 二叉树的存储结构 二叉树一般可以使用两种结构存储:一种是顺序结构,一种是链式结构。
2.3.1 顺序结构 顺序结构是使用数组来进行存储的,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二叉树使用顺序结构进行存储。
2.3.2 链式结构 ⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址。链式结构⼜分为⼆叉链和三叉链,当前我们学习中⼀般都是⼆叉链。后⾯课程学到⾼阶数据结构如红⿊树等会⽤到三叉链。
3. 实现顺序结构二叉树 堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他的特性。
3.1 堆的概念与结构
堆中某个结点的值总是不大于或者不小于其父结点的值
堆总是一棵完全二叉树
对于具有 n 个结点的完全二叉树,如果按照从上至下、从左至右的数组顺序对所有结点从 0 开始编号(例如上述的图片),则对于序号为 i 的结点有:
若 i > 0 , i 位置结点的双亲序号为:( i - 1 ) / 2;i = 0,i 为根结点编号,无双亲编号。
若 2i + 1 < n,左孩子序号:2i + 1,2i + 1 >= n,否则无左孩子。
若 2i + 2 < n,右孩子序号:2i + 2,2i + 2 >= n,否则无右孩子。
3.2 堆的实现
3.2.1 堆的初始化与销毁 与前几次实现基本相同,这里省略分析过程。最终的代码会汇合在一起。
3.2.2 堆数据的插入 由于插入的数据是随机的,我们不确定插入数据的大小,而大堆和小堆之间的数据的顺序是有一定规律的,因此,我们要通过适当的方法来进行插入。用堆的向上调整方法 。
这里对于堆的向上调整算法不再做过多的介绍,直接上代码:
void Swap (int * x, int * y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
void AdjustUp (HPDataType* arr, int child) {
int parent = (child - 1 ) / 2 ;
while (child > 0 ) {
if (arr[child] < arr[parent]) {
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1 ) / 2 ;
} else {
break ;
}
}
}
void HPPush (HP* php, HPDataType x) {
assert(php);
if (php->capacity == php->size) {
int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc (php->arr, newCapacity * sizeof (HPDataType));
if (tmp == NULL ) {
perror("realloc fail!" );
exit (1 );
}
php->arr = tmp;
php->capacity = newCapacity;
}
php->arr[php->size] = x;
AdjustUp(php->arr, php->size);
++php->size;
}
3.2.3 堆数据的删除 删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法 。
与之前不同的是,在堆的删除中,我们删除的是堆顶的数据。
void AdjustDown (HPDataType* arr, int parent, int n) {
int child = parent * 2 + 1 ;
while (child < n) {
if (child + 1 < n && arr[child] > arr[child + 1 ]) {
child++;
}
if (arr[child] < arr[parent]) {
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1 ;
} else {
break ;
}
}
}
void HPPop (HP* php) {
assert(php && php->size);
Swap(&php->arr[0 ], &php->arr[php->size - 1 ]);
--php->size;
AdjustDown(php->arr, 0 , php->size);
}
3.3 堆的应用---TOP-K 问题 TOP-K 问题:即求数据集合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大。
当数据量太大的时候,就不能用排序的方法,这时,我们可以用堆来解决问题。
思路:拿 K 个数据来建堆,然后让取到的数据分别与原始的 K 个数据进行比较,最后所得即为所求。时间复杂度为 O(N),空间复杂度为 O(K)。根据上述的分析可得,找最小的 K 个数据,建大堆,找最大的 K 个数据,建小堆。
void CreateNDate () {
int n = 100000 ;
srand(time(0 ));
const char * file = "data.txt" ;
FILE* fin = fopen(file, "w" );
if (fin == NULL ) {
perror("fopen error" );
return ;
}
for (int i = 0 ; i < n; i++) {
int x = (rand() + i) % 1000000 ;
fprintf (fin, "%d\n" , x);
}
fclose(fin);
}
void TOPK () {
int k = 0 ;
printf ("请输入 K:" );
scanf ("%d" , &k);
const char * file = "data.txt" ;
FILE* fout = fopen(file, "r" );
if (fout == NULL ) {
perror("fopen fail!" );
exit (1 );
}
int * minHeap = (int *)malloc (k * sizeof (int ));
if (minHeap == NULL ) {
perror("malloc fail!" );
exit (0 );
}
for (int i = 0 ; i < k; i++) {
fscanf (fout, "%d" , &minHeap[i]);
}
for (int i = (k - 1 - 1 ) / 2 ; i >= 0 ; i--) {
AdjustDown(minHeap, i, k);
}
int x = 0 ;
while (fscanf (fout, "%d" , &x) != EOF) {
if (x > minHeap[0 ]) {
minHeap[0 ] = x;
AdjustDown(minHeap, 0 , k);
}
}
for (int i = 0 ; i < k; i++) {
printf ("%d " , minHeap[i]);
}
fclose(fout);
}
int main () {
CreateNDate();
TOPK();
return 0 ;
}
4. 实现链式结构二叉树 用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
4.1 前中后序遍历
前序遍历 (Preorder Traversal) :访问根结点的操作发生在遍历其左右子树之前。访问顺序为:根结点、左子树、右子树
中序遍历 (Inorder Traversal) :访问根结点的操作发生在遍历其左右子树之中(间)。访问顺序为:左子树、根结点、右子树
后序遍历 (Postorder Traversal) :访问根结点的操作发生在遍历其左右子树之后。访问顺序为:左子树、右子树、根结点
4.1.1 前序遍历 void PreOrder (BTNode* root) {
if (root == NULL ) {
return ;
}
printf ("%d " , root->data);
PreOrder(root->left);
PreOrder(root->right);
}
4.1.2 中序遍历 void InOrder (BTNode* root) {
if (root == NULL ) {
return ;
}
InOrder(root->left);
printf ("%d " , root->data);
InOrder(root->right);
}
4.1.3 后序遍历 void PostOrder (BTNode* root) {
if (root == NULL ) {
return ;
}
PostOrder(root->left);
PostOrder(root->right);
printf ("%d " , root->data);
}
4.2 结点个数以及高度等
int BinaryTreeSize (BTNode* root) ;
int BinaryTreeLeafSize (BTNode* root) ;
int BinaryTreeLevelKSize (BTNode* root, int k) ;
int BinaryTreeDepth (BTNode* root) ;
BTNode* BinaryTreeFind (BTNode* root, BTDataType x) ;
void BinaryTreeDestroy (BTNode** root) ;
int BinaryTreeSize (BTNode* root) {
if (root == NULL ) {
return 0 ;
}
return (1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right));
}
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 BinaryTreeDepth (BTNode* root) {
if (root == NULL ) {
return 0 ;
}
int leftdep = BinaryTreeDepth(root->left);
int rightdep = BinaryTreeDepth(root->right);
return leftdep > rightdep ? leftdep + 1 : rightdep + 1 ;
}
BTNode* BinaryTreeFind (BTNode* root, BTDataType x) {
if (root == NULL ) {
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 ;
}
void BinaryTreeDestroy (BTNode** root) {
if (*root == NULL ) return ;
BinaryTreeDestroy(&((*root)->left));
BinaryTreeDestroy(&((*root)->right));
free (*root);
*root = NULL ;
}
4.3 层序遍历 设二叉树的根结点所在层数为 1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第 2 层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过就是层序遍历 。
void LevelOrder (BTNode* root) {
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q)) {
BTNode* top = QueueFront(&q);
printf ("%d " , top->data);
QueuePop(&q);
if (top->left) {
QueuePush(&q, top->left);
}
if (top->right) {
QueuePush(&q, top->right);
}
}
QueueDestroy(&q);
}
4.4 判断是否为完全二叉树 bool BinaryTreeComplete (BTNode* root) {
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!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 != NULL ) {
QueueDestroy(&q);
return false ;
}
}
QueueDestroy(&q);
return true ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online