堆+堆排序+topK问题

news2025/1/13 2:25:39

目录

堆:

1、堆的概念

2、堆的结构

3、堆的实现

3.1、建堆

3.1.1、向上调整建堆(用于堆的插入)

3.1.2、向下调整建堆

3.2、堆的删除

3.3、堆的代码实现

3.3.1、Heap.h

3.3.2、Heap.c

堆排序:(O(N*log(N)))

1、排序如何建堆

2、代码实现(以小堆为例)

topK问题:

1、方法1

2、方法2


堆:

1、堆的概念

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

2、堆的结构

1、堆中某个结点的值总是<=或>=其父结点的值

2、堆 逻辑上 是一棵完全二叉树物理上数组

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆

3、堆的实现

3.1、建堆

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
3.1.1、向上调整建堆(用于堆的插入)

适用:

1、已经是堆了,要进行插入时,只能用向上调整建堆

2、对于数组(N个元素),可以用向上调整建堆,模拟插入的过程(插入一个,调整一次),但是其时间复杂度为O(N*log(N)),对于一堆的数据,不建议使用向上调整建堆

void AdjustUp(HPDataType* a, int child)//建小堆,child为插入元素的下标
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])//要建大堆,< 改 >
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
			break;//没有交换,则就是在这个位置
	}
}

int main()
{
	int arr[] = { 7,4,1,7,9,3,5,2,6,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		AdjustUp(arr, i);
	}
	return 0;
}

 3、O(N*log(N))证明

节点数量为N个,根节点为第1层,树的高度为h层,h = log(N+1)

第h层   :2^(h-1)个节点,向上调整h-1次

第h-1层:2^(h-2)个节点,向上调整h-2次

第2层   :2^1个节点,向上调整1次

第1层   :2^0个节点,向上调整0次

F(h) = 2^0 * 0 + 2^1 * 1 + ……+ 2^(h-2) * (h-2) + 2^(h-1) * (h-1) = 2^h * (h-2) + 2

        = (N+1)*(log(N+1) - 2) + 2   ,忽略后为 N*log(N)

3.1.2、向下调整建堆

适用:

1、对于数组(N个元素)建议使用 向下调整建堆 时间复杂度为O(N),更高效

2、向下调整建堆的前提左右子树必须是堆(保证向下调整完后,还是堆),因此,从第一个非叶子节点开始(对于叶子节点,可以认为就是堆),向下调整建堆

void AdjustDown(HPDataType* a, int n, int parent)//建小堆,n为a的元素个数,parent为要向下调整的元素下标
{
	assert(a);
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (a[child] > a[child + 1] && child + 1 < n)//假设法,要建大堆,< 改 >
			child++;
		if (a[child] < a[parent])//要建大堆,< 改 >
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * child + 1;
		}
		else
			break;
	}
}

int main()
{
	int arr[] = { 7,4,1,7,9,3,5,2,6,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = (sz - 1 - 1) / 2; i >= 0; i--)//倒数第一个非叶子节点,开始遍历
	{
		AdjustDown(arr, sz, i);
	}
	return 0;
}

3、 O(N)证明

节点数量为N个,根节点为第1层,树的高度为h层,h = log(N+1)

第h层   :2^(h-1)个节点,向下调整0次

第h-1层:2^(h-2)个节点,向下调整1次

第2层   :2^1个节点,向上调整h-2次

第1层   :2^0个节点,向上调整h-1次

F(h) = 2^0 * (h-1) + 2^1 * (h-2) +……+ 2^(h-2) * 1 + 2^(h-1) * 0 = 2^h - 1 - h = N - log(N+1)

忽略后为  N

3.2、堆的删除

删除最后一个元素,无意义,删除堆是删除堆顶的数据,

如果是挪动数据,关系乱了,要重新建堆,效率低

所以将堆顶的数据最后一个数据交换,size--,再进行向下调整算法

void HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);
	Swap(&(php->_a[0]), &(php->_a[php->_size - 1]));
	php->_size--;
	AdjustDown(php->_a, php->_size, 0);
}

3.3、堆的代码实现

3.3.1、Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);

void HeapInit(Heap* php);
void HeapDestory(Heap* php);
void HeapPush(Heap* php, HPDataType x);
void HeapPop(Heap* php);
HPDataType HeapTop(Heap* php);
int HeapSize(Heap* php);
int HeapEmpty(Heap* php);
3.3.2、Heap.c
#include "Heap.h"

void HeapInit(Heap* php)
{
	assert(php);
	php->_a = NULL;
	php->_size = php->_capacity = 0;
}

void HeapDestory(Heap* php)
{
	assert(php);
	free(php->_a);
	php->_a = NULL;
	php->_size = php->_capacity = 0;
}

void HeapCapacity(Heap* php)
{
	assert(php);
	if (php->_size == php->_capacity)
	{
		php->_capacity = php->_capacity == 0 ? 4 : 2 * php->_capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->_a,sizeof(HPDataType) * php->_capacity);
		if (tmp == NULL)
		{
			perror("HeapCapacity()::realloc()");
			return;
		}
		php->_a = tmp;
	}
}

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

void AdjustUp(HPDataType* a, int child)//建小堆,child为插入元素的下标
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])//要建大堆,< 改 >
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
			break;//没有交换,则就是在这个位置
	}
}

void AdjustDown(HPDataType* a, int n, int parent)//建小堆,n为a的元素个数,parent为要向下调整的元素下标
{
	assert(a);
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (a[child] > a[child + 1] && child + 1 < n)//假设法,要建大堆,< 改 >
			child++;
		if (a[child] < a[parent])//要建大堆,< 改 >
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * child + 1;
		}
		else
			break;
	}
}

void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	HeapCapacity(php);//扩容
	php->_a[php->_size++] = x;
	AdjustUp(php->_a, php->_size-1);
}

void HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);
	Swap(&(php->_a[0]), &(php->_a[php->_size - 1]));
	php->_size--;
	AdjustDown(php->_a, php->_size, 0);
}

HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);
	return php->_a[0];
}

int HeapSize(Heap* php)
{
	assert(php);
	return php->_size;
}

int HeapEmpty(Heap* php)
{
	assert(php);
	if (php->_size == 0)
		return 1;
	else
		return 0;
}

堆排序:(O(N*log(N))

对于数组,建堆时,用向下调整建堆,效率高,

此时是一定程度脱离了堆这个数据结构(没有用push,向上调整建堆),只是用了向下调整的方法

1、排序如何建堆

升序:建大堆

如果建小堆,第1个数已经排好了,后面的数据,关系乱了(兄弟变父子,左右子树很可能不是堆了),要多次向下调整,才建好堆,效率低,

那建大堆,首尾交换(兄弟还是兄弟,左右子树还是堆,一次向下调整就建好堆)再伪删除,"删除"完,就排好升序了

建堆(向下调整),时间复杂度为O(N)

排序(首尾交换,向下调整),时间复杂度为O(N*log(N))

节点数量为N个,根节点为第1层,树的高度为h层,h = log(N+1)

第h层   :2^(h-1)个节点,交换,向下调整h-1次

第h-1层:2^(h-2)个节点,交换,向下调整h-2次

第2层   :2^1个节点,交换,向下调整1次

第1层   :2^0个节点,交换,向下调整0次

F(h) = 2^0 * 0 + 2^1 * 1 + ……+ 2^(h-2) * (h-2) + 2^(h-1) * (h-1) = 2^h * (h-2) + 2

        = (N+1)*(log(N+1) - 2) + 2   ,忽略后为 N*log(N)

降序:建小堆

依次类推

2、代码实现(以小堆为例)

void Heapsort(int* arr,int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//倒数第一个非叶子节点,开始遍历
	{
		AdjustDown(arr, n, i);
	}
	int end = n;
	while (end>1)
	{
		Swap(&(arr[0]), &(arr[end-1]));
		end--;//伪删除
		AdjustDown(arr,end,0);
	}
}

int main()
{
	int arr[] = { 7,4,1,7,9,3,5,2,6,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Heapsort(arr,sz);
	return 0;
}

topK问题:

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般N远大于K

以下 以找出前K个最大的数为例  N远大于K

1、方法1

建N个数的大堆O(N)

popK次            O(K*log(N))

时间复杂度为O(N),但对空间要求太大

2、方法2

建一个K个小堆(先从文件里读K个元素,向下调整建堆),再从文件里读一个个元素,大于堆顶元素,就覆盖,向下调整

void CreateNDate()
{
	// 造数据
	int n = 100000;
	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;//自己可以改10个大于10000000,相当于10标记,验证结果
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void TestHeap3()
{
	int k;
	printf("请输入k>:");
	scanf("%d", &k);
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读取文件中前k个数
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	// 建K个数的小堆
	for (int i = (k-1-1)/2; i>=0 ; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	// 读取剩下的N-K个数
	int x = 0;
	while (fscanf(fout, "%d", &x) > 0)
	{
		if (x > kminheap[0])
		{
			kminheap[0] = x;
			AdjustDown(kminheap, k, 0);
		}
	}

	printf("最大前%d个数:", k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
	fclose(fout);
}

注意:

交换后,向下调整,是一次,O(log(N))

直接堆数组向下调整建堆,是多次,O(N)

降序,找出前K个最大,建小堆

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

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

相关文章

接口测试用例的编写

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、接口测试发现的典型问题 接口测试经常遇到的bug和问题&#xff0c;如下&#xff1a; 传入参数处理不当&#xff0c;导致程序crash类型溢出&#xff0c;导…

下载chromedriver驱动

首先进入关于ChromeDriver最新下载地址&#xff1a;Chrome for Testing availability 进入之后找到与自己所匹配的&#xff0c;在浏览器中查看版本号&#xff0c;下载版本号需要一致。 下载即可&#xff0c;解压&#xff0c;找到 直接放在pycharm下即可 因为在环境变量中早已配…

严重干扰的验证码识别系统源码分享

严重干扰的验证码识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…

领夹麦克风哪个品牌好?大疆、西圣、博雅无线麦克风在线测评

​随着科技的不断进步&#xff0c;越来越多的专业音频设备出现在大家的视野中&#xff0c;无线领夹麦克风就是其中之一&#xff0c;并且很多人在视频创作、直播等场景中都会进行选购。但是近些年来无线领夹麦克风市场较为复杂&#xff0c;很多质量不佳的产品混杂其中&#xff0…

安全生产许可证的重要性

在现代社会&#xff0c;安全生产许可证对于企业来说&#xff0c;不仅仅是一种法律要求&#xff0c;更是一种社会责任和企业形象的体现。本文将深入探讨安全生产许可证的重要性&#xff0c;以及它如何影响企业的长期发展和社会责任。 一、法律合规性的重要性 安全生产许可证是企…

Windows上指定盘符-安装WSL虚拟机(机械硬盘)

参考来自于教程1&#xff1a;史上最全的WSL安装教程 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/386590591#%E4%B8%80%E3%80%81%E5%AE%89%E8%A3%85WSL2.0 教程2&#xff1a;Windows 10: 将 WSL Linux 实例安装到 D 盘&#xff0c;做成移动硬盘绿色版也不在话下 - 知乎 (z…

那些网站需要使用OV SSL证书?怎么申请?

&#x1f512; 需要OV SSL证书的网站 &#x1f3e2; 企业网站 公司官网&#xff1a;展示公司信息、产品服务&#xff0c;增强信任度。 电子商务网站&#xff1a;处理在线交易&#xff0c;保护用户数据安全。 &#x1f3e6; 金融网站 银行网站&#xff1a;处理金融交易&#x…

SMB流量分析

SMB协议流量主要分为以下几个阶段 1、 tcp三次握手 2、会话建立(Negotiate Protocol)&#xff1a; 客户端发送Negotiate Protocol Request &#xff0c;其中包含客户端支持的smb协议版本列表以及SMB Header信息 服务端发送Negotiate Protocol Response&#xff0c;包含服务…

Springboot中自定义监听器

一、监听器模式图 二、监听器三要素 广播器&#xff1a;用来发布事件 事件&#xff1a;需要被传播的消息 监听器&#xff1a;一个对象对一个事件的发生做出反应&#xff0c;这个对象就是事件监听器 三、监听器的实现方式 1、实现自定义事件 自定义事件需要继承ApplicationEv…

Git学习尚硅谷(005 idea集成git)

尚硅谷Git入门到精通全套教程&#xff08;涵盖GitHub\Gitee码云\GitLab&#xff09; 总时长 4:52:00 共45P 此文章包含第27p-第p32的内容 文章目录 忽略特定文件在家目录里创建这个文件在.gitconfig文件里配置这个文件 配置IDEA定位到git程序进行添加文件初始化本地库添加单个…

yolov1到yolov5的发展

基础概念 1. YOLO简介 YOLO&#xff08;You Only Look Once&#xff09;&#xff1a;是一种基于深度神经网络的对象识别和定位算法&#xff0c;其最大的特点是运行速度很快&#xff0c;可以用于实时系统。 2. 目标检测算法 RCNN&#xff1a;该系列算法实现主要为两个步骤&am…

数据结构排序——归并排序递归与非递归

基本思想&#xff1a; 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个…

CANopen协议的理解

本文的重点是对CANopen协议的理解&#xff0c;不是编程实现 参考链接 canopen快速入门 1cia301协议介绍_哔哩哔哩_bilibili CANopen是什么&#xff1f; CANopen通讯基础&#xff08;上&#xff09;_哔哩哔哩_bilibili CANopen概述 图1. CAN报文标准帧的格式 CAN的报文可简单…

50projects50days案例代码分析学习、效果,Html+CSS+JavaScript小案例

案例来源于&#xff1a;https://github.com/bradtraversy/50projects50days&#xff0c;部分资源需要科学上网加载使用&#xff0c;往后不再赘述。 合集链接&#xff0c;欢迎订阅&#xff1a; https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzkwODY2OTA5NA&actiongetal…

Invoke-Maldaptive:一款针对LDAP SearchFilter的安全分析工具

关于Invoke-Maldaptive MaLDAPtive 是一款针对LDAP SearchFilter的安全分析工具&#xff0c;旨在用于对LDAP SearchFilter 执行安全解析、混淆、反混淆和安全检测。 其基础是 100% 定制的 C# LDAP 解析器&#xff0c;该解析器处理标记化和语法树解析以及众多自定义属性&#x…

35岁失业后:靠这几个AI副业,也能养活自己

最近几年连续的经济下行&#xff0c;到现在已经彻底传导到所有行业&#xff0c;波及到越来越多的人… 这种波及&#xff0c;最集中反映在失业率上&#xff0c;今年又是1179万应届生毕业即失业&#xff0c;加入到庞大的就业漩涡中&#xff0c;35岁裁员已成常态。 大环境确实如此…

RocketMQ 基础入门

文章内容是学习过程中的知识总结&#xff0c;如有纰漏&#xff0c;欢迎指正 文章目录 前言 RocketMQ 特点 RocketMQ 优势 1. RocketMQ 基本概念 1.1 NameServer 1.1.1 NameServer作用 1.1.2 和zk的区别 1.1.3 高可用保障 1.2 Broker 1.2.1 部署方式 1.2.1.1 单 Master 1.2.1.2 …

OpenAI推出o1,一个能够自我事实核查的模型

ChatGPT的开发者OpenAI宣布了其下一次重大产品发布&#xff1a;一个代号为“Strawberry”&#xff08;草莓&#xff09;的生成式AI模型&#xff0c;正式名称为OpenAI o1。 更准确地说&#xff0c;o1实际上是一个模型家族。周四&#xff0c;两个版本将在ChatGPT和OpenAI的API中…

最新kubernetes的安装填坑之旅(新手篇)

Kubernetes&#xff08;常简称为 K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化部署、扩展和管理容器化应用程序&#xff0c;lz也不知道哪根脑经秀逗了&#xff0c;竟然妄挑战学习一下&#xff0c;结果折戟沉沙&#xff0c;被折腾的欲仙欲死&#xff0c;不过…

26577flask旧衣物捐赠系统—计算机毕业设计源码26577

摘要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作规…