堆的实现,画图和代码分析建堆,堆排序,时间复杂度以及TOP-K问题

news2024/11/19 11:20:18

堆的实现

  • 堆的概念及结构
  • 堆的实现
    • 初始化
    • 销毁
    • 返回堆顶元素
    • 判空
    • 有效数据个数
    • 堆的插入(向上调整算法)
    • 删除堆顶元素,仍然保持堆的形态(向下调整算法)
  • 堆的创建
    • 向上调整法建堆
    • 向下调整建堆
    • 两种建堆方法时间复杂度
      • 向下调整法建堆时间复杂度分析
      • 向上调整法建堆时间复杂度分析
  • 堆排序
  • TOP-k问题

堆的概念及结构

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

堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。

在这里插入图片描述

根据数组下标我们可以得到父子结点下标之间的关系

在这里插入图片描述

堆的实现

堆其实就是一根完全二叉树,我们可以采用数组的方式实现

初始化

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

销毁

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

返回堆顶元素

//返回堆顶元素
HPDatatype HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpt(php));

	return php->a[0];
}

判空

//判空
bool HeapEmpt(Heap* php)
{
	assert(php);
	return php->size == 0;
}

有效数据个数

//数据个数
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

堆的插入(向上调整算法)

插入一个数据,也要保持大堆或(小堆)
在这里插入图片描述

void Swap(HPDatatype* x, HPDatatype* y)
{
	HPDatatype tmp = *x;
	*x = *y;
	*y = tmp;
}

//向上调整
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 = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}

//插入,仍保持堆的形态
void HeapPush(Heap* php, HPDatatype x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDatatype* tmp = (HPDatatype*)realloc(php->a, newcapacity * sizeof(HPDatatype));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;

	//向上调整
	AdjustUp(php->a, php->size - 1);
}

删除堆顶元素,仍然保持堆的形态(向下调整算法)

删除堆顶元素,是为了得到次大次少的元素。
既然用数组实现的堆,那删除元素可以直接像顺序表那样把后边元素向前移,覆盖前面元素吗?
在这里插入图片描述
显然是不可以的,这个就称不上堆了。
所以使用下面这种方法。
在这里插入图片描述

//向下调整
void AdjustDown(HPDatatype* a, int n, int parent)
{
	int minchild = parent * 2 + 1;
	while (minchild < n)
	{
		if (minchild + 1 < n && a[minchild+1] < a[minchild])
		{
			minchild++;
		}

		if (a[minchild] < a[parent])
		{
			Swap(&a[minchild], &a[parent]);
			parent = minchild;
			minchild = parent * 2 + 1;
		}
		else
		{
			break;
		}

	}
}

//删除堆顶元素,仍然保持堆的形态
void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpt(php));

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	//向下调整
	AdjustDown(php->a, php->size, 0);
}

注意向下调整算法如果使用if和else来判断该父节的孩子结点谁大谁小这样会显得代码冗余,所以我们假设最小的是左孩子,然后左孩子和右孩子比较一下。看看那个是最小的,这样我们的代码会简单很多。值得我们借鉴。

其实大家发现没有,我们的建堆和堆排序已经完成了,

在这里插入图片描述

但是如果让我们排序一组数据,难道我们要把写一个堆然后在排序吗?

首先把数组的数据拷贝到堆里,然后进行建堆排序,结果我们只是让堆里面的数据变得有序了,数组里面的数据还是无序的,还得把堆里面数据拷贝回数组里,空间复杂度O(N);这样实在太麻烦了。所以我们考虑一下直接对数组进行建堆排序。

堆的创建

我们这里建的都是小堆。如果建大堆的话,改一下符号就可以了。

在这里插入图片描述
在这里插入图片描述

向上调整法建堆

void HeapSort(int* a, int n)
{
	//向上调整法建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
}

我们假设第一个元素就是一个堆,所以从下标为1的元素开始调整。

在这里插入图片描述

向下调整建堆

在这里插入图片描述

void HeapSort(int* a, int n)
{
	//向下调整法建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
}

最后一个结点下标n-1,(n-1-1)/ 2是最后一个非叶子结点下标。

两种建堆方法时间复杂度

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

向下调整法建堆时间复杂度分析

在这里插入图片描述

向上调整法建堆时间复杂度分析

在这里插入图片描述

因此建堆,我们应该选择向下调整建堆法

堆排序

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

  1. 建堆
    升序:建大堆
    降序:建小堆

  2. 利用堆删除思想来进行排序
    建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

排序算法思想:每次交换第一个和最后一个元素,然后把最后一个元素不再看成堆里的元素,再进行向下调整(选择次大次小的元素位于堆顶)。依次下次直到所有元素排序完毕。

void HeapSort(int* a, int n)
{
	//向上调整法建堆O(NlogN)
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}


	//向下调整法建堆O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	//堆排序O(NlogN)
	int i = 1;
	while (i < n)
	{
		Swap(&a[0], &a[n - i]);
		AdjustDown(a, n - i, 0);
		++i;
	}
}

在这里插入图片描述

TOP-k问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

假设我们要求给出数据前K个最大的元素。我们用有三种思路解决这个问题

1.堆排序
2.建大堆,把N个元素建成大堆,堆顶就是最大的元素,取堆顶元素,然后再Pop堆顶元素,总共Pop k次。
3.建小堆,把前k的元素建小堆,后N-k个元素于堆顶元素比较,比堆顶元素大就让该元素占据堆顶位置,然后再调整堆。

分析一下每种方法时间复杂度把。
堆排序O(Nlog2N);

建大堆,建堆O(N),调整堆O(log2N),总共调整K次,时间复杂度O(N+log2N*K),空间复杂度O(N)

建小堆,建堆O(K),调整堆O(log2K),假设最坏调整N-K次,时间复杂度O(K+log2K*(N-K)),约等于O(N),空间复杂度O(K)

还可以从下面这个角度再分析。
如果数据很大有100w个,而我们只取很小的K呢。代入时间时间复杂度看一看。

所以解决TOP-K问题最佳方式

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

下面是生成随机数放在文件里,再从文件读取K个数据建成小堆,再把N-K个与堆顶元素进行比较。。。。关于想了解文件的请点击这里文件然后结合看下面代码。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void GenerateFile(const char* filename, int N)
{
	//把数据写入文件里
	FILE* fin = fopen(filename, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}

	srand((unsigned int)time(NULL));

	//写
	for (int i = 0; i < N; i++)
	{
		fprintf(fin, "%d ", rand());
	}


	fclose(fin);
}

void PrintTopk(const char* filename, int k)
{
	//把文件中数据读到内存
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	int* minHeap = (int*)malloc(k * sizeof(int));
	if (minHeap == NULL)
	{
		perror("malloc fail");
		return;
	}

	//读k个数据
	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);
	}

	int val = 0;
	//继续读取N-K个元素
	while (fscanf(fout, "%d", &val) != EOF)
	{
		if (val > minHeap[0])
		{
			minHeap[0] = val;
			//调整堆
			AdjustDown(minHeap, k, 0);
		}
	}

	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minHeap[i]);
	}

	fclose(fout);

}
int main()
{
	const char* filename = "Data.txt";
	int k = 10;
	int N = 10000;
	GenerateFile(filename, N);
	PrintTopk(filename, k);

	return 0;
}

为了验证这个代码是否正确,我们可以把这段代码

	//写
	for (int i = 0; i < N; i++)
	{
		fprintf(fin, "%d ", rand());
	}

改成

	//写
	for (int i = 0; i < N; i++)
	{
		fprintf(fin, "%d ", rand()%10000);
	}

这样就得到随机数为10000里的数字,再在文件里添加10001-10010,千万注意不要再生成文件了,不然你的数据就会被覆盖。然后打印,是我们想要的结果。

在这里插入图片描述

自此关于堆的实现,建堆,堆排序以及TOP-K问题再一篇文章里都解决了,欢迎有问题的小伙伴们再评论区提问,点赞,收藏。一键三连哦!万分感谢!

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

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

相关文章

Go并发情况下debug调试无法进入所需调试goroutine,附带并发测试代码

大部分情况 问题&#xff1a; 在使用go语法的时候&#xff0c;由于并发情况&#xff0c;只能调试一个goroutine&#xff0c;但存在随机性&#xff0c;难指定 找到你所需的线程直接切换即可跳转。没有找到同时调试多个的办法&#xff0c;理论上是不行的&#xff0c;不然就不叫并…

美颜sdk的商业价值分析:如何利用美颜技术赢得市场?

当下&#xff0c;从自拍软件到直播平台&#xff0c;从手机相机到电商平台&#xff0c;美颜技术都有着广泛的应用。而美颜sdk作为美颜技术的重要组成部分&#xff0c;其商业价值也越来越受到关注。 一、美颜sdk有哪些商业价值&#xff1f; 随着美颜技术的不断发展&#xff0c…

历年系统架构师下午真题详解

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 试题一是必答题 阅读以下关于软件架构设计与评估的叙述&#xff0c;在答题纸上回答问题1和问题2。 【说明】 某电子商务公司拟升级其会员与促销管理系统&#…

LinkedList 的作者:我写了 LinkedList,但我自己都不用

Joshua Bloch&#xff1a;我写了 LinkedList&#xff0c;但我自己都不用&#xff01; 对&#xff0c;Joshua Bloch 就是 LinkedList 的作者&#xff01; 如果你真信了作者的话&#xff0c;那就真的大错特错了&#xff0c;LinkedList 虽然用的没有 ArrayList 多&#xff0c;但使…

FISCO BCOS(三十六)———Python Sdk window环境部署及应用开发

1、环境要求 Python环境:python 3.6.3, 3.7.3(最好是3.7.3,因为我是) FISCO BCOS节点:可以直接建链,可以是节点前置,也可以是一键部署2、安装python 2.1、python下载地址 https://www.python.org/downloads/release/python-373/2.2、环境变量配置 3、下载pip 3.1、pip下…

【企业信息化】第1集 世界排名第一的免费开源ERP: Odoo 16 CRM客户关系管理系统

文章目录 前言一、概览二、使用功能1.加快销售速度2.销售线索3.机会4.客户5.高效沟通6.报告7.集成 三、总结 前言 世界排名第一的免费开源ERP: Odoo 16 CRM客户关系管理系统。真正以客户为中心的CRM。 一、概览 获得准确预测 使用可操作数据&#xff0c;以做出更好的决定。 获…

BUUFCTF—[极客大挑战 2019]Upload 1

考点&#xff1a; 文件上传黑名单绕过内容绕过目录浏览 文件上传 1. 测试是黑名单还是白名单 上传图片1.png成功&#xff0c;上传1.php失败&#xff0c;1.pngs成功&#xff0c;说明是黑名单过滤。 所以要寻找黑名单外的php文件&#xff1a;php, php3, php5, php7, phtml都能…

Linux基础学习---4、文件权限类、搜索查找类、压缩和解压类

1、文件权限类 1.1 文件属性 Linux系统是一种典型的多用户系统&#xff0c;不同的系统处于不同的地位&#xff0c;拥有不同的权限。为了保护系统的安全性&#xff0c;Linux系统对不同的用户访问访问同一文件&#xff08;包括目录文件&#xff09;的权限做了不同的规定。在Lin…

港联证券|北交所行情活跃 近九成个股5月以来录得上涨

北交所A股昨日表现活跃&#xff0c;北证50指数上涨1.92%。数字人30cm涨停&#xff0c;云创数据涨18.38%&#xff0c;流金科技涨16.61%&#xff0c;广道数字、艾融软件、惠丰钻石等涨幅居前。 数字人昨日录得近16个月以来最大涨幅。近3个交易日&#xff0c;数字人累计获得融资净…

微服务架构下网关的技术选型

一、简介 当使用单体应用程序架构时&#xff0c;客户端&#xff08;Web 或移动端&#xff09;通过向后端应用程序发起一次 REST 调用来获取数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表&#xff0c;并将响应返回给客户端。微…

ant-design实现树的穿梭框,穿梭后右侧是已选树(一)

主要内容: 基于ant-design树的穿梭框&#xff0c;实现穿梭后右侧是已选树&#xff0c;&#xff08;当前antd右侧只有一个层级&#xff09; 理想的树的穿梭框&#xff1a; 左边是完整的树&#xff0c;右边是已选的树&#xff0c;左边已选穿梭到右边左边已选的消失&#xff0c;右…

LeetCode-206. 反转链表

目录 双指针递归 题目来源 206. 反转链表 双指针 定义两个指针&#xff1a; pre 和 cur&#xff1b;pre 在前 cur 在后。 每次让 cur 的 next 指向 pre&#xff0c;实现一次局部反转 局部反转完成之后&#xff0c;pre 和 cur 同时往前移动一个位置 循环上述过程&#xff0c;直…

AWS S3 跨账号迁移

目录 **迁移架构&#xff1a;****具体实施&#xff1a;****1. 在目标账号创建策略&#xff08;S3MigrationPolicy&#xff09;和角色&#xff08;S3MigrationRole&#xff09;****2. 安装 aws cli&#xff0c;并配置$ aws configure&#xff0c;**[请参阅 AWS CLI 文档中的安装…

C++回调函数

回调函数 文章目录 回调函数一、函数指针二、回调函数应用Reference 一、函数指针 指针是一个变量&#xff0c;是用来指向内存地址。 一个程序运行时&#xff0c;所有和运行相关的东西都需要加载到内存当中&#xff0c;因此可以通过指针指向该内存。 函数是存放在内存代码区…

Otter CTF--Network(web网络1-3)

目录 一.题目 1.Birdmans Data 2.Look At Me 3.Otter Leak 一.题目 网址 OtterCTF 1.Birdmans Data 下载文件&#xff1a; .pcap文件 Wireshark打开&#xff1a; 既然是web 我们就从http分析&#xff1a; 追踪流 HTTP流&#xff1a; 发现两个密钥key&#xff1a; {"…

数据集 | 基于语音(Speech)/多模态(Multimodal)的情绪识别数据集,格式及下载

本文主要介绍了一些常用的语音&#x1f5e3;识别数据集&#xff0c;文件格式以及下载地址&#xff1a; 目录 1.IEMOCAP Emotion Speech Database(English) 2.Emo-DB Database(German) 文件命名 对象 3.Ryerson Audio-Visual Database of Emotional Speech and Song (Engli…

解密JS代码:一个有趣的故事

作为一名前端开发者&#xff0c;我们经常需要处理加密和解密的任务。近日&#xff0c;我遇到了一个有趣的故事和一个需要解密的JavaScript代码。让我和你分享一下这个故事以及我是如何解密这段代码的。 最近我收到了一个任务&#xff0c;要将一个网站上的一段JavaScript代码进…

torch_geometric获取datasets(解决连不了外网的问题)

文章目录 1. torch_geometric.data介绍2. 使用Planetoid下载Cora数据集的代码3. 解决程序运行的机器无法联网的问题3.1 尝试运行&#xff0c;查看数据集下载链接3.2 放置到对应文件夹下3.3 重新运行之前写的程序 4. 一点感慨 1. torch_geometric.data介绍 torch_geometric&…

进货商模式玩法解析:当老板、亲自进货、自己赚差价?

如今很多人都看到互联网的发展前景&#xff0c;有了创业的想法&#xff0c;但是资金、技术、市场等问题给他们带来了瓶颈。进货商模式的出现&#xff0c;为这些&#xff08;文章编辑ycy6221&#xff09;有创业想法&#xff0c;有梦想的人打破了这些限制&#xff0c;而且还可以实…

HTTP协议演进:为什么说HTTP/1.1的时代已经过去了

前言 欢迎来到今天的每日一题&#xff0c;每日一提。昨天聊到了&#xff0c;HTTP 是什么。有哪些组成部分。并且最后提到了 HTTP 的一些缺点&#xff0c;比如&#xff1a;性能较低&#xff0c;容易导致网络拥塞和延迟&#xff0c;不支持服务器推送等等。设计协议的大佬们&#…