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

【数据结构】堆(Heap):堆的实现、堆排序、TOP-K问题

目录

堆的概念及结构

​编辑

堆的实现 

实现堆的接口

堆的初始化

堆的打印

堆的销毁

获取最顶的根数据

 交换

堆的插入(插入最后)

向上调整(这次用的是小堆)

堆的删除(删除根)

向下调整(这次用的小堆)

堆排序

TOP-K问题


堆的概念及结构

如果有一个关键码的集合 K = { , , , ,},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0, 1 , 2…,则称为小堆 ( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

小根堆:父亲节点大于等于孩子节点

大根堆:父亲节点小于等于孩子节点 

堆的实现 

实现堆的接口

#define CRT_SECURE_NO_WARNING 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<stdbool.h>//二叉树-堆
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void AdjustUp(HPDataType* a, int child);void AdjustDown(HPDataType* a, int n, int parent);//交换
void Swap(HPDataType* p1, HPDataType* p2);
//打印
void HeapPrint(HP* php);
//初始化
void HeapInit(HP* php);
//
void HeapInitArray(HP* php, int* a, int n);
//销毁
void HeapDestroy(HP* php);
//插入
void HeapPush(HP* php, HPDataType x);
//删除
void HeapPop(HP* php);
//返回最顶数据
HPDataType HeapTop(HP* php);
//判空
bool HeapEmpty(HP* php);

堆的初始化

//初始化
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}

堆的打印

void HeapPrint(HP* php)
{assert(php);//最后一个孩子下标为size-1for (size_t i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");
}

堆的销毁

//销毁
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

获取最顶的根数据

//获取根数据
HPDataType HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}

 交换

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}

堆的插入(插入最后)

先考虑扩容,将数据插到最后,再用向上调整法。

//插入数据
void HeapPush(HP* php, HPDataType x)
{assert(php);//扩容if (php->size == php->capacity)//有效元素个数和容量是否相等{//相等的情况分两种:1.容量为0,先扩4个sizeof   2.容量占用满了,扩2个int newCapacity =php->capacity == 0 ? 4 : php->capacity * 2;//返回扩容后的内存新地址							//扩容后的新大小HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}//扩容后的新地址php->a = tmp;//新容量php->capacity = newCapacity;}//   php->size下标位置  先将x放最后,后面再调整php->a[php->size] = x;//   有效数据++php->size++;//   向上调整     //size-1为新插入数据的下标AdjustUp(php->a, php->size - 1);}

向上调整(这次用的是小堆)

向上调整的前提:左右子树是堆 ,时间复杂度O(logN)

//向上调整                    //新插入的数据下标
void AdjustUp(HPDataType* a, int child)
{   //定义其父节点的下标int parent = (child - 1) / 2;//循环while (child > 0){//如果子小于父就交换  (小堆)if (a[child] < a[parent]){//数值交换Swap(&a[child], &a[parent]);//下标child = parent;parent = (parent - 1) / 2;}else{break;}}
}

堆的删除(删除根)

先判空,看下是否还有元素可以删除。根数据先和最后一个孩子交换位置,孩子再向下调整。

//删除
void HeapPop(HP* php)
{assert(php);//确保有元素可删assert(php->size > 0);//最后一个孩子和要删除的根交换Swap(&php->a[0], &php->a[php->size - 1]);//有效元素size减减,相当于删除了交换后的原来的根--php->size;//删除后向下调整AdjustDown(php->a, php->size, 0);}

向下调整(这次用的小堆)

向下调整的前提:左右子树是堆 

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;//n下标位置已经没有数了while (child < n){//选小的孩子往上浮(小堆)if (child + 1 < n && a[child + 1] < a[child]){++child;}//若小的孩子都小于父,则交换if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//交换后下来的数重新变成parent,继续向下调整parent = child;child = parent * 2 + 1;}}
}

堆排序

1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序
建堆:向上调整法建堆的时间复杂度:O(N*logN)
           向下调整法建堆的时间复杂度:O(N)
可以用堆删除思想向下调整法将栈顶和最后一个元素交换,依次将最大的次大的......往后放,就达到了升序排列。
void HeapSort(int* a, int n)
{//建堆  这里可以选建大堆还是小堆// 向下调整建堆// O(N)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;}
}

TOP-K问题

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

先创建一个包含有10000000个数的data.txt文本文件。

void CreateNDate()
{// 造数据int n = 10000000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 10000000;fprintf(fin, "%d\n", x);}fclose(fin);
}

前k个建小堆(堆顶元素为k中最小),剩余n-k个依次和堆顶元素比较,比k大就插入堆中(插入堆插入向下调整法),完成后打印前k个元素。

void PrintTopK(const char* filename, int k)
{// 1. 建堆--用a中前k个元素建堆FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");return;}//给堆开辟空间int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc fail");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);}// 前k个数建小堆for (int i = (k - 2) / 2; i >= 0; --i){AdjustDown(minheap, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > minheap[0]){// 替换你进堆minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}printf("\n");free(minheap);fclose(fout);
}

假设k等于5,成功打印出前5个最大的数据

http://www.lryc.cn/news/230629.html

相关文章:

  • 保护数字前沿:下一代防火墙如何塑造网络安全的未来
  • 深入理解Java中的String.join方法
  • 【MySQL系列】 第三章 · 函数
  • 微信小程序wxss定位/选择/查找元素的几种方式
  • Canvas—从入门到案例实现
  • 飞书开发学习笔记(六)-网页应用免登
  • 【ROS】Nav2源码下载、编译、运行
  • 微信小程序 30分钟倒计时功能
  • JAVA判断指定日期是否在指定的时间段内
  • 关于晋升与跳槽的一些思考
  • url找不到404的问题,url被拼接
  • 如何解决golang开发中遇到的报错:checksum mismatch downloaded
  • 4.以docker容器生成镜像推送到阿里云镜像仓库
  • CSS Form表单布局
  • c++ shared_mutex 读写锁使用详解
  • 淘宝商品详情接口,淘宝详情页接口,宝贝详情页接口,商品属性接口,商品信息查询,商品详细信息接口,h5详情,淘宝API接口演示案例
  • python爬取网站数据,作为后端数据
  • 【机器学习】K近邻算法:原理、实例应用(红酒分类预测)
  • 基于安卓android微信小程序的快递取件及上门服务系统
  • leetCode 92.反转链表 II + 图解
  • 【MongoDB】索引 – 通配符索引
  • python安装pip install报错Could not fetch URL https://pypi.org/simple/pip/...更换镜像源
  • C++ 算数运算符 学习资料
  • 问题 H: 棋盘游戏(二分图变式)
  • SQL 主从数据库实时备份
  • C/C++:在#define中使用参数
  • Hive 查询优化
  • 【Java 进阶篇】JQuery 案例:优雅的隔行换色
  • Redis 常用的类型和 API
  • 在qt的设计师界面没有QVTKOpenGLWidget这个类,只有QOpenGLWidget,那么我们如何得到QVTKOpenGLWidget呢?