堆,堆排序和TOP—K问题(C语言版)

前言

        堆是一种重要的数据结构,堆分为大根堆和小根堆,大根堆堆顶的数据是最大的,小根堆堆顶的数据是最小的,堆在逻辑结构上是一颗完全二叉树,这棵树中如果满足根节点大于左右子树,每个节点都满足这个条件就是大根堆,反之就是小根堆。

1.堆的概念和性质

        堆标准的概念是:如果有一个关键码的集合K = {k0,k1,k2,...,kn-1},把它的所有元素按照完全二叉树的顺序存储方式存储在一个数组中,并且满足: i = 0,1,2..,则称为小堆(或大堆)。将根节点最大的堆称为最大堆或者大根堆,根节点最小的堆叫做最小堆或者小根堆。 

         堆的性质:

        1.堆中某个节点的值总是不大于或者不小于其父节点的值

        2.堆是一颗完全二叉树

        从堆是一颗完全二叉树来理解堆是更容易理解的。

  

2.堆的实现

        2.1向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过根节点开始的向下调整算法可以把它调整为一个小堆。向下调整算法的前提是左右子树都必须是小堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

         

        2.2堆的创建

        下面我们给出一个数组,这个数组在逻辑上可以看出一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点的左右子树不是堆,我们怎么调整呢?我们可以从叶子节点开始调整,但是没有必要,因为每个叶子结点都可以看成一个堆。我们可以从倒数第一个非叶子节点开始调整,一直调整到根节点的树,就可以调成一个堆。 

        int a[] = {1,5,3,8,7,6};

          

        2.3建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值) ,多几个节点没有影响):

        假设树的高度为h

        第一层有2的零次方个节点,需要向下移动h - 1层

        第二层有2的一次方个节点,需要向下移动h - 2层

        第三层有2的二次方个节点,需要向下移动h - 3层

        第四层有2的三次方个节点,需要向下移动h - 4层

        第h - 1层有2的h - 2次方个节点,需要向下移动1层

        则需要移动节点总的移动步数为:

        因此:建堆的时间复杂度为O(N)

        2.4堆的插入

        堆的插入要插入到数组的末尾,在进行向上调整算法,直到满足堆的特性。

  

        2.5堆的删除

        删除堆,删除的是堆顶的元素,如果直接删除好吗?

        答案是否定的,直接删除堆顶的数据,这个堆就废了,需要重新建堆,所以正确的操作是运用先交换堆顶和堆最后一个元素,进行一次向下调整即可解决问题。 

        2.6堆的代码实现

        //Heap.h

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>
#include<string.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;//存储数据int _size;int _capacity;
}Heap;
void HeapSort(int* a, int size);//堆排序
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法void HeapInit(Heap* php, HPDataType* a, int n);//初始化堆void HeapDestory(Heap* php);//销毁队void HeapPush(Heap* php, HPDataType x);//在堆里面入数据void HeapPop(Heap* php);//出堆顶的数据HPDataType HeapTop(Heap* php);//获取堆顶的数据

        //Heap.c 

#include"Heap.h"
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法void Swap(HPDataType* left, HPDataType* right)
{HPDataType tmp = *left;*left = *right;*right = tmp;
}
void HeapSort(int* a, int size)//堆排序
{//建堆int root = (size - 1 - 1) / 2;//找到非叶子结点while (root >= 0){ADJustDown(a, root, size);--root;}//将堆顶的数据与堆底的数据交换int end = size - 1;while (end > 0){Swap(&a[0], &a[end]);//向下调整ADJustDown(a, 0, end);end--;}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子{++child;}if (a[child] < a[parent])//交换孩子和父亲{Swap(&a[child], &a[parent]);//迭代继续parent = child;child = parent * 2 + 1;}else{break;//不需要调整}}
}
void ADJustUp(HPDataType* a, int child)//向上调整算法
{assert(a);//确保指针有效int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//交换父子节点//迭代向后走child = parent;parent = (child - 1) / 2;}else{//结束调整break;}}
}void HeapInit(Heap* php, HPDataType* a, int n)//初始化堆
{php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);php->_capacity = php->_size = n;memcpy(php->_a, a, sizeof(HPDataType)*n);//建队=堆int root = (n - 1 - 1) / 2;//找到非叶子结点while(root >= 0){ADJustDown(php->_a, root, n);--root;}
}
void HeapDestory(Heap* php)//销毁队
{assert(php);//堆存在free(php->_a);php->_size = php->_capacity = 0;
}
void HeapPush(Heap* php, HPDataType x)//在堆里面入数据
{//判断是不是需不需要增容if (php->_capacity == php->_size){php->_capacity *= 2;HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);if (tmp == NULL){printf("申请内存失败\n");exit(-1);}php->_a = tmp;}//插入数据php->_a[php->_size] = x;php->_size++;//向上调整ADJustUp(php->_a, php->_size - 1);
}
void HeapPop(Heap* php)//出堆顶的数据
{assert(php);//确保堆不为空assert(php->_size > 0);//确保堆里面存在数据//为了保持堆的特性,需要先将堆顶的数据与堆底的数据交换,然后pop调堆底的数据//在对堆顶开始进行一次向下调整if (php->_size > 1){Swap(&php->_a[0], &php->_a[php->_size - 1]);php->_size--;ADJustDown(php->_a, 0, php->_size);}else if (php->_size == 1){php->_size--;}
}
HPDataType HeapTop(Heap* php)//获取堆顶的数据
{assert(php);//指针存在assert(php->_size > 0);//堆里面有数据return php->_a[0];
}

3.堆的应用

        3.1堆排序

        堆序,即利用堆的思想进行排序,总共分为两个步骤:

        1.建堆

        如果是排升序,是建大堆还是小堆呢? 如果是排降序呢?

        如果排升序,建小堆的话,每次选出最小的数以后,整个堆就不能用来,就要重新建堆,所以,排升序要建大堆,每次选出最大的数放在数组的最后,堆的大小减一,调用一次向下调整就可以再选出堆里面最大的数了。利用这样的方法就可以实现堆排序了。 

        2.利用堆删除的思想进行排序

        建堆和堆删除中都用到了向下调整算法,因此掌握了向下调整算法就掌握了和堆相关的大部分内容了 ,堆就是这么简单又朴实无华,哈哈哈哈。

 

        

void HeapSort(int* a, int size)//堆排序
{//建堆int root = (size - 1 - 1) / 2;//找到非叶子结点while (root >= 0){ADJustDown(a, root, size);//调用向下调整算法--root;}//将堆顶的数据与堆底的数据交换int end = size - 1;while (end > 0){Swap(&a[0], &a[end]);//向下调整ADJustDown(a, 0, end);end--;}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子{++child;}if (a[child] < a[parent])//交换孩子和父亲{Swap(&a[child], &a[parent]);//迭代继续parent = child;child = parent * 2 + 1;}else{break;//不需要调整}}
}

        3.2TOP-K问题

         TOP-K问题:即求数据集合中前k个最大或者最小的元素,一般情况下数据量会很大。

        比如:专业前10名,世界500强,富豪榜,游戏中前100的活跃玩家等等。

        对于TOP-K问题,能想到的最简单的方式就是排序,但是如果数据量很大,排序就不太可取了(数据可能无法加载到内存中,数据太多了)。最佳的解决方式就是用堆来解决,基本思路如下:

        1.用数据前K个元素来建堆

        如果是求前K大的数,就建一个小堆,如果堆顶的数比剩下的数小就替换堆顶的数据。直到比较完所有的数据。

        如果是求前K小的数据,就建一个大堆,如果堆顶的数比剩下的数大就将堆顶的数替换为正在比较的数,直到比较完所有的数据。

        形象一点来说堆顶数就像是守门员一样,到最后堆顶的数肯定是前K小的数或者前K大数。

        2.用剩余的K-N个元素来和堆顶的数据进行比较,不满足则替换堆顶的元素

将剩余N-K个元素依次与堆顶的元素比较完后,堆里面剩余的K个元素就是所求的前K个最小或者最大的元素。 

        它的时间复杂度是N*log(K)。 

        TOP-K问题

        代码:

void Swap(int* left, int* right)
{int tmp = *left;*left = *right;*right = tmp;
}
void AdJustDown(int* a,int root, int size)//向下调整算法
{assert(a);//指针存在int parent = root;int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子{++child;}if (a[child] < a[parent])//交换孩子和父亲{Swap(&a[child], &a[parent]);//迭代继续parent = child;child = parent * 2 + 1;}else{break;//不需要调整}}
}
int findKthLargest(int* nums, int numsSize, int k)
{//使用向下调整算法进行建堆int root = (k - 1) / 2;//找到倒数第一个非叶子节点while(root >= 0)//对前k个数组元素建小堆{AdJustDown(nums, root, k);--root;}for(int i = k; i < numsSize; ++i){if(nums[0] < nums[i]){//取代堆顶的数据,进行向下调整int tmp = nums[0];nums[0] = nums[i];nums[i] = tmp;AdJustDown(nums, 0, k);}}for(int i = 0; i < numsSize; ++i){printf("%d ",nums[i]);}return nums[0];//此时堆顶的元素就是第K大的元素
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/113033.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MybatisPlus插件篇—逻辑删除+p6spy

文章目录 一、前言二、插件1、逻辑删除1.1、官方说明&#xff1a;1.2、配置依赖1.3、配置全局配置1.4、实体类字段上添加TableLogic注解1.5、验证是否成功 2、执行SQL分析打印2.1、配置依赖2.2、数据库驱动配置2.3、spy配置文件配置2.4、注意事项 三、总结提升 一、前言 本文将…

LLM本地知识库问答系统(一):使用LangChain和LlamaIndex从零构建PDF聊天机器人指南

随着大型语言模型&#xff08;LLM&#xff09;&#xff08;如ChatGPT和GPT-4&#xff09;的兴起&#xff0c;现在比以往任何时候都更容易构建比普通熊更智能的智能聊天机器人&#xff0c;并且可以浏览堆积如山的文档&#xff0c;为您的输入提供准确的响应。 在本系列中&#xf…

java八股文面试[数据库]——数据库三范式

什么是范式&#xff1f; 范式是数据库设计时遵循的一种规范&#xff0c;不同的规范要求遵循不同的范式。 最常用的三大范式 第一范式(1NF)&#xff1a;属性不可分割&#xff0c;即每个属性都是不可分割的原子项。(实体的属性即表中的列) 理解&#xff1a;一个列不能包含两个数…

【GO】LGTM_Grafana_Tempo(2)_官方用例改后实操

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 根据官方文档实操跑起来 tempo&#xff0c;中间根…

网络编程 day 4

1、多进程并发服务器根据流程图重新编写 #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号&#xff0c;范围1024~49151 #define IP "192.168.11…

渲染如何做到超强渲染?MAX插件CG MAGIC中的渲染功能!

渲染工作应该算是设计师的日常工作流程中最重要的环节之一了。如果渲染速度加快&#xff0c;可能是要看渲染技巧掌握的有多少了。 大家熟悉的3d Max本地渲染通道&#xff0c;对于CG MAGIC渲染功能你也一定不能错过&#xff0c;要知道操作简单易使用&#xff0c;就完全拿捏了效率…

微信小程序echart导出图片

echarts版本5.1.0 用到的echarts组件是uni插件市场的echart组件 <div style"overflow: hidden;"><dCanvas class"uni-ec-canvass" id"uni-ec-canvas" ref"canvas" canvas-id"mychart-gauge" :ec"ec"&g…

基于Thinkphp6框架全新UI的AI网址导航系统源码

2023全新UI的AI网址导航系统源码&#xff0c;基于thinkphp6框架开发的 AI 网址导航是一个非常实用的工具&#xff0c;它能够帮助用户方便地浏览和管理自己喜欢的网站。 相比于其他的 AI 网址导航&#xff0c;这个项目使用了更加友好和易用的 ThinkPHP 框架进行搭建&#xff0c;…

1. Spatial Intelligence of a Self-driving Car and Rule-Based Decision Making

主要内容 本文主要介绍了一些基于规则的方法&#xff0c;以实现自动驾驶规划技术在复杂车流中取得人类驾驶效果。因此此类场景更适合城市NOA。 当然本文在城市道路&#xff0c;封闭区域道路以及城际高速都适宜。主要技术点 &#xff08;1&#xff09;本文把自车周围车辆的决策…

webpack loader和plugins的区别

在Webpack中&#xff0c;Loader和Plugin是两个不同的概念&#xff0c;用于不同的目的。 Loader是用于处理非JavaScript模块的文件的转换工具。它们将文件作为输入&#xff0c;并将其转换为Webpack可以处理的模块。例如&#xff0c;当您在Webpack配置中使用Babel Loader时&…

【STM32】学习笔记(OLED)-江科大

调试方式 OLED简介 硬件电路 驱动函数 OLED.H #ifndef __OLED_H #define __OLED_Hvoid OLED_Init(void); void OLED_Clear(void); void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char); void OLED_ShowString(uint8_t Line, uint8_t Column, char *String); void OL…

qt设计界面

widget.h #ifndef WIDGET_H #define WIDGET_H //防止文件重复包含#include <QWidget> //QWidget类所在的头文件&#xff0c;父类头文件 #include<QIcon> #include<QPushButton> …

理论转换实践之keepalived+nginx实现HA

背景&#xff1a; keepalivednginx实现ha是网站和应用服务器常用的方法&#xff0c;之前项目中单独用nginx实现过负载均衡和服务转发&#xff0c;keepalived一直停留在理论节点&#xff0c;加之最近工作编写的一个技术文档用到keepalived&#xff0c;于是便有了下文。 服务组件…

使用Gitea自建仓库 并配置git上传

使用Gitea自建仓库 并配置git上传 使用 Docker 安装 | Gitea Documentation 1. 安装Docker 2. 使用Docker Compose快速安装 在安装目录下创建config 和 data两个文件夹 以下是我的配置&#xff0c;和官网提供的大差不差 version: "3"networks:gitea:external: …

论文阅读_医疗知识图谱_GraphCare

英文名称: GraphCare: Enhancing Healthcare Predictions with Open-World Personalized Knowledge Graphs 中文名称: GraphCare&#xff1a;通过开放世界的个性化知识图增强医疗保健预测 文章: http://arxiv.org/abs/2305.12788 代码: https://github.com/pat-jj/GraphCare 作…

uniapp项目实战系列(2):新建项目,项目搭建,微信开发工具的配置

目录 系列文章目录uniapp项目实战系列(1)&#xff1a;导入数据库&#xff0c;启动后端服务&#xff0c;开启代码托管&#xff08;点击跳转&#xff09;1.新建项目2.托管项目的操作&#xff1a;&#xff08;无勾选托管项目可无视&#xff09;3.项目编译预览3.1游览器编译3.2微信…

以GitFlow分支模型为基准的Git版本分支管理流程

以GitFlow分支模型为基准的Git版本分支管理流程 文章目录 以GitFlow分支模型为基准的Git版本分支管理流程GitFlow分支模型中的主要概念GitFlow的分支管理流程图版本号说明借助插件Git Flow Integration Plus实现分支模型管理其他模型TBD模型阿里AoneFlow模型 GitFlow分支模型中…

2023-8-30 八数码(BFS)

题目链接&#xff1a;八数码 #include <iostream> #include <algorithm> #include <unordered_map> #include <queue>using namespace std;int bfs(string start) {string end "12345678x";queue<string> q;unordered_map<strin…

ATKck靶场系列二

信息收集 nmap -sP 192.168.111.0/24 nmap -sS -T4 -A -v -p- 192.168.111.80─# nmap -sS -T4 -A -v -p- 192.168.111.80 Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-29 01:46 EDT NSE: Loaded 155 scripts for scanning. NSE: Script Pre-scanning. Initiating NS…

el-date-picker 等 点击无反应不回显问题解决

如上图&#xff0c;编辑回显正常&#xff0c;但是时间控件在拖动过程中时间不会跟随改变。 解决办法&#xff1a; <el-date-picker input"onInput()" ...><el-input input"onInput()" ...>js中onInput() {this.$forceUpdate();},