【数据结构与算法】堆 / 堆排序 / TopK问题(Heap)

文章目录

  • 1.堆
  • 2.C语言实现堆
    • 2.1 堆结构与基本操作
    • 2.2 其它辅助操作
    • 2.3 堆的基本操作
      • 2.3.1 插入
      • 2.3.2 删除
  • 3. 堆排序
  • 4. TopK
  • 5. 所有代码

1.堆

堆总是一棵完全二叉树,而完全二叉树更适合使用**顺序结构(数组)**存储,完全二叉树前h-1层是满的,最后一层不一定是满的,但节点一定连续的。需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
在这里插入图片描述
非完全二叉树不适合用数组来存储,因为存在空间浪费。而极端的二叉搜索树则会造成更多浪费,二叉搜索树即右孩子节点比父节点大,左孩子节点比父节点小的树。
在这里插入图片描述
使用数组存储二叉树,基于父子节点下标间的关系:leftchild = parent * 2 + 1,rightchild = parent * 2 + 2,parent = (child - 1) / 2,如果打破这种存储关系则数组无法表示二叉树,所以数组存储非完全二叉树注定要浪费空间。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。最大堆即任意一个父节点都大于等于孩子节点,最小堆即任意一个父节点都小于等于孩子节点。
在这里插入图片描述

满二叉树实际上也是一棵特殊的完全二叉树,树的每一层都是满节点即是满二叉树。满二叉树有这样一个规律,第一层的节点数为20,第二层节点数为21,假设树的高度为h,则第h层的节点数为2h-1,不难看出是一个等比数列,所以满二叉树的节点数量为2h-1。
在这里插入图片描述
基于这样一个规律,则完全二叉树最多有2h-1个节点,最少有2h-1个节点:[2h-1,2h-1]。知道了节点数量,也就知道了树的高度h:假设N是节点数量,N = 2h-1,则h = log2(N) + 1;N = 2h-1,则h = log2(N+1)。

2.C语言实现堆

2.1 堆结构与基本操作

堆结构看起来与顺序表无异,毕竟都是数组实现。不一样的是逻辑结构,顺序表是线性结构,堆是树形结构。堆的基本操作只有插入和删除,应用场景有堆排序和TopK(前k个最大或最小的数)。

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>// 堆结构
typedef int datatype;
typedef struct Heap {datatype* arr;int size;int capacity;
} Heap;// 其它辅助操作
void HeapInit(Heap* heap);
void HeapDestroy(Heap* heap);
datatype HeapTop(Heap* heap); // 取堆顶元素
size_t HeapSize(Heap* heap);
bool HeapEmpty(Heap* heap);// logn
void HeapPush(Heap* heap, datatype val);
void HeapPop(Heap* heap); // 删除堆顶元素
// 插入或删除时,堆向上、向下调整
void Swap(datatype* x, datatype* y);
void AdjustUp(datatype* arr, int child);
void AdjustDown(datatype* arr, int size, int parent);// 堆排序 nlogn、求TopK nlogk
void HeapSort(datatype* arr, int size);
void PrintTopK(const char* file, int k);

2.2 其它辅助操作

void HeapInit(Heap* heap) {assert(heap);heap->arr = NULL;heap->size = heap->capacity = 0;
}void HeapDestroy(Heap* heap) {assert(heap);free(heap->arr);heap->arr = NULL;heap->size = heap->capacity = 0;
}datatype HeapTop(Heap* heap) {assert(heap && heap->arr && heap->size > 0);return heap->arr[0];
}size_t HeapSize(Heap* heap) {assert(heap);return heap->size;
}bool HeapEmpty(Heap* heap) {assert(heap);return heap->size == 0;
}

2.3 堆的基本操作

2.3.1 插入

直接往数组插入数据,然后再向上调整即可。以构建最小堆举例,只要插入的数据比父节点小,就与父节点交换,重复这个操作直到不能再做交换。

void HeapPush(Heap* heap, datatype val) {assert(heap);if (heap->size == heap->capacity) { // 空间不够时扩容heap->capacity = heap->capacity == 0 ? 10 : heap->capacity * 2;datatype* tmp = realloc(heap->arr, sizeof(datatype) * heap->capacity);if (tmp == NULL) {perror("HeapPush malloc failed.");exit(-1);}heap->arr = tmp;}// 插入heap->arr[heap->size++] = val;AdjustUp(heap->arr, heap->size - 1);
}
void Swap(datatype* x, datatype* y) {int tmp = *x;*x = *y;*y = tmp;
}
// logn
void AdjustUp(datatype* arr, int child) {int parent = (child - 1) / 2;while (child > 0 && arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]); // upchild = parent;parent = (child - 1) / 2;}
}

如果将while循环的条件arr[child] < arr[parent]改成大于arr[child] > arr[parent],则是调整构建最大堆。

Heap heap;
HeapInit(&heap);
// 建堆 nlogn
HeapPush(&heap, 67864);
HeapPush(&heap, 7432);
HeapPush(&heap, 854312);
HeapPush(&heap, 909876);
HeapPush(&heap, 8765);
HeapPush(&heap, 2345678);
HeapPush(&heap, 2563);
HeapPush(&heap, 12676);
HeapPush(&heap, 6543);
HeapPush(&heap, 2167);
for (int i = 0; i < heap.size; i++) {printf("%d ", heap.arr[i]);
}
HeapDestroy(&heap);

在这里插入图片描述

2.3.2 删除

删除堆元素,只能删除堆顶元素,这是合理的规定,其它诸如任意删除、删除最后一个元素的操作对堆而言都是没有意义的。

如果是直接删除堆顶元素,数组剩下的元素不再构成堆,所以不能这么做。还是以最小堆为例,(1)标准的实现是:将堆顶元素与数组最后一个元素进行交换,即最小的数与较大的数交换,接着删除最后一个元素,然后再向下调整,目的是将堆顶元素往下沉,重新构建最小堆;(2)向下调整的思路:从左右孩子节点中选出最小的,这个孩子节点比父节点小就做交换,重复这个操作。

void HeapPop(Heap* heap) {assert(heap && heap->arr && heap->size > 0);Swap(&heap->arr[0], &heap->arr[--heap->size]);AdjustDown(heap->arr, heap->size, 0);
}
void Swap(datatype* x, datatype* y) {int tmp = *x;*x = *y;*y = tmp;
}
// logn
void AdjustDown(datatype* arr, int size, int parent) {int child = parent * 2 + 1; // left or right child = child + 1 < size && arr[child + 1] < arr[child]? ++child : chi// child<parent调整为小堆,child>parent调整为大堆 while (child < size && arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]); // downparent = child;child = parent * 2 + 1; // left or rightchild = child+1 < size && arr[child+1] < arr[child]? ++child : child;}
}

如果将arr[child + 1] < arr[child]改成arr[child + 1] > arr[child],以及while循环的条件arr[child] < arr[parent]改成大于arr[child] > arr[parent],则是调整构建最大堆。

Heap heap;
HeapInit(&heap);
// 建堆 nlogn
HeapPush(&heap, 67864);
HeapPush(&heap, 7432);
HeapPush(&heap, 854312);
HeapPush(&heap, 909876);
HeapPush(&heap, 8765);
HeapPush(&heap, 2345678);
HeapPush(&heap, 2563);
HeapPush(&heap, 12676);
HeapPush(&heap, 6543);
HeapPush(&heap, 2167);
while (!HeapEmpty(&heap)) {printf("%d ", HeapTop(&heap));HeapPop(&heap);
}
HeapDestroy(&heap);

在这里插入图片描述
这其实相当于排了个序,时间复杂度为nlogn。不过由于还有插入操作的时间复杂度nlogn,所以整体时间复杂度为2n*2logn。

另外这也能解决TopK问题:

int k = 3; 
while (k--) {printf("%d ", HeapTop(&heap));HeapPop(&heap);
}

3. 堆排序

前面借助堆基本操作Top和Pop也能做到堆排序,不过却比较麻烦,因为需要实现堆的基本操作。这里所指的堆排序是指,直接将数组构建成堆然后排序,不包含建堆的时间则堆排序的时间复杂度为nlogn。
在这里插入图片描述

// 排升序则构建大堆,排降序则构建小堆
void HeapSort(datatype* arr, int size) {// 建堆 O(nlogn) //for (int i = 1; i < size; ++i) {//	AdjustUp(arr, i);  //}// 建堆 O(n) 并非是nlognfor (int i = (size - 1 - 1) / 2; i >= 0; --i) {AdjustDown(arr, size, i);}// 排序 O(nlogn)for (int end = size - 1; end > 0; --end) {Swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);}
}

利用向下调整建堆的时间复杂度为O(n)的原因是,最后一层不需要向下调整,直接从倒数第二层开始向下调整,这节省了很多时间复杂度,毕竟最后一层的节点占了堆节点总数一半。每一层的节点数量越多,向下调整次数越少;每一层的节点数量越少,向下调整的次数才越多。

而利用向上调整构建堆,从最后一层的节点往上,则会耗费较多时间复杂度,因为最后一层也需要向上调整。

4. TopK

如果数据量太大,比如一千万个数据,以int来算则是四千万字节,差不多相当于40G内存,如果还是按以前那样将所有数据插入堆中求TopK显然是不可能的。

void PrintTopK(const char* file, int k) {// 文件FILE* fr = fopen(file, "r");if (fr == NULL) {perror("PrintTopK fopen failed.");exit(-1);}// 大小为k的堆datatype* minheap = (datatype*)malloc(sizeof(datatype) * k);if (minheap == NULL) {perror("PrintTopK malloc failed.");exit(-1);}// 从文件读取前k个建小堆for (int i = 0; i < k; i++) {fscanf(fr, "%d", &minheap[i]);AdjustUp(minheap, i); // child<parent}// 从文件挨个读取,寻找TopKint val = 0;while (fscanf(fr, "%d", &val) != EOF) {if (val > *minheap) {*minheap = val; // 大的往下沉 child<parentAdjustDown(minheap, k, 0); }}// 打印TopKfor (int i = 0; i < k; i++) {printf("%d ", minheap[i]);}printf("\n");free(minheap);minheap = NULL;fclose(fr);
}

5. 所有代码

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>// 堆结构
typedef int datatype;
typedef struct Heap {datatype* arr;int size;int capacity;
} Heap;void HeapInit(Heap* heap);
void HeapDestroy(Heap* heap);void HeapPush(Heap* heap, datatype val);
void HeapPop(Heap* heap);
// 插入或删除时,堆向上、向下调整
void Swap(datatype* x, datatype* y);
void AdjustUp(datatype* arr, int child);
void AdjustDown(datatype* arr, int size, int parent);datatype HeapTop(Heap* heap);
size_t HeapSize(Heap* heap);
bool HeapEmpty(Heap* heap);// 堆排序、求TopK
void HeapSort(datatype* arr, int size);
void PrintTopK(const char* file, int k);
#define _CRT_SECURE_NO_WARNINGS 1#include "Heap.h"void HeapInit(Heap* heap) {assert(heap);heap->arr = NULL;heap->size = heap->capacity = 0;
}void HeapDestroy(Heap* heap) {assert(heap);free(heap->arr);heap->arr = NULL;heap->size = heap->capacity = 0;
}void HeapPush(Heap* heap, datatype val) {assert(heap);if (heap->size == heap->capacity) {heap->capacity = heap->capacity == 0 ? 10 : heap->capacity * 2;datatype* tmp = realloc(heap->arr, sizeof(datatype) * heap->capacity);if (tmp == NULL) {perror("HeapPush malloc failed.");exit(-1);}heap->arr = tmp;}heap->arr[heap->size++] = val;AdjustUp(heap->arr, heap->size - 1);
}void HeapPop(Heap* heap) {assert(heap && heap->arr && heap->size > 0);Swap(&heap->arr[0], &heap->arr[--heap->size]);AdjustDown(heap->arr, heap->size, 0);
}datatype HeapTop(Heap* heap) {assert(heap && heap->arr && heap->size > 0);return heap->arr[0];
}size_t HeapSize(Heap* heap) {assert(heap);return heap->size;
}bool HeapEmpty(Heap* heap) {assert(heap);return heap->size == 0;
}void Swap(datatype* x, datatype* y) {int tmp = *x;*x = *y;*y = tmp;
}void AdjustUp(datatype* arr, int child) {int parent = (child - 1) / 2;while (child > 0 && arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]); // upchild = parent;parent = (child - 1) / 2;}
}void AdjustDown(datatype* arr, int size, int parent) {int child = parent * 2 + 1; //child+1为右孩子节点child = child + 1 < size && arr[child + 1] < arr[child]? ++child : child;// child<parent调整为小堆,child>parent调整为大堆 while (child < size && arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]); // downparent = child;child = parent * 2 + 1; // left or rightchild = child+1 < size && arr[child+1] < arr[child]? ++child : child;}
}// 升序构建大堆,降序构建小堆
void HeapSort(datatype* arr, int size) {// 建堆 O(nlogn) //for (int i = 1; i < size; ++i) {//	AdjustUp(arr, i);  //}// 建堆 O(n) for (int i = (size - 1 - 1) / 2; i >= 0; --i) {AdjustDown(arr, size, i);}// 排序 O(nlogn)for (int end = size - 1; end > 0; --end) {Swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);}
}void PrintTopK(const char* file, int k) {// 文件FILE* fr = fopen(file, "r");if (fr == NULL) {perror("PrintTopK fopen failed.");exit(-1);}// 大小为k的堆datatype* minheap = (datatype*)malloc(sizeof(datatype) * k);if (minheap == NULL) {perror("PrintTopK malloc failed.");exit(-1);}// 从文件读取前k个建小堆for (int i = 0; i < k; i++) {fscanf(fr, "%d", &minheap[i]);AdjustUp(minheap, i); // child<parent}// 从文件挨个读取,寻找TopKint val = 0;while (fscanf(fr, "%d", &val) != EOF) {if (val > *minheap) {*minheap = val; // 大的往下沉 child<parentAdjustDown(minheap, k, 0); }}// 打印TopKfor (int i = 0; i < k; i++) {printf("%d ", minheap[i]);}printf("\n");free(minheap);minheap = NULL;fclose(fr);
}
#define _CRT_SECURE_NO_WARNINGS #include "Heap.h"
#include <time.h>static void CreateNData();int main() {//Heap heap;//HeapInit(&heap); 建堆 nlogn//HeapPush(&heap, 67864);//HeapPush(&heap, 7432);//HeapPush(&heap, 854312);//HeapPush(&heap, 909876);//HeapPush(&heap, 8765);//HeapPush(&heap, 2345678);//HeapPush(&heap, 2563);//HeapPush(&heap, 12676);//HeapPush(&heap, 6543);//HeapPush(&heap, 2167);//printf("%zd\n", HeapSize(&heap));//for (int i = 0; i < heap.size; i++) {//	printf("%d ", heap.arr[i]);//}//printf("\n");// top k//int k = 3; //while (k--) {//	printf("%d ", HeapTop(&heap));//	HeapPop(&heap);//}//printf("\n");// Push nlogn + 排序 nlogn =  O(2nlogn)//while (!HeapEmpty(&heap)) {//	printf("%d ", HeapTop(&heap));//	HeapPop(&heap);//}//HeapDestroy(&heap);//printf("\n");// 堆排序//int arr[] = { 67864,7432,854312,909876,8765,2345678,2563,12676,6543,2167 };//HeapSort(arr, sizeof arr / sizeof arr[0]);//for (int i = 0; i < sizeof arr / sizeof arr[0]; i++) {//	printf("%d ", arr[i]);//}// 大量数据下的TopK//CreateNData(); PrintTopK("data.txt", 6);return 0;
}static void CreateNData() {int n = 100000;srand((unsigned int)time(0));FILE* fw = fopen("data.txt", "w");if (fw == NULL) {perror("CreateNData fopen failed.");exit(-1);}for (int i = 0; i < n; i++) {fprintf(fw, "%d\n", rand() % n);}fclose(fw);
}

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

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

相关文章

蓝桥杯省赛无忧 课件92 行列式

01 什么是行列式 02 行列式的性质 03 高斯消元求行列式

相机图像质量研究(5)常见问题总结:光学结构对成像的影响--景深

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

代码随想录算法训练营第二八天 | 分割 子集

目录 复原IP地址子集子集 II LeetCode 93.复原IP地址 LeetCode 78.子集 LeetCode 90.子集II 复原IP地址 一些字符串的基本操作不会 s.insert(i 1, ‘.’); s.deleteCharAt(i 1); class Solution {List<String> result new ArrayList<>();public List<St…

使用clearml监控模型训练过程

安装依赖 pip install clearml依赖安装好后登陆clearml官网 创建一个工作空间 点击Create new credentials 点击后将api整块复制出来&#xff0c;随后需要在当前终端环境中初始化这个clearml的账户信息 终端输入&#xff1a; clearml-init 在出现的Paste copied configurat…

UDP端口探活的那些细节

一 背景 商业客户反馈用categraf的net_response插件配置了udp探测, 遇到报错了&#xff0c;如图 udp是无连接的&#xff0c;无法用建立连接的形式判断端口。 插件最初的设计是需要配置udp的发送字符&#xff0c;并且配置期望返回的字符串&#xff0c; [[instances]] targets…

2.6:冒泡、简选、直插、快排,递归,宏

1.冒泡排序、简单选择排序、直接插入排序、快速排序(升序) 程序代码&#xff1a; 1 #include<stdio.h>2 #include<string.h>3 #include<stdlib.h>4 void Bubble(int arr[],int len);5 void simple_sort(int arr[],int len);6 void insert_sort(int arr[],in…

2024PMP考试新考纲-近年PMP真题练一练和很详细解析(3)

今天华研荟继续为您分享和解析PMP真题&#xff0c;一方面让大家感受实际的PMP考试和出题形式&#xff0c;另一方面是通过较详细的解题思路和知识讲解帮助大家最后一个多月有效备考&#xff0c;一次性3A通过2024年PMP考试。 2024年PMP考试新考纲-近年真题随机练一练 (注&#x…

企业邮箱是什么?企业邮箱百科

本文将为大家讲解&#xff1a;1、企业邮箱的定义&#xff1b;2、企业邮箱的主要功能特点&#xff1b;3、企业邮箱如何选择和部署&#xff1b;4、企业邮箱的运营与维护&#xff1b;5、企业邮箱在实际工作中的应用与挑战&#xff1b;6、2024年最新五大企业邮箱盘点   下面提到的…

Redis缓存高可用集群

Redis集群方案 哨兵集群 在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态&#xff0c;如果master节点异常&#xff0c;则会做主从切换&#xff0c;将某一台slave作为master&#xff0c;哨兵的配置略微复杂&#xff0c;并且性能和高可用性等各…

sql非查询知识点(增删改-crud没有r)

1.建库 create database database_name 2.使用该数据库 use database_name 3.建表 3.1普通建表 create table if not exists actor(actor_id smallint(5) not null primary key comment "主键id",first_name varchar(45) not null comment "名字",last…

双非本科准备秋招(18.1)—— 力扣二叉树

1、404. 左叶子之和 方法一&#xff1a; 可以在父节点判断一下&#xff0c;如果左子树不为null&#xff0c;并且左子树没有左右子树&#xff0c;说明这是个左叶子节点。 class Solution {public int sumOfLeftLeaves(TreeNode root) {if(root null) return 0;int LV sumOfL…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Rating组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Rating组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Rating组件 提供在给定范围内选择评分的组件。 子组件 无。 接口 Rating(opt…

ERR_SSL_VERSION_OR_CIPHER_MISMATCH

我在namesilo买的域名&#xff0c;coludflare做的解析&#xff0c;华为云的SSL&#xff0c;用宝塔部署的SSL&#xff0c;访问https报错&#xff0c;http却正常&#xff1a; 报错&#xff1a;此网站无法提供安全连接www.hongkong.ioyunxin.top 使用了不受支持的协议。 ERR_SSL_…

Cocos creator 3.x 刚体组件碰撞无效

Cocos creator 3.x 刚体组件碰撞无效 问题描述&#xff1a;只有一个circleCollider2D时&#xff0c;可以在碰撞时正确输出结果&#xff0c;但是当我在外围加了一个circle之后&#xff0c;期望character进入圆圈范围时就触发方法&#xff0c;此时原代码失效 import { _decorat…

gcore服务器设置root账号密码登录

这个厂商很奇怪&#xff0c;默认只能用centos用户与公钥登录&#xff0c;但是这样有时候很麻烦。 他默认开启了SELinux&#xff0c;和强制ssh密钥登录。 下面所有操作在root模式下进行 SELinux设置为兼容模式 setenforce 0vi /etc/selinux/config然后将文件中的SELINUXenfo…

阿里云服务器centos_7_9_x64位,3台,搭建k8s集群

目录 1.环境信息 2.搭建过程 2.1 安装Docker源 2.2 安装Docker 2.3 安装kubeadm&#xff0c;kubelet和kubectl 2.4 部署Kubernetes Master(node1) 2.5 安装Pod网络插件&#xff08;CNI&#xff09; 2.6 加入Kubernetes Node 2.7 测试kubernetes集群 3.部署 Dashboard…

pytorch tensor维度变换

目录 1. view/reshape2. squeeze/unsqueeze3. expand 扩展4. repeat5 .t转置6. transpose7. permute 1. view/reshape view(*shape) → Tensor 作用&#xff1a;类似于reshape&#xff0c;将tensor转换为指定的shape&#xff0c;原始的data不改变。返回的tensor与原始的tensor…

【Linux系统学习】1.初识Linux

初识Linux 操作系统概述 初识Linux 虚拟机介绍 VMware WorkStation安装 1.操作系统概述 了解操作系统的作用 了解常见的操作系统 1.1 硬件和软件 计算机由哪两个主要部分组成&#xff1f; 硬件&#xff1a;计算机系统中由电子&#xff0c;机械和光电元件等组成的各种物理装置的…

小红的字符串中值

题目描述: 小红定义一个长度为奇数的字符串的“中值”为中间那个字符。例如"kou"的中值是o。 现在小红拿到了一个字符串&#xff0c;她想知道某个字符是多少个子串的中值。你能帮帮她吗&#xff1f; 输入描述: 输出描述: 一个整数&#xff0c;代表中值为chr的连续子串…

2024/02/06

画出TCP三次握手和四次挥手的示意图 三次握手 四次挥手 并且总结TCP和UDP的区别 TCP: TCP提供面向连接的&#xff0c;可靠的数据传输服务传输过程中&#xff0c;数据无误、数据无丢失、数据无失序、数据无重复 TCP会给每个数据包编上编号&#xff0c;该编号称之为序列号每个序…