【数据结构精讲】堆与二叉树从底层原理到代码落地:堆的构建 / 调整 / 排序 + 二叉树遍历 / 操作(附完整 C++ 源码 + LeetCode 题解)

文章目录

堆 和 二叉树 的区别

底层实现方式完全不同

知识点 底层实现 核心逻辑
完全二叉树, 数组实现 AdjustUp/AdjustDown 维护堆的性质(父节点与子节点的大小关系)
通用二叉树 链式实现(节点带左右孩子指针) 递归 / 栈实现遍历、计数、求高

堆Heap

通过结构体中的数组指针实现,结构体中还有size和capacity,和前面的顺序表的实现一样,都是整形指针+size+capacity

1.初始化HPInit

  • 和顺序表的实现相似:

    cpp 复制代码
    void 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 语言里,两个整数做除法,只看整数部分,而且是 向零取整

正确使用:

  1. 堆中还没有内容,这时候可以将新加入的数字进行AdjustUp,堆能够维持
    1. 想靠「向上调整」把数组建成堆,必须从前往后(下标 1 到末尾)处理,不能从后往前!
  2. 对于一个已经成型的堆,进行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函数

  1. 传参:传数组中两个元素的地址(child和parent), 想要改变变量,必须传址
  2. 解引用改变两个地址对应的内容
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

唯一使用场景:

  • 将一个混乱的数组变成堆
    • 而且,建议从最后一个节点的父节点开始进行向下调整(最后一层不需要调整)

错误使用:

  1. ❌在从零插入元素建堆的时候使用:这时候下面啥都没有,无法实现向下调整,顺序不变
  2. ❌在对堆添加元素的时候使用:同上
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.堆排序(将数组变成堆)

  1. 将数组中所有元素整理成堆
  2. 每次把「当前堆的最值」(就是堆顶)扔到数组的末尾,慢慢把数组 "排好队"。
  3. 所以,如果想让最后的结果从小到大(升序),根就应该是最大的(大根堆);想让结果从大到小(降序),开始要整理成小根堆
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);
}

题目与对应解析:

144. 二叉树的前序遍历

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.判断单值二叉树

965. 单值二叉树

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.判断相同/对称二叉树

100. 相同的树

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);
}

101. 对称二叉树

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.另一颗树的子树

  • 找到前一棵树的所有字数和另一个树比较

572. 另一棵树的子树

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.翻转二叉树

226. 翻转二叉树

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.平衡二叉树

110. 平衡二叉树

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);

}
相关推荐
努力努力再努力wz1 小时前
【MySQL 进阶系列】C/C++ 如何通过客户端库访问 MySQL?从连接原理到 API 调用流程详解(附完整demo代码)
服务器·c语言·数据结构·数据库·c++·b树·mysql
xuhaoyu_cpp_java1 小时前
单调栈(算法)
java·数据结构·经验分享·笔记·学习·算法
CSCN新手听安2 小时前
【Qt】Qt窗口(七)QColorDialog颜色对话框,QFileDialog文件对话框的使用
开发语言·c++·qt
A charmer2 小时前
从 C++ 到 Objective-C:零基础平滑转学专栏【总目录】
开发语言·c++·objective-c
cookies_s_s2 小时前
C++ 内存模型与无锁编程:从底层原理到实战
linux·服务器·开发语言·c++
诙_2 小时前
C++数据结构--排序算法
数据结构·算法·排序算法
alwaysrun2 小时前
C++之字符串视图string_view
开发语言·c++·字符串·string_view·字符串视图
城俊BLOG2 小时前
C++的注册机制和插件系统
java·服务器·c++
兩尛2 小时前
c++知识点4
开发语言·c++