堆,堆排序和TOP—K问题(C语言版)

news2024/11/28 0:54:14

前言

        堆是一种重要的数据结构,堆分为大根堆和小根堆,大根堆堆顶的数据是最大的,小根堆堆顶的数据是最小的,堆在逻辑结构上是一颗完全二叉树,这棵树中如果满足根节点大于左右子树,每个节点都满足这个条件就是大根堆,反之就是小根堆。

1.堆的概念和性质

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

         堆的性质:

        1.堆中某个节点的值总是不大于或者不小于其父节点的值

        2.堆是一颗完全二叉树

        从堆是一颗完全二叉树来理解堆是更容易理解的。

  

2.堆的实现

        2.1向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过根节点开始的向下调整算法可以把它调整为一个小堆。向下调整算法的前提是左右子树都必须是小堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

         

 

        2.2堆的创建

        下面我们给出一个数组,这个数组在逻辑上可以看出一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点的左右子树不是堆,我们怎么调整呢?我们可以从叶子节点开始调整,但是没有必要,因为每个叶子结点都可以看成一个堆。我们可以从倒数第一个非叶子节点开始调整,一直调整到根节点的树,就可以调成一个堆。 

        int a[] = {1,5,3,8,7,6};

          

        2.3建堆的时间复杂度

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

        假设树的高度为h

        第一层有2的零次方个节点,需要向下移动h - 1层

        第二层有2的一次方个节点,需要向下移动h - 2层

        第三层有2的二次方个节点,需要向下移动h - 3层

        第四层有2的三次方个节点,需要向下移动h - 4层

        第h - 1层有2的h - 2次方个节点,需要向下移动1层

        则需要移动节点总的移动步数为:

 

        因此:建堆的时间复杂度为O(N)

        2.4堆的插入

        堆的插入要插入到数组的末尾,在进行向上调整算法,直到满足堆的特性。

  

        2.5堆的删除

        删除堆,删除的是堆顶的元素,如果直接删除好吗?

        答案是否定的,直接删除堆顶的数据,这个堆就废了,需要重新建堆,所以正确的操作是运用先交换堆顶和堆最后一个元素,进行一次向下调整即可解决问题。 

 

        2.6堆的代码实现

        //Heap.h

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>
#include<string.h>
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;//存储数据
	int _size;
	int _capacity;
}Heap;
void HeapSort(int* a, int size);//堆排序
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法

void HeapInit(Heap* php, HPDataType* a, int n);//初始化堆

void HeapDestory(Heap* php);//销毁队

void HeapPush(Heap* php, HPDataType x);//在堆里面入数据

void HeapPop(Heap* php);//出堆顶的数据

HPDataType HeapTop(Heap* php);//获取堆顶的数据

        //Heap.c 

#include"Heap.h"
void ADJustDown(HPDataType* a, int root, int size);//向下调整算法

void Swap(HPDataType* left, HPDataType* right)
{
	HPDataType tmp = *left;
	*left = *right;
	*right = tmp;
}
void HeapSort(int* a, int size)//堆排序
{
	//建堆
	int root = (size - 1 - 1) / 2;//找到非叶子结点
	while (root >= 0)
	{
		ADJustDown(a, root, size);
		--root;
	}
	//将堆顶的数据与堆底的数据交换
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//向下调整
		ADJustDown(a, 0, end);
		end--;
	}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{
	assert(a);//指针存在
	int parent = root;
	int child = parent * 2 + 1;
	
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子
		{
			++child;
		}
		if (a[child] < a[parent])//交换孩子和父亲
		{
			Swap(&a[child], &a[parent]);
			//迭代继续
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//不需要调整
		}
	}
}
void ADJustUp(HPDataType* a, int child)//向下调整算法
{
	assert(a);//确保指针有效
	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 HeapInit(Heap* php, HPDataType* a, int n)//初始化堆
{
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	php->_capacity = php->_size = n;
	memcpy(php->_a, a, sizeof(HPDataType)*n);
	//建队=堆
	int root = (n - 1 - 1) / 2;//找到非叶子结点
	while(root >= 0)
	{
		ADJustDown(php->_a, root, n);
		--root;
	}
}
void HeapDestory(Heap* php)//销毁队
{
	assert(php);//堆存在
	free(php->_a);
	php->_size = php->_capacity = 0;
}
void HeapPush(Heap* php, HPDataType x)//在堆里面入数据
{
	//判断是不是需不需要增容
	if (php->_capacity == php->_size)
	{
		php->_capacity *= 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);
		if (tmp == NULL)
		{
			printf("申请内存失败\n");
			exit(-1);
		}
		php->_a = tmp;
	}
	//插入数据
	php->_a[php->_size] = x;
	php->_size++;
	//向上调整
	ADJustUp(php->_a, php->_size - 1);
}
void HeapPop(Heap* php)//出堆顶的数据
{
	assert(php);//确保堆不为空
	assert(php->_size > 0);//确保堆里面存在数据
	//为了保持堆的特性,需要先将堆顶的数据与堆底的数据交换,然后pop调堆底的数据
	//在对堆顶开始进行一次向下调整
	if (php->_size > 1)
	{
		Swap(&php->_a[0], &php->_a[php->_size - 1]);
		php->_size--;
		ADJustDown(php->_a, 0, php->_size);
	}
	else if (php->_size == 1)
	{
		php->_size--;
	}
}
HPDataType HeapTop(Heap* php)//获取堆顶的数据
{
	assert(php);//指针存在
	assert(php->_size > 0);//堆里面有数据
	return php->_a[0];
}

3.堆的应用

        3.1堆排序

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

        1.建堆

        如果是排升序,是建大堆还是小堆呢? 如果是排降序呢?

        如果排升序,建小堆的话,每次选出最小的数以后,整个堆就不能用来,就要重新建堆,所以,排升序要建大堆,每次选出最大的数放在数组的最后,堆的大小减一,调用一次向下调整就可以再选出堆里面最大的数了。利用这样的方法就可以实现堆排序了。 

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

        建堆和堆删除中都用到了向下调整算法,因此掌握了向下调整算法就掌握了和堆相关的大部分内容了 ,堆就是这么简单又朴实无华,哈哈哈哈。

 

        

void HeapSort(int* a, int size)//堆排序
{
	//建堆
	int root = (size - 1 - 1) / 2;//找到非叶子结点
	while (root >= 0)
	{
		ADJustDown(a, root, size);//调用向下调整算法
		--root;
	}
	//将堆顶的数据与堆底的数据交换
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//向下调整
		ADJustDown(a, 0, end);
		end--;
	}
}
void ADJustDown(HPDataType * a,int root, int size)//向下调整算法
{
	assert(a);//指针存在
	int parent = root;
	int child = parent * 2 + 1;
	
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子
		{
			++child;
		}
		if (a[child] < a[parent])//交换孩子和父亲
		{
			Swap(&a[child], &a[parent]);
			//迭代继续
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//不需要调整
		}
	}
}

 

        3.2TOP-K问题

         TOP-K问题:即求数据集合中前k个最大或者最小的元素,一般情况下数据量会很大。

        比如:专业前10名,世界500强,富豪榜,游戏中前100的活跃玩家等等。

        对于TOP-K问题,能想到的最简单的方式就是排序,但是如果数据量很大,排序就不太可取了(数据可能无法加载到内存中,数据太多了)。最佳的解决方式就是用堆来解决,基本思路如下:

        1.用数据前K个元素来建堆

        如果是求前K大的数,就建一个小堆,如果堆顶的数比剩下的数小就替换堆顶的数据。直到比较完所有的数据。

        如果是求前K小的数据,就建一个大堆,如果堆顶的数比剩下的数大就将堆顶的数替换为正在比较的数,直到比较完所有的数据。

        形象一点来说堆顶数就像是守门员一样,到最后堆顶的数肯定是前K小的数或者前K大数。

        2.用剩余的K-N个元素来和堆顶的数据进行比较,不满足则替换堆顶的元素

将剩余N-K个元素依次与堆顶的元素比较完后,堆里面剩余的K个元素就是所求的前K个最小或者最大的元素。 

        它的时间复杂度是N*log(K)。 

        TOP-K问题

        代码:

void Swap(int* left, int* right)
{
	int tmp = *left;
	*left = *right;
	*right = tmp;
}
void AdJustDown(int* a,int root, int size)//向下调整算法
{
	assert(a);//指针存在
	int parent = root;
	int child = parent * 2 + 1;
	
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])//找出左右孩子中小的那个孩子
		{
			++child;
		}
		if (a[child] < a[parent])//交换孩子和父亲
		{
			Swap(&a[child], &a[parent]);
			//迭代继续
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//不需要调整
		}
	}
}
int findKthLargest(int* nums, int numsSize, int k)
{
    //使用向下调整算法进行建堆
    int root = (k - 1) / 2;//找到倒数第一个非叶子节点
    while(root >= 0)//对前k个数组元素建小堆
    {
        AdJustDown(nums, root, k);
        --root;
    }
    for(int i = k; i < numsSize; ++i)
    {
        if(nums[0] < nums[i])
        {
            //取代堆顶的数据,进行向下调整
            int tmp = nums[0];
            nums[0] = nums[i];
            nums[i] = tmp;
            AdJustDown(nums, 0, k);
        }
    }
    for(int i = 0; i < numsSize; ++i)
    {
        printf("%d ",nums[i]);
    }
    return nums[0];//此时堆顶的元素就是第K大的元素
}

 

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

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

相关文章

capl使用汇总

数组类 2维数组定义 dword data[DIDN][100];其中数组的类型是dword&#xff0c;二维数组的元素个数是DIDN值&#xff0c;第二维100表示每个数组data[i]的都是一个一维数组并且这个一维数组是100个dword数组成的 结构体 结构体定义 以下的结构体类型supDTC&#xff08;支持…

Skip Connection——提高深度神经网络性能的利器

可以参考一下这篇知乎所讲 https://zhuanlan.zhihu.com/p/457590578

禅道后台命令执行漏洞二

漏洞简介 禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体&#xff0c;是一款专业的研发项目管理软件&#xff0c;完整地覆盖了项目管理的核心流程。 禅道管理思想注重实效&#xff0c;功能完备丰富&#xff0c;…

LeetCode--HOT100题(44)

目录 题目描述&#xff1a;230. 二叉搜索树中第K小的元素&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;230. 二叉搜索树中第K小的元素&#xff08;中等&#xff09; 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你…

网络安全(黑客技术)0基础学习手册

目录梗概 一、自学网络安全学习的误区和陷阱 二、学习网络安全的一些前期准备 三、网络安全学习路线 四、学习资料的推荐 想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来…

prometheus+cadvisor监控docker容器

一、安装cadvisor docker pull google/cadvisor:latest二、运行容器 docker run -d \--volume/:/rootfs:ro \--volume/var/run:/var/run:rw \--volume/sys:/sys:ro \--volume/var/lib/docker/:/var/lib/docker:ro \--publish8088:8080 \--detachtrue \--namecadvisor \--priv…

【FAQ】从存储权限看HarmonyOS 3.0中应用适配

问题背景&#xff1a; HarmonyOS 3.0发布了&#xff0c;之前开发的Android的应用&#xff0c;发现系统选项中存储权限部分有变化&#xff0c;如下图&#xff1a; ”存储“权限变为”媒体和文件“&#xff0c;且只能访问”仅媒体“的文件目录。因为项目需要读取本地导入存储文件…

基金市场的冷热传递什么信号?

摘要及声明 1&#xff1a;本文主要利用实际数据进行检验&#xff0c;从定量角度分析基金发行情况与股票市场之间的关系&#xff1b; 2&#xff1a;本文主要为理念的讲解&#xff0c;模型也是笔者自建&#xff0c;文中假设与观点是基于笔者对模型及数据的一孔之见&#xff0c…

【分享】PDF如何拆分成2个或多个文件呢?

当我们需要把一个多页的PDF文件拆分成2个或多个独立的PDF文件&#xff0c;可以怎么操作呢&#xff1f;这种情况需要使用相关工具&#xff0c;下面小编就来分享两个常用的工具。 1. PDF编辑器 PDF编辑器不仅可以用来编辑PDF文件&#xff0c;还具备多种功能&#xff0c;拆分PDF文…

【python知识】用 Tkinter实现“剪刀-石头-布”和“弹球游戏 ”

一、提要 Tkinter是一个Python内置模块&#xff0c;它提供了一个简单易用的界面来创建GUI。 在实现一些动态的画面、如游戏还是需要一些创新性思维的。在本文中&#xff0c;我们将使用 Tkinter 探索 Python GUI 编程。我们将介绍 Tkinter 的基础知识&#xff0c;并演示如何使用…

【Web系列二十四】使用JPA简化持久层接口开发

目录 环境配置 1、引入依赖 配置文件 代码编写 实体类创建 JPA常用注解 Service与ServiceImpl Service ServiceImpl Controller Dao 三种实现Dao功能方式 1.继承接口&#xff0c;使用默认接口实现 2.根据接口命名规则默认生成实现 3.自定义接口实现(类似MyBatis…

Windows-docker集成SRS服务器的部署和使用

Windows-docker集成SRS服务器的部署和使用 一、Windows Docker安装 Docker Desktop 官方下载地址&#xff1a; https://docs.docker.com/desktop/install/windows-install/ 下载windows版本的就可以了。 注意&#xff1a;此方法仅适用于 Windows 10 操作系统专业版、企业版、…

C语言之数组题

目录 1.使用函数实现数组操作 2.冒泡排序 3.三子棋 4.【一维数组】交换数组 5.扫雷 6.概念辨析tips 我又来了&#xff0c;今天是数组题&#xff0c;本人还在补军训真的热&#xff01;&#x1f197; 1.使用函数实现数组操作 2.冒泡排序 3.三子棋 4.【一维数组】交换数组 …

Python自动化测试之线上流量回放:录制、打标、压测与平台选择

在自动化测试中&#xff0c;线上流量回放是一项关键技术&#xff0c;可以模拟真实用户的请求并重现线上场景&#xff0c;验证系统的性能和稳定性。本文将介绍Python自动化测试中的线上流量回放技术&#xff0c;并提供实战代码&#xff0c;帮助你了解流量的录制、打标、压测发起…

Ubuntu安装JDK8(直接下载jdk压缩包方式)

1.官网下载JDK 地址: https://www.oracle.com/java/technologies/downloads/ 选择相应的 .gz包下载 2.解压缩,放到指定目录 创建目录: sudo mkdir /usr/lib/jvm 解压缩到该目录: sudo tar -zxvf jdk-8u381-linux-x64.tar.gz -C /usr/lib/jvm 3.配置环境变量 sudo vim ~/.ba…

跨专业申请成功|金融公司经理赴美国密苏里大学访学交流

J经理所学专业与从事工作不符&#xff0c;尽管如此&#xff0c;我们还是为其成功申请到美国密苏里大学经济学专业的访问学者职位&#xff0c;全家顺利过签出国。 J经理背景&#xff1a; 申请类型&#xff1a; 自费访问学者 工作背景&#xff1a; 某金融公司经理 教育背景&am…

YUV数据图形化理解

以下为音视频基础数据的图像化展示&#xff0c;方便大家理解 RGB24 RGB交替排列&#xff0c;RGBRGBRGB 占用空间Width*Height*3 YUV420P YU12(I420) 每4个Y分量&#xff0c;共一个UV分量 Y是连续的&#xff0c;U也是连续的&#xff0c;V也是连续的 占用空间 Width*Height …

北京筑龙受邀出席中物联“采购供应链中国行—走进雄安”活动

日前&#xff0c;“采购供应链中国行—走进雄安”活动在河北雄安新区成功举办&#xff0c;来自30家相关单位的50余名领导和代表参加了本次活动。活动由中国物流与采购联合会公共采购分会主办&#xff0c;中国物流与采购联合会采购委、中国雄安集团有限公司、河北雄安新区招标投…

【Apollo】阿波罗自动驾驶系统:驶向未来的智能出行(含源码安装)

前言 Apollo (阿波罗)是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 开放能力、共享资源、加速创新、持续共赢是 Apollo 开放平台的口号。百度把自己所拥有的强大、…