2024-1-26学习任务:堆实现算法和topK问题

news2024/11/21 0:33:59

前言

本文的学习任务:关于堆的实现以及相关的基础操作,包括向上调整算法和向下调整算法,同时利用该算法解决常见的topk问题,之后再对两种算法的时间复杂度进行分析,加深理解。

1.堆的实现

前面提到过,堆总是一个完全二叉树,那么可以在逻辑上看成一棵二叉树会更加容量理解堆是如何存储数据的,在物理上,我们用一个数组来进行存储。

1.堆的基础功能

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
//向上调整
void Adjustup(HPDataType* a, int child);
//向下调整
void AdjustDown(HPDataType* a, int size, int parent);
//堆排序
void HeapSort(int* a, int n);

既然堆是一个数组,它的创建与顺序表非常类似,这里我们重点讲解堆的插入和删除

1.堆的插入

数组的插入一般是在末尾插入,但插入后要考虑一个问题:就是它插入后,整个数组还是不是一个堆?大堆还是小堆?我们需要进行调整

向上调整算法

以大堆为例子,小堆反过来就可以。

大堆顾名思义就是根节点最大的堆,其次任意一个孩子结点的值都小于父亲结点,当在数组末尾插入值时,我们肯定是在二叉树的最下层插入,当插入后,如果不满足上述情况的话,就得进行调整。

如何调整?交换,和谁交换?自己的父节点。当我们的值插入时,它必定会成为一个孩子结点,如果它的值大于父亲结点,那它就要和父亲结点进行交换,以此类推,直到它到达比所有孩子都大的位置,完成调整。

void Adjustup(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;//求二叉树父亲结点的方法
	while (child > 0)
	{
		if (a[parent] < a[child])//父亲结点的值小于孩子就交换(大堆
		{
			Swap(&a[parent], &a[child]);//简单的交换函数(自己定义即可)
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}

2.堆的删除

向下调整算法

顺序表的删除更加简单,直接将size--就可以了,但是如果在堆也这样使用删除的话,未免有些过于简单且没有意义(和顺序表一样的删法还用堆干嘛),所以一般规定,删除的都是堆顶元素。

这样有一个好处,在建立大堆和小堆时,将需要删除的堆顶元素一返回就是最大值或最小值(后面提到的堆排序就是基于这种思想),和插入一样,在删除元素后,也需要进行调整使它再次成为一个堆。这里就要用到向下调整算法。

所谓向下调整算法,就是找自己的孩子结点的值与其进行比较,(以大堆例)找出最大的孩子结点,如果比父亲大就交换,直到所有结点都完成交换,调整完毕

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;//根据孩子求父亲
	while (child < size)
	{
		if (child+1<size&&a[child]<a[child+1])//假设法,假设第一个孩子最大,如果下一个孩子不越界的情况比它还大,就++
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
		
	}

}

 ps:小堆找最小的孩子,大堆就找最大的孩子

2.堆的应用

1.堆排序

利用堆的思想来进行排序,分两个步骤进行

1.建堆:

升序建大堆,降序建小堆

原理是什么?

我们以大堆为例,一般来说,大堆如果要排的话一定是从大到小,但问题的关键在于任何一个堆都不会涉及到孩子结点之间的关系(例如5,4,都比6小,但有可能会出现6,4,5这样的排法),所以孩子结点之间的大小关系是不能简单的建堆来考虑,我们必须将此情况考虑在内。

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

还是以大堆为例,既然已经知道大堆的根结点就是最大值,那我们可以利用堆删除的思想,将堆顶元素取出与数组末尾交换,交换完后,这个值固定不动,将剩下的值进行建堆,之后再次与末尾交换,这里的末尾是剩下的值的最后一个数,以此类推,这就是堆排序

1.建堆(大堆)

关于建堆的说法。我们将数组的第一个值看成一个堆,然后往里放值,每次都进行向上调整或向下调整即可完成建堆。

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

代码实现
void HeapSort(int* a, int n)
{
	for (int i=(n-2)/2;i>=0;i--)
	{
		//建堆
		AdjustDown(a, n,i);//采用向下调整算法进行建堆
	}
	int end = n - 1;//从数组的最后一个数开始,n-1就是
	while (end > 0)
	{
		Swap(&a[0], &a[end]);//交换函数
		AdjustDown(a, end, 0);
		--end;//每次调整完后,end--
	}
}

2.TOP-K问题

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

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

原理:以找最大的前k个元素为例,先将前k个元素建堆,如果最大值还没有进堆,那么当他进堆时,它一定大于堆里的所有值,与栈顶元素交换后,进行调整成为新的堆,最大值就会沉到堆底下

最后。堆里只剩下要求求出的最大值了。

代码实现
void CreateNDate()
{
	// 造数据
	int n = 10000;
	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) % 100000;//求随机数函数
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(const char* file, int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 建一个k个数小堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc error");
		return;
	}

	// 读取前k个,建小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		Adjustup(minheap, i);
	}

	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);
}

3.建堆算法讨论

前面一直说向上调整算法用来建堆,向下调整算法用来删除,其实有点过于局限,Topk问题和堆排序我也采用向下调整算法来进行建堆是有原因的。

首先先基于一个给定的数组来讨论如何采用两种算法来进行建堆

向上调整

之前在堆排序使用过,以数组的第一个数看成一个堆,将数组剩下的数一次插入并进行调整

void HeapSort(int* a, int n)
{
	for (int i=1;i<n;i++)
	{
		//建堆
		Adjustup(a, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

向下调整

需要注意的是,堆的最后一层是不需要调整的,所以为了方便控制循环,我们从数组尾到头开始,所以最开始的结点就是最后一个结点的父亲结点开始,怎么求,我们知道最后一个结点的下标是n-1,而前面提到过根据孩子结点求父亲结点的公式。

void HeapSort(int* a, int 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;
	}
}

 时间复杂度分析

可以看到,通过时间复杂度,向下调整的效率会优于向上调整。

总结

堆是一种数据结构,这里提到的堆和内存里提到的堆不一样。堆的核心算法是向上调整算法和向下调整算法,通过这两种算法来解决堆排序问题和TopK问题,由于堆总是一棵完全二叉树,用数组来进行存储会非常方便,也有有益于接下来对于普通二叉树的理解。

 


 

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

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

相关文章

Jmeter学习系列之一:Jmeter的详细介绍

目录 一、Jmeter的介绍 二、Jemeter的特点 三、Jemter相关概念 3.1采样器&#xff08;Samplers&#xff09; 3.2逻辑控制器&#xff08;Logic Controllers&#xff09; 3.3监听器&#xff08;Listeners&#xff09; 3.4配置元件&#xff08;Configuration Elements&#…

算法沉淀——滑动窗口(leetcode真题剖析)

算法沉淀——滑动窗口 01.长度最小的子数组02.无重复字符的最长子串03.最大连续1的个数 III04.将 x 减到 0 的最小操作数05.水果成篮06.找到字符串中所有字母异位词07.串联所有单词的子串08.最小覆盖子串 滑动窗口算法是一种用于解决数组或列表中子数组或子序列问题的有效技巧。…

不确定优化入门:用简单实例讲明白随机规划、鲁棒优化和分布鲁棒优化

文章目录 1 引言2 学习动机3 经典问题4 解决方案4.1 忽略不确定性4.2 随机规划4.3 鲁棒优化4.4 分布鲁棒优化 5 总结相关阅读 1 引言 按2024的原定计划&#xff0c;今年开始要学习不确定优化了。 粗略翻阅了一些相关的书籍和教程&#xff0c;大都包含许多数学公式&#xff0c…

xxl-job相关面试题整理

什么是xxl-job&#xff1f; ​ xxl-job是一个分布式的任务调度平台&#xff0c;其核心设计目标是&#xff1a;学习简单、开发迅速、轻量级、易扩展&#xff0c;现在已经开放源代码并接入多家公司的线上产品线&#xff0c;开箱即用。xxl是xxl-job的开发者大众点评的许雪里名称的…

腾讯云幻兽帕鲁4核16G14M服务器性能测评和价格

腾讯云幻兽帕鲁服务器4核16G14M配置&#xff0c;14M公网带宽&#xff0c;限制2500GB月流量&#xff0c;系统盘为220GB SSD盘&#xff0c;优惠价格66元1个月&#xff0c;277元3个月&#xff0c;支持4到8个玩家畅玩&#xff0c;地域可选择上海/北京/成都/南京/广州&#xff0c;腾…

在windows环境下安装hadoop

Hadoop是一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。但这个架构是基于java语言开发的&#xff0c;所以要先进行jdk的安装&#xff0c;如果电脑已经配置过jdk或者是曾经运行成功过java文件&#xff0c;那就可以跳过第一步。 …

单入双出高电压信号隔离变送器

定义&#xff1a;一路高电压信号输入&#xff0c;双路国际标准模拟量信号输出的小型仪器设备。 单入双出高电压模拟量信号隔离变送器 型号&#xff1a;JSD TAH-1002 特征&#xff1a; ◆薄体积&#xff0c;低成本&#xff0c;国际标准DIN35mm导轨安装方式 ◆五端隔离(输入、…

实验2:DEBUG基本命令使用

目录 1、实验目的&#xff1a; 2、实验内容&#xff1a; 3、实验要求&#xff1a; 4、源代码&#xff1a; 5、实验结果 1、实验目的&#xff1a; 熟悉汇编语言程序设计的上机过程&#xff0c;掌握DEBUG的基本命令和功能。 2、实验内容&#xff1a; 从键盘键入一个大写英…

解锁多模态独特魅力-“机器人+Agent+多传感器融合+3DLLM”诠释终极组合大招!

01-Multiply算法背景 01.01-触觉传感器 触觉传感器是一种用于感知和测量物体接触力、形状、纹理和其他相关参数的传感器。它们模拟人类触觉系统&#xff0c;通过收集和解释物体与传感器之间的相互作用来获取信息。工作原理&#xff1a;触觉传感器使用不同的原理来感知接触力和…

Spring 事务原理二

该说些什么呢&#xff1f;一连几天&#xff0c;我都沉溺在孤芳自赏的思维中无法自拔。不知道自己为什么会有这种令人不齿的表现&#xff0c;更不知道这颗定时炸弹何时会将人炸的粉身碎骨。好在儒派宗师曾老夫子“吾日三省吾身”的名言警醒了我。遂潜心自省&#xff0c;溯源头以…

springIoc以及注解的使用

注解 注解的定义 注解&#xff08;Annotation&#xff09;是一种在 Java 程序中以元数据的形式对代码进行标记和说明的机制。它可以被添加到类、方法、字段、参数等程序元素上&#xff0c;用于提供额外的信息和指示。 也就是说注解是一种标记 注解怎么生效呢&#xff1f; 通…

Element-Plus如何实现表单校验和表单重置

一&#xff1a;页面布局介绍&#xff1a; 这是我刚刚用基于vue3element-plus写好的一个部门管理的页面 基本的增删改查已经写好&#xff0c;下面我只提供页面的template和style的代码&#xff1a; template <template><el-card class"box-card"><…

静态代理IP该如何助力Facebook多账号注册运营?

在Facebook运营中&#xff0c;充分利用静态代理IP是多账号运营的关键一环。通过合理运用静态代理IP&#xff0c;不仅可以提高账号安全性&#xff0c;还能有效应对Facebook的算法和限制。以下是这些关键点&#xff0c;可以帮助你了解如何运用静态代理IP进行Facebook多账号运营&a…

BGP:04 fake-as

使用 fake-as 可以将本地真实的 AS 编号隐藏&#xff0c;其他 AS 内的对等体在指定本端对等体所在的AS 编号时&#xff0c;应该设置成这个伪AS 编号。 这是实验拓扑&#xff0c;IBGP EBGP 邻居都使用物理接口来建立 基本配置&#xff1a; R1: sys sysname R1 int loo0 ip add…

网络原理,网络通信以及网络协议

​​​​&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录专栏&#xff1a;网络原理,网络通信以及网络协议 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 网络原理概念网络通信局域网LAN广域网WAN 网络通信IP地址端口号…

第17节-高质量简历写作求职通关-投递反馈

&#xff08;点击即可收听&#xff09; 投递跟进和感谢信 如果对一家公司特别心仪&#xff0c;但是投递简历后一直得不到回复怎么办&#xff1f; 面试之后觉得自己没有表现好怎么办&#xff1f; 面试完几天了&#xff0c;依然没有得到回应怎么办&#xff1f; 这个时候你需要写一…

OkHttp完全解读

一&#xff0c;概述 OkHttp作为android非常流行的网络框架&#xff0c;笔者认为有必要剖析此框架实现原理&#xff0c;抽取并理解此框架优秀的设计模式。OkHttp有几个重要的作用&#xff0c;如桥接、缓存、连接复用等&#xff0c;本文笔者将从使用出发&#xff0c;解读源码&am…

sqli-labs靶场第七关

7、第七关 id1 --单引号报错,id1" --双引号不报错,可以判断是单引号闭合 id1) --也报错&#xff0c;尝试两个括号闭合&#xff0c;id1)) --不报错 接下来用脚本爆库 import stringimport requestsnumbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] letters2 list(string.ascii_…

二、Gradle 与 Idea 整合

这里写自定义目录标题 1、Groovy简介2、Groovy 安装3、创建 Groovy 项目4、Groovy 基本语法 1、Groovy简介 详细了解请参考&#xff1a;http://www.groovy-lang.org/documentation.html 2、Groovy 安装 下载后解压到本地 验证&#xff1a; groovy的安装情况 3、创建 Groo…

231. Power of Two(2 的幂)

题目描述 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2 x n 2^x n2x&#xff0c;则认为 n 是 2 的幂次方。 问题分析 题目要求的是给定一个数判断…