
文章目录
-
- [堆 和 二叉树 的区别](#堆 和 二叉树 的区别)
- 堆Heap
- 二叉树BinaryTree
堆 和 二叉树 的区别
底层实现方式完全不同
| 知识点 | 底层实现 | 核心逻辑 |
|---|---|---|
| 堆 | 完全二叉树, 数组实现 | 靠 AdjustUp/AdjustDown 维护堆的性质(父节点与子节点的大小关系) |
| 通用二叉树 | 链式实现(节点带左右孩子指针) | 靠递归 / 栈实现遍历、计数、求高 |
堆Heap
通过结构体中的数组指针实现,结构体中还有size和capacity,和前面的顺序表的实现一样,都是整形指针+size+capacity
1.初始化HPInit
-
和顺序表的实现相似:
cppvoid HPInit(HP* php) { assert(php); php->a = NULL; php->size = php->capacity = 0; }
2.销毁:
- 和顺序表的实现相似:

cpp
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
3.堆插入数据:HPPush
- assert
- 判断要不要扩容
- 直接放到队尾
- 进行向上调整:数组(树)的地址
php->a,传最后一个元素的下标php->size - 1
cpp
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)//扩容操作
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;//在末尾添加元素
php->size++;
AdjustUp(php->a, php->size - 1);//向上调整
}
4.向上调整算法AdjustUp
- 传参:数组首元素地址,child下标
- 实现:
- 从第二个节点(下标为1)开始向上调整。(因为根上面没有元素)
- 通过child找到parent:
parent = (child - 1) / 2; - 循环:
**while** (child > 0)- 和父亲节点对比,child更小就交换的(实现交换函数),更新parent和child
- 符合小根堆(parent更小)就break
为什么结束条件不写
while(parent >= 0)?(如果child最后在最顶端,child=0;parent = (child - 1) / 2在数学上小于零;)原因:parent不会<0:C 语言里,两个整数做除法,只看整数部分,而且是 向零取整
正确使用:
- 堆中还没有内容,这时候可以将新加入的数字进行AdjustUp,堆能够维持
- 想靠「向上调整」把数组建成堆,必须从前往后(下标 1 到末尾)处理,不能从后往前!
- 对于一个已经成型的堆,进行AdjustUp,堆能够维持
错误使用:
- ❌想靠「向上调整」把数组建成堆, 从后往前用向上调整

cpp
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;//得到上面的下标(parent)
while (child > 0)//while (parent >= 0)是错误的写法
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);//swap
child = parent;//得到下一次的child
parent = (child - 1) / 2;//得到下一次的parent
}
else
{
break;
}
}
}
5.实现swap函数
- 传参:传数组中两个元素的地址(child和parent), 想要改变变量,必须传址
- 解引用改变两个地址对应的内容
cpp
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
6.删除堆顶数据:HPPop
- 两个assert
- 交换第堆顶和最后一个数据
- 向下调整
cpp
void HPPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size-1]);//想要改变变量,必须传址
php->size--;
AdjustDown(php->a, php->size, 0);//向下调整 上位元素
}
7.向下调整算法:AdjustDown
- 传参:
- 树对应的数组首元素地址
- 数组size
- 传父节点地址(因为要不断向下调整)
- 得到更小的孩子:
- 先得到左孩子,
a[child + 1] < a[child]- 然后判断左右孩子哪个小,如果右孩子更小,
++child;得到右孩子
- 然后判断左右孩子哪个小,如果右孩子更小,
- 然后不断向下,得到新的parent和child
- 先得到左孩子,
唯一使用场景:
- 将一个混乱的数组变成堆
- 而且,建议从最后一个节点的父节点开始进行向下调整(最后一层不需要调整)
错误使用:
- ❌在从零插入元素建堆的时候使用:这时候下面啥都没有,无法实现向下调整,顺序不变
- ❌在对堆添加元素的时候使用:同上
cpp
void AdjustDown(HPDataType* a, int n, int parent)//从上往下调整
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // child >= n说明孩子不存在,调整到叶子了,该停了
{
// 找出小的那个孩子
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])//父亲比孩子大,调整到下面,因为是小根堆
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
8.返回堆顶元素:HPTop
cpp
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
9.堆排序(将数组变成堆)
- 将数组中所有元素整理成堆
- 每次把「当前堆的最值」(就是堆顶)扔到数组的末尾,慢慢把数组 "排好队"。
- 所以,如果想让最后的结果从小到大(升序),根就应该是最大的(大根堆);想让结果从大到小(降序),开始要整理成小根堆
cpp
void HeapSort(int* a, int n)
{
// 降序,建小堆
// 升序,建大堆
//第一版
/*for (int i = 1; i < n; i++)/// 从第2个(下标从0开始)元素开始进行向上调整,最终形成小根堆
{
AdjustUp(a, i);
}*/
//升级版------向下调整建堆,从末尾节点的父节点开始一直往前到首节点,都向下调整
for (int i = (n-1-1)/2; i >= 0; i--)//
{
AdjustDown(a, n, i);
}//
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);//将小根堆最上面的元素和最后的元素交换位置,最小的就放在了后面
AdjustDown(a, end, 0);// 将上面放上去的调整到合适的位置
--end;//最后一个已经排好,序号--
}
}
另一种写法:
cpp
// 对数组进行堆排序
void HeapSort(int* a, int size)
{
assert(a && size > 0);
///将数组变成一个小根堆:从最后一个非叶节点往前遍历,当做父节点,进行向下调整
///循环:得到top,和尾换位,向下调整新的小根;
int father = (size - 1 - 1) / 2;
while (father >= 0)
{
AdjustDown(a, size, father);
father--;
}
//for (int i = 0; i < size; i++)
//{
// Swap(&a[0], &a[size - 1 - i]);
//
// AdjustDown(a, size, 0);
//}
for (int i = 0; i < size; i++)
{
Swap(&a[0], &a[size - 1 - i]);
//void AdjustDown(int* a, int size, int father)
AdjustDown(a, size-i-1, 0);///////////////////////////////
}
}


二叉树BinaryTree
1.构建二叉树‼️:
https://www.nowcoder.com/share/jump/491928771777962135052
通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

cpp
//通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a/*节点内容*/,
int n/*数组节点个数*/,
int* pi/*当前处理到数组的哪个位置的「下标指针」*/)
{
//0.停止标志/特殊情况
if (*pi >= n || a[*pi] == '#')
{
//*pi++;
(*pi)++;
return NULL;
}
//1.创建新节点
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
node->data = a[*pi];
*pi++;
//2.递归创建左右子树------符合前序遍历
node->left = BinaryTreeCreate(a, n, pi);
node->right = BinaryTreeCreate(a, n, pi);
return node;
}
‼️注意:* 解引用 和 ++ 自增 ------ 优先级一样高!(第9行)
2.二叉树销毁
cpp
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
return;
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
*root = NULL;
}
‼️注意:先判空
3.节点总个数
cpp
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
升级版:
cpp
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root==NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
4.叶节点个数
cpp
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right) + 1;
}
5.求第k层的节点个数
cpp
// 二叉树第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);
}
‼️注意:层数是从1开始的
3.前/中/后序遍历
cpp
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
题目与对应解析:
cpp
int GetSize(struct TreeNode* root)
{
//结束条件:遇到了NULL。循环条件:左右子树
if(root==NULL)
return 0;
return GetSize(root->left)+GetSize(root->right)+1;
}
void Put(struct TreeNode* root,int *arr,int* pi)
{
//前序
if(root==NULL)
return;
arr[*pi]=root->val;
(*pi)++;
Put(root->left,arr,pi);
Put(root->right,arr,pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
//1.求出总结点个数2.创建数组遍历二叉树,前序遍历,放进去
int n=GetSize(root);
*returnSize=n;
int * arr=(int*)malloc(n*sizeof(int));
int i=0;
Put(root,arr,&i);
return arr;
}
cpp
#include <stdio.h>
#include<stdlib.h>
//实现将前序字符串转为树
typedef struct TreeNode
{
char val;
struct TreeNode*left;
struct TreeNode*right;
}TreeNode;
TreeNode* ToTree(char* arr ,int size,int* pi)
{
//前序遍历建树
if((*pi)>=size||arr[*pi]=='#')
{
(*pi)++;
return NULL;
}
TreeNode* node=(TreeNode*)malloc(sizeof(TreeNode));
node->val=arr[*pi];
(*pi)++;
node->left=ToTree(arr, size,pi);
node->right=ToTree(arr, size,pi);
return node;
}
//实现将树中序打印
void InPrint(TreeNode* tree)
{
if(tree==NULL)
return ;
InPrint(tree->left);
printf("%c ",tree->val);
InPrint(tree->right);
}
int main() {
char arr[100]={0};
scanf("%s",arr);
int size=0;
for(int i=0;i<100;i++)
{
// if(arr[i]>='a'&&arr[i]<='z')
// size++;
size++;
if(arr[i]==0)
break;
}
int i=0;
TreeNode* tree=ToTree(arr, size, &i);
InPrint( tree);
return 0;
}
5.求树高
普通方法:
cpp
int BinaryTreeMaxDepth(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeMaxDepth(root->left) > BinaryTreeMaxDepth(root->right) ?
BinaryTreeMaxDepth(root->left)+1 : BinaryTreeMaxDepth(root->right)+1;
}
如果树的高度是 n,调用次数会接近 2^(n+1) 次
优化方法1:提前记录
cpp
int BinaryTreeMaxDepth(BTNode* root)
{
if (root == NULL)
return 0;
int r = BinaryTreeMaxDepth(root->right);
int l = BinaryTreeMaxDepth(root->left);
return(l > r ? l : r)+1;
}
优化方法2:fmax
cpp
int BinaryTreeMaxDepth(BTNode* root)
{
if (root == NULL)
return 0;
return fmax(BinaryTreeMaxDepth(root->left), BinaryTreeMaxDepth(root->right))+1;
}
‼️注意:头文件 :<math.h>
6.查找节点‼️
cpp
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)//结束条件1:范围超过
{
return NULL;
}
if (root->data == x)//结束条件2:成功找到节点
{
return root;
}
BTNode* return1= BinaryTreeFind(root->left,x);//递归,结果有上面两种:NULL/成功节点
if (return1 != NULL)
return return1;
BTNode* return2 = BinaryTreeFind(root->right,x);
if (return2 != NULL)
return return2;
return NULL;
}
7.判断单值二叉树
cpp
bool isUnivalTree(struct TreeNode* root) {
//循环条件,循环这个二叉树,
//结束条件,如果
if(root==NULL)
return true;
if(root->left&&root->val!=root->left->val)
return false;
if(root->right&&root->val!=root->right->val)
return false;
return (isUnivalTree( root->left)&&isUnivalTree( root->right));
}
8.判断相同/对称二叉树
cpp
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
//结束条件:遇到了NULL,二者相同;二者不相等,return false
//循环条件:判断左右子树
if(p==NULL&&p==q)
return true;
if(p==NULL||q==NULL)
return false;
return p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
cpp
bool isSymmetricTree(struct TreeNode* p, struct TreeNode* q) {
//结束条件:遇到了NULL,二者相同;二者不相等,return false
//循环条件:判断左右子树
if(p==NULL&&p==q)
return true;
if(p==NULL||q==NULL)
return false;
return p->val==q->val&&isSymmetricTree(p->left,q->right)&&isSymmetricTree(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root) {
return isSymmetricTree(root->right,root->left);
}
9.另一颗树的子树
- 找到前一棵树的所有字数和另一个树比较
cpp
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
//结束条件:遇到了NULL,二者相同;二者不相等,return false
//循环条件:判断左右子树
if(p==NULL&&p==q)
return true;
if(p==NULL||q==NULL)
return false;
return p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
if(root==NULL&&subRoot==NULL)
return true;
if(root==NULL||subRoot==NULL)
return false;
if(isSameTree(root,subRoot))
return true;
if(isSameTree(root->left,subRoot))
return true;
if(isSameTree(root->right,subRoot))
return true;
return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
10.翻转二叉树
cpp
struct TreeNode* invertTree(struct TreeNode* root) {
if(root==NULL)
return NULL;
struct TreeNode* tmp=root->left;
root->left=root->right;
root->right=tmp;
if(root->left)
invertTree( root->left);
if(root->right)
invertTree( root->right);
return root;
}
11.平衡二叉树
cpp
int MaxDepth(struct TreeNode* root)
{
if(root==NULL)
return 0;
return fmax(MaxDepth(root->left),MaxDepth(root->right))+1;
}
bool isBalanced(struct TreeNode* root) {
//求左右子树的高度,如果符合,就return左右子树的is balance
if(root==NULL)
return true;
if(abs(MaxDepth(root->left)-MaxDepth(root->right))>1)
return false;
return isBalanced(root->left)&&isBalanced(root->right);
}