跳到主要内容 堆和二叉树数据结构初阶:C/C++ 实现与理论 | 极客日志
C++ 算法
堆和二叉树数据结构初阶:C/C++ 实现与理论 介绍堆和二叉树的基础理论与 C/C++ 实现。内容涵盖堆的定义、大根堆与小根堆的模拟实现(初始化、插入、删除、建堆)、TopK 问题及堆排序。同时讲解二叉树的性质、四种遍历方式(前序、中序、后序、层序)及常用功能(销毁、求节点数、深度、查找、判断完全二叉树)。结合习题示例,解析递归与队列在树操作中的应用。
雪落无声 发布于 2026/3/30 更新于 2026/4/13 0 浏览前言
本文讲解堆和二叉树的理论部分及习题实践。
理论部分
二叉树性质
对于任意一个二叉树,度为 0 的节点比度为 2 的节点多一个。
数组存储公式(根节点下标为 0):
parent = (child - 1) / 2
leftchild = parent * 2 + 1
rightchild = parent * 2 + 2
数组存储二叉树只适合完全二叉树,否则空间浪费较多。
第 n 层有 2^(n-1) 个节点。
节点个数 = 边数 + 1;边数 = 节点的度相加之和。
树度:树中节点度的最大值。
堆是完全二叉树。
小根堆:所有父亲的值小于等于孩子(最小在根节点)。
大根堆:所有父亲的值大于等于孩子(最大在根节点)。
需与二叉搜索树区分(后者对左右孩子放置有要求)。
堆的模拟实现(以大根堆为例)
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap {
HPDataType* a;
int size;
int capacity;
} HP;
void HeapInit (HP* php) ;
void HeapPush (HP* php, HPDataType x) ;
void HeapPop (HP* php) ;
int HeapSize (HP* php) ;
void AdjustUp (HPDataType* a, int child) ;
void AdjustDown ;
;
{
assert(php);
php->a = (HPDataType*) ( (HPDataType) * );
(php->a == ) {
perror( );
;
}
php->size = ;
php->capacity = ;
}
{
HPDataType x = *p1;
*p1 = *p2;
*p2 = x;
}
{
parent = (child - ) / ;
(child > ) {
(a[child] > a[parent]) {
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - ) / ;
} {
;
}
}
}
{
assert(php);
(php->size == php->capacity) {
HPDataType* tmp = (HPDataType*) (php->a, (HPDataType) * php->capacity * );
(tmp == ) {
perror( );
;
}
php->a = tmp;
php->capacity *= ;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - );
}
{
child = parent * + ;
(child < n) {
(child + < n && a[child + ] < a[child]) {
++child;
}
(a[child] < a[parent]) {
Swap(&a[child], &a[parent]);
parent = child;
child = parent * + ;
} {
;
}
}
}
{
assert(php);
Swap(&php->a[ ], &php->a[php->size - ]);
php->size--;
AdjustDown(php->a, php->size, );
}
{
assert(php);
php->size;
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
(HPDataType* a, int n, int parent)
void
Swap
(HPDataType* p1, HPDataType* p2)
void
HeapInit
(HP* php)
malloc
sizeof
4
if
NULL
"malloc fail"
return
0
4
void
Swap
(HPDataType* p1, HPDataType* p2)
void
AdjustUp
(HPDataType* a, int child)
int
1
2
while
0
if
1
2
else
break
void
HeapPush
(HP* php, HPDataType x)
if
realloc
sizeof
2
if
NULL
"realloc fail"
return
2
1
void
AdjustDown
(HPDataType* a, int n, int parent)
int
2
1
while
if
1
1
if
2
1
else
break
void
HeapPop
(HP* php)
0
1
0
int
HeapSize
(HP* php)
return
注意:php->a[php->size] 表示还没存数据的位置。如果是小堆,判断条件改为 a[child] < a[parent]。
堆的创建 想给数组排升序,要建大堆。时间复杂度为 N*logN。
void HeapSort (int * a, int n) {
for (int i = (n - 1 - 1 ) / 2 ; i >= 0 ; --i)
AdjustDown(a, n, i);
int end = n - 1 ;
while (end > 0 ) {
Swap(&a[end], &a[0 ]);
AdjustDown(a, end, 0 );
--end;
}
}
向上调整建堆:时间复杂度 N*logN。
向下调整建堆:时间复杂度 N(一般用这个)。
TOPK 问题 :求数据集合中前 K 个最大或最小的元素(数据量通常较大)。
方法:
用数据集合中的前 K 个来建堆。
求前 K 个最大的元素,需要建小堆。
求前 K 个最小的元素,需要建大堆。
用剩余的 N-K 个元素依次和堆顶元素比较,满足则替换堆顶元素并向下调整。
二叉树的遍历
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层序遍历:每层从左到右,一层遍历完去下一层
struct BTNode {
int data;
struct BTNode * left ;
struct BTNode * right ;
};
void PreOrder (BTNode* root) {
if (root == NULL ) {
printf ("NULL " );
return ;
}
printf ("%d " , root->data);
PreOrder(root->left);
PreOrder(root->right);
}
void InOrder (BTNode* root) {
if (root == NULL ) {
printf ("NULL " );
return ;
}
InOrder(root->left);
printf ("%d " , root->data);
InOrder(root->right);
}
void PostOrder (BTNode* root) {
if (root == NULL ) {
printf ("NULL " );
return ;
}
PostOrder(root->left);
PostOrder(root->right);
printf ("%d " , root->data);
}
void LevelOrder (BTNode* root) {
Queue q;
QueueInit(&q);
if (root) QueuePush(&q, root);
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf ("%d " , front->data);
if (front->left) QueuePush(&q, front->left);
if (front->right) QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
注意:一般题目不需要打印 NULL,自定义遍历若出现 NULL 需注意处理。
二叉树的其他功能实现
void TreeDestory (BTNode* root) {
if (root == NULL ) return ;
TreeDestory(root->left);
TreeDestory(root->right);
free (root);
}
int TreeSize (BTNode* root) {
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 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 ;
}
int TreeKLevel (BTNode* root, int k) {
assert(k > 0 );
if (root == NULL ) return 0 ;
if (k == 1 ) return 1 ;
return TreeKLevel(root->left, k - 1 ) + TreeKLevel(root->right, k - 1 );
}
BTNode* TreeFind (BTNode* root, int x) {
if (root == NULL ) return NULL ;
if (root->data == x) return root;
BTNode* lret = TreeFind(root->left, x);
if (lret) return lret;
BTNode* rret = TreeFind(root->right, x);
if (rret) return rret;
return NULL ;
}
bool TreeComplete (BTNode* root) {
Queue q;
QueueInit(&q);
if (root) QueuePush(&q, root);
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL ) {
break ;
} else {
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 ;
}
关于变量作用域:全局变量定义在函数声明后面、函数定义前面可直接使用;局部变量必须传参。工程中少用全局变量和静态变量,一般用指针代替。续行符一般只有字符串需要使用。
习题与实战
目录结构通道 在用树表示的目录结构中,从根目录到任何数据文件,有唯一一条通道。
堆的判断 下列关键字序列中,是堆的是:D.{16, 23, 53, 31, 94, 72}。解题思路是将数据从左到右按堆从上到下排列验证。
单值二叉树 做法:遍历二叉树,让左右节点和自己的根节点比较(注意考虑节点为 NULL 的情况)。
相同的树 做法:单独检验是否为空,递归返回左子树相等且右子树相等。
对称二叉树 做法:把除根外的二叉树分成两部分,比较 isSameTree(p->left, q->right) && isSameTree(p->right, q->left)。
前序遍历 注意指针操作细节,返回值要是 int* 类型,需返回数组名。
另一棵树的子树
二叉树遍历(牛客网) 注意:这题要求打印中序遍历,但是给的是前序遍历的数组。易忘把树连接起来:root->left = PreOrder(); root->right = PreOrder();
扩展思考
开辟的空间不会因为函数的结束而自动销毁,只有程序结束才会自动销毁。
如果一颗二叉树的前序遍历的结果是 ABCD,则满足条件的不同的二叉树有 14 种。
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足只有一个叶子结点。
已知某二叉树的中序遍历序列和后序遍历序列,可推导前序遍历序列。通用做法:中序遍历用来确定根的左右区间,先序或后序遍历用来确定每个子树的根。