当前位置: 首页 > news >正文

【数据结构】堆(下)+ 二叉树

上期回顾:【数据结构】树(堆)·上

一.堆的应用

1.1堆排序(向下调整在上一期)

向上调整算法建堆:

首先先回顾一下向上调整算法

void AdjustUP(HPDataType* arr, int child)
{int parent = (child - 1) / 2;//找到父结点的位置while (child > 0)//循环,确保父结点一定小于(大于)子结点{//小堆:<//大堆:>//     |//     Vif (arr[child] > arr[parent]){//调整位置Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}
}

在上一期我们也学过了向下调整算法建堆,其是通过由下向上循环,通过确保父结点下方的是堆结构,而向上算法建队=堆就刚好反过来,是由上到下进行循环。

//堆排序
void HeapSort(int *arr,int n)
{//建堆--向下调整for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDOWN(arr, i, n);}//建堆--向上调整for (int i = 0; i < n; i++){AdjustUP(arr, i);}//排序int end = n - 1;while (end > 0){Swap(&arr[end], &arr[0]);AdjustDOWN(arr, 0, end);end--;}
}

1.2.TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

通过前面的学习可了解到,小堆的堆顶是数据的最小值,所以若想通过小堆找最小,就需要遍历所有的数据,在进行堆排序,这对于大数据肯定是不行的,

那么换一种思路,用小堆找最大数据:将前K个数据直接建小堆,然后再遍历剩下的n-k个数据,将其与堆顶进行比较,若比堆顶大,则替换堆顶,来回重复,最后构成一个新堆,该堆就是数据中前K大的数。

而找前K小的数据道理相同,所以就可以总结出:

找最大的前K个数据,建小堆

找最小的前K个数据,建大堆

实践:

先通过代码生成10万个数据,并保存在data.txt文件中

随后实现topK函数:

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);}//找最大的前K个数据--建小堆//找最小的前K个数据--建大堆int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail");exit(1);}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minHeap[i]);}//minHeap -- 向下调整建堆for (int i = (k - 2) / 2; i >=- 0; i--){//找最大的前K个数据--建小堆//找最小的前K个数据--建大堆AdjustDOWN(minHeap, i, k);}//遍历剩下的n-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]);}printf("\n");fclose(fout);
}

先读取文件数据,取前K个保存在数组中,并构成堆,随后遍历剩下的n-k个数据,进行比较替换。

最后成功打印

二.实现链式结构二叉树

二叉树的相关知识需要用到递归知识,所以建议先学习递归后再来看以下文章!!!


2.1二叉树的插入与删除

数据的插入和删除可以在二叉树的任何位置,所以此时暂时不写文章,在后期学习到平衡树,红黑树等在进行编写。

2.2二叉树的遍历

二叉树的遍历有前序/中序/后序的遍历的递归结构遍历:

1)前序遍历:访问根节点的操作在遍历其左右子树之间

 访问顺序为:根结点、左子树、右子树

2)中序遍历:访问根结点的操作发生在遍历其左右子树之中(间)

访问顺序为:左子树、根结点、右子树

3)后序遍历:访问根结点的操作发生在遍历其左右子树之后

访问顺序为:左子树、右子树、根结点

遍历的实现:

通过函数的递归实现遍历,需要先判断递归条件,当目前根节点为NULL时,递归中止,拿前序遍历为例,在目前根节点时,先访问根节点的data,随后根据前序遍历的顺序,分别访问左子树,右子树,进行递归。那么中序遍历与后序遍历也就很好写出来了。

构造文章上文的二叉树例子,然后进行遍历输出:

2.3二叉树的结点个数

1.运用二叉树遍历的知识,通过递归遍历每一个结点,然后size++,那么结果会是什么呢?

int BinaryTreeSize(BTNode* root)
{int size = 0;if (root == NULL){return;}//结点非空,+1size++;BinaryTreeSize(root->left);BinaryTreeSize(root->right);return size;
}

运行后可以发现,不管调用几次,size都为1,原因是因为,每次递归size都被初始化为0

2.那么把size设为全局变量后是不是就可以避免了?

int size = 0;
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return;}//结点非空,+1size++;BinaryTreeSize(root->left);BinaryTreeSize(root->right);return size;
}

运行后发现,虽然第一次调用size的结果是正确的,但是经过多次调用,size的值就会越加越大。

3.这次换一种思路,直接把size设为参数进行调用。

int BinaryTreeSize(BTNode* root, int size)
{if (root == NULL){return;}//结点非空,+1size++;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right,size);return size;
}

这一次发现结果又变为三个1,分析原因,当递归回溯的时候,size的值会返回到上一步的值,例如当递归到4结点时,size=3,但是回溯到2结点时,size又回到了2

4.要解决这个问题,把传值调用改为传址调用就可解决。

int BinaryTreeSize(BTNode* root,int*size)
{if (root == NULL){return 0;}//结点非空,+1(*size)++ ;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right,size);return *size;
}

这时结果就正确了,但是,每次调用参数,都需要把size初始化为0,有一点麻烦,在对函数进行改造

分析二叉树可知,结点数 = 根结点+左子树的结点数+右子树的结点数 

最终版:

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}//结点非空,+1return 	1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

2.4二叉树叶子结点数

叶子结点数 = 左子树叶子节点数 + 右子树叶子结点数

结合刚才学到的结点数的知识,可以写出:

//二叉树叶子节点数
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);
}

2.5二叉树第K层结点个数

//二叉树第K层结点个数
int BinaryTreeLevelSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelSize(root->left, k - 1) + BinaryTreeLevelSize(root->right, k - 1);
}

2.6二叉树的深度

深度 = 根结点 + max(左子树的深度+右子树的深度)

//二叉树的深度
int BinaryTreeDepth(BTNode* root)
{if (root == NULL){return 0;}return 1 + max(BinaryTreeDepth(root->left), BinaryTreeDepth(root->right));//return 1 + (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? BinaryTreeDepth(root->left) : BinaryTreeDepth(root->right));
}

2.7二叉树查找值为x的结点

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDtatatype 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;}
}

2.8二叉树的销毁

//二叉树的销毁
void BinaryTreeDestroy(BTNode** root)
{if (root == NULL){return;}BinaryTreeDestroy(&((*root)->left));BinaryTreeDestroy(&((*root)->right));free(*root);*root = NULL;
}

二叉树的层序遍历

借助数据结构--队列

首先将之前所编写过的队列的.h和.c文件导入进来

注意:

1.要包含队列的头文件

2.修改队列的头文件:修改队列的保存类型,务必要加struct,为了告诉编译器BinaryTreeNode是个结构体。

2.9层序编列实现:

//层序遍历
void levelOrder(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,出队头BTNode*top =  QueueFront(&q);QueuePop(&q);printf("%c ", top->data);//将左右孩子入队列if (top->left)QueuePush(&q, top->left);if (top->right)QueuePush(&q, top->right);}QueueDestrory(&q);
}

2.10判断是否为完全二叉树

前置知识:完全二叉树的特点:

1)最后一层节点个数不一定达到最大

2)结点从左到右依次排列

与二叉树的层序遍历相同,借助队列进行实现:

在第一个循环中,取队头,出对头,直到遇到第一个空结点出循环,并进入到第二个循环。

若在第二个循环中,从非空队列中取到了非空的队头结点,那么该二叉树一定不是完全二叉树,否则一定是完全二文树

//判断是否为完全二叉树
bool BinaryTreeComlete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,出队头BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}QueuePush(&q, top->left);QueuePush(&q, top->right);}//队列不一定为空while(!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){return false;}}QueueDestrory(&q);return true;
}

以上图做例子,应不是完全二叉树:符合预期

把G H I结点去掉后应为完全二叉树:符合预期

http://www.dtcms.com/a/286345.html

相关文章:

  • 深度学习笔记002-引言--日常生活总的机器学习、训练模型的过程、关键组件、各种机器学习问题
  • 如何在服务器上获取Linux目录大小
  • 使用python读取json数据,简单的处理成元组数组
  • 验证大语言模型不会算数但可以编写算数的程序
  • 【数据结构初阶】--双向链表(一)
  • 机器学习17-Mamba
  • C语言—如何生成随机数+原理详细分析
  • Linux服务器端口被占用?
  • 无刷电机控制 - 基于STM32F405+CubeMX+HAL库+SimpleFOC04,完成霍尔传感器的驱动代码
  • @Scheduled的作用分析
  • 赛道观察:AI智能自习室哪家强?深挖深度逻辑与价值锚点
  • 链表算法之【链表的中间节点】
  • 【CMake】CMake 项目打包与 find_package 使用流程:从 A 到 B 的完整范例
  • 基于MATLAB的朴素贝叶斯NB的数据分类预测方法应用
  • 一种新颖的可解释人工智能框架,用于整合统计、视觉和基于规则的方法进行医学图像分类|文献速递-医学影像算法文献分享
  • Flutter ScaffoldMessenger 详细介绍
  • P1205 [USACO1.2] 方块转换 Transformations
  • 《通信原理》学习笔记——第四章
  • 【论文阅读】BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework
  • Redis——BigKey
  • Radix-4 Booth乘法器计算步骤
  • 【AI论文】CLiFT:面向计算高效与自适应神经渲染的压缩光场标记
  • vue2 面试题及详细答案150道(41 - 60)
  • Node.js链接MySql
  • Vue常见指令
  • Java大厂面试实录:从Spring Boot到AI微服务架构的深度解析
  • 深度学习零基础入门(3)-图像与神经网络
  • UE5 一些关于过场动画sequencer,轨道track的一些Python操作
  • 力扣347:前K个高频元素
  • 科技照亮童心|激光院与跳伞塔社区开展公益活动