堆(建堆算法,堆排序)

目录

一.什么是堆?

1.堆 

2.堆的储存 

二.堆结构的创建

1.头文件的声明:

2.向上调整

3.向下调整 

4.源码: 

三.建堆算法

1.向上建堆法

2.向下建堆法

四.堆排序

五.在文件中Top出最小的K个数


一.什么是堆?

1.堆 

        堆就是完全二叉树,而且是一种特殊的完全二叉树它需要满足每一个父节点都大于子节点,称为大堆或每一个父节点都小于子节点,称为小堆。而对兄弟节点之间的大小关系并没有要求(为此它并不是有序的)。如下:

2.堆的储存 

         对于完全二叉树有一个更好的储存方法,就是用顺序表来储存,相比链式储存使用顺序表储存的一个很大的好处在于知道一个结点可以很容易的算出它父结点和子结点的下标,还有可以随机访问。

父子结点下标计算公式 :

        左子结点下标 = 父结点下标*2+1

        右子结点下标 = 父结点下标*2+2

        父结点下标 = (子结点下标-1) / 2 

二.堆结构的创建

1.头文件的声明:

Heap.h

#pragma
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define HpDataType int
typedef int HPDataType;
typedef struct Heap
{HPDataType* arr;int size;int cap;
}Heap;
void HeapInit(Heap* php);//堆的初始化
void HeapDestory(Heap* hp);//堆的销毁
void HeapPush(Heap* hp, HPDataType x);//堆的插入
void HeapPop(Heap* hp);//堆的删除
HPDataType HeapTop(Heap* hp);//取堆顶的数据
int HeapSize(Heap* hp);//堆的数据个数
int HeapEmpty(Heap* hp);//堆的判空void AdjustUP(HpDataType* arr, int child);//向上调整
void AdjustDOWN(HpDataType* arr, int size, int parent);//向下调整
void Swap(HpDataType* a, HpDataType* b);//元素的交换

        其中堆的初始化,堆的销毁,堆的数据个数,堆的判空,和取堆顶数据和顺序表的操作是一样的这里重点来学一下堆的插入,堆的删除。

2.向上调整

        插入元素呢直接往数组最后插入就可以,但是插入后就不一定是堆结构的,所以需要调整。例如一个大堆:

向大堆中插入53

调整后:

代码示例:

void AdjustUP(HpDataType* arr,int child)
{int parent = (child - 1) / 2;//计算父节点下标while (child>0)//注意这里不能是parent>0{if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);//封装一个函数进行交换child = parent;//更新子节点parent = (child - 1) / 2;//更新父节点}elsebreak;}
}

★如果是小堆只需要把if条件的大于号改为小于号

3.向下调整 

        要注意删除元素我们删除的不是尾元素,这样毫无意义,我们删除的是下标为0位置的元素它是整个堆中最小或最大的元素。怎么删除呢?直接将它删除然后后面的元素在覆盖上吗?这样做的话,它就不是堆了,而且元素之间关系将会全部混乱,就需要从0开始创建堆,效率非常低,我们可以把首元素与尾元素互换然后删除尾元素,虽然这个操作过后它也可能就不是堆了,不过我们可以将首元素向下调整,让它成为堆。比刚才的方案效率要高得多。

比如我们删除大堆中的一个元素

调整过程:

调整后的结果:

代码示例:

void AdjustDOWN(HpDataType* arr, int size, int parent)
{int child = parent * 2 + 1;while (child < size){if ((child+1)<size&&arr[child] < arr[child + 1])child++;if (arr[child] > arr[parent]){Swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}

★如果是小堆只需要把if条件里兄弟节点的大小关系和父子节点的大小关系改变一下就行

4.源码: 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HeapInit(Heap* ps)//初始化
{assert(ps);ps->arr = NULL;ps->cap = ps->size = 0;
}
void HeapDestory(Heap* hp)//销毁堆
{assert(hp);free(hp->arr);hp->cap = hp->size = 0;
}
void Swap(HpDataType* a, HpDataType* b)//交换元素
{HpDataType c = *a;*a = *b;*b = c;
}
void AdjustUP(HpDataType* arr,int child)//向下调整
{int parent = (child - 1) / 2;while (child>0){if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}
}
void AdjustDOWN(HpDataType* arr, int size, int parent)//向上调整
{int child = parent * 2 + 1;while (child < size){if (arr[child] > arr[child + 1])child++;if ((child+1)<size&&arr[child] < arr[parent]){Swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}
void HeapPush(Heap* ps, HPDataType x)//插入元素
{assert(ps);if (ps->size == ps->cap){int pnc = ps->cap == 0 ? 4 : 2 * ps->cap;HpDataType* pnew = realloc(ps->arr, sizeof(HPDataType)*pnc);assert(pnew);ps->arr = pnew;ps->cap = pnc;}ps->arr[ps->size] = x;ps->size++;AdjustUP(ps->arr, ps->size - 1);
}
void HeapPop(Heap* hp)//删除元素
{assert(hp);assert(hp->size);if (hp->size == 1){hp->size--;return;}Swap(&(hp->arr[0]), &(hp->arr[hp->size - 1]));hp->size--;AdjustDOWN(hp->arr, hp->size, 0);
}
HPDataType HeapTop(Heap* hp)//取堆顶元素
{assert(hp);assert(hp->size);return hp->arr[0];
}
int HeapSize(Heap* hp)//计算堆元素个数
{assert(hp);return hp->size;
}
int HeapEmpty(Heap* hp)//判断堆是否为空
{assert(hp);return hp->size == 0;
}

三.建堆算法

        在学习建堆算法的时候我们以对数组建堆为例,就是把数组的数据之间的关系做成一个堆结构,一般有两种方法,向上调整建堆和向下调整建堆,具体怎么做我们来看下面。

1.向上建堆法

        向上建堆法也就是通过向上调整建堆,我们拿到一个数组后可以把数组的首元素当做堆,第二个元素当做把新的元素插入堆,然后通过向上调整构成新的堆,以此类推下去把数组遍历完后一个堆就建成了。时间复杂度为O(N*logN)

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{int arr[] = { 1,9,3,7,6,4,2,10,8,5 };int size = sizeof(arr) / sizeof(int);for (int i = 0; i < size; i++)AdjustUP(arr, i);//该函数在上文已给出,这里不再展示printf("建大堆后:\n");for (int i = 0; i < size; i++)printf("%d ", arr[i]);return 0;
}

 不过该方法相比向下调整建堆效率比较低,我们来看向下调整建堆法。

2.向下建堆法

        向下建堆法也就是通过向下调整建堆,要注意并不是从首元素开始调整,因为刚开始它并不满足左右子树都是堆结构,所以不能直接从第一个元素开始向下调整。既然要满足左右子树都是堆那么我们可以考虑从最后一个元素开始调整,不过最后一层下面已经没有元素了,它已经是堆,并不用调整,那么我们从倒数第二层开始调整,所以我们先来计算一下倒数第二层最后一个父节点的下标:

                (size-1-1)/2

        第一个size-1得到二叉树的最后一个元素的下标,再减一除以二得到它的父节点的下标。

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{int arr[] = { 1,9,3,7,6,4,2,10,8,5 };int size = sizeof(arr) / sizeof(int);for (int i = (size - 1 - 1) / 2; i >= 0; i--)AdjustDOWN(arr, size,i);//该函数在上文已给出,这里不再展示printf("建大堆后:\n");for (int i = 0; i < size; i++)printf("%d ", arr[i]);return 0;
}

它的时间复杂度为O(N)证明如下: 

其中Sn为总的调整次数. 

四.堆排序

        给一个数组建堆后利用堆的性质给数组排序,使其效率更高,这就是一个堆排序。比如现在要对一个数组进行堆排序,第一个问题就是建大堆还是小堆,怎么利用堆来给数组排序。

        要进行升序就需要建大堆,如果建的是小堆,那么堆顶也就是首元素就是最小的元素,并不需要动,那么来处理第二个元素就注意到它并不一定是第二小的元素,只能从第二个元素开始重新建一个小堆,那么每排一个元素都需要重新建一个小堆效率就会变得很低。

        升序建大堆的话,第一个元素就是最大的元素,我们可以让它与最后一个元素互换,然后把堆的元素个数减一(就是把最后一个元素当做是堆外),最后把堆顶元素向下调整,反复操作直到堆的元素个数变为了零。这样一个数组就按升序排好了。

        降序需要建小堆,原理和排升序相同这里就不在赘述。

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{int arr[] = { 1,9,3,7,6,4,2,10,8,5 };int size = sizeof(arr) / sizeof(int);for (int i = (size - 1 - 1) / 2; i >= 0; i--)AdjustDOWN(arr, size,i);printf("建大堆后:\n");for (int i = 0; i < size; i++)printf("%d ", arr[i]);while (size){Swap(&arr[0], &arr[size - 1]);//交换元素size--;AdjustDOWN(arr, size, 0);}printf("\n排序后;\n");for (int i = 0; i < sizeof(arr) / sizeof(int); i++)printf("%d ", arr[i]);return 0;
}

五.在文件中Top出最小的K个数

        用堆结构的一个好处就在于,不需要排序就能高效的找出最小的前n个元素或最大的前n个元素,现在我们来利用堆来尝试找出文件中最小的K个数,一个比较低效的一个方法就是把文件中涉及到的所以数据都取出来然后把它建成一个小堆,然后Pop出前k次,得到最小的k个数。但是如果这个数据非常的大呢,比如有上亿个数据,那么就会消耗很大的内存空间。

        有一个很优的方法就是只取出文件的前K个数建成一个大堆,也就是说这个堆只用储K个元素,那么堆顶就是这个堆的最大元素,然后继续遍历文件每遍历一个元素都与堆顶元素作比较,如果比堆顶元素小就更新一下堆顶元素(把小的那个变成堆顶元素),然后进行向下调整,直到遍历完整个文件,那么此时堆中的元素就是文件中最小的K个元素。此方法在时间复杂度上与上一方法差不多,但它大大的节省了空间。

代码示例:

#include<stdio.h>
#include"Heap.h"
void CreateNDate()
{//造数据,写入文件中int n = 10000;srand((unsigned int)time(NULL));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() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}
void PrintTopK(int k)
{int* arr = (int*)malloc(sizeof(int) * k);assert(arr);FILE* fop = fopen("data.txt", "r");if (!fop){perror("fopen error");return;}for (int i = 0; i < k; i++)//先取出k个建大堆fscanf(fop, "%d", &arr[i]);for (int i = (k - 1 - 1) / 2; i >= 0; i--)AdjustDOWN(arr, k, i);int x = 0;while (fscanf(fop, "%d", &x) != EOF){if (arr[0] > x){arr[0] = x;AdjustDOWN(arr, k, 0);}}for (int i = 0; i < k; i++)//输出堆中元素printf("%d ", arr[i]);
}
int main()
{CreateNDate();int k = 0;scanf("%d", &k);PrintTopK(k);return 0;
}

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

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

相关文章

Docker之xfs文件系统下安装报错解决方案

一、需求说明 centos系统下安装docker最新版的时候&#xff0c;安装成功&#xff0c;启动的时候报错。报错信息“failed to start daemon: error initializing graphdriver: overlay2: the backing xfs filesystem is formatted without d_type support, which leads to incorr…

ROS添加GDB调试

文章目录 一、问题描述二、配置步骤1. debug 模式编译2. rosrun 添加GDB指令3. launch 添加GDB指令 三、GDB基本命令1. 基本2. 显示被调试文件信息3. 查看/修改内存4. 断点5. 调试运行 一、问题描述 在享受ROS带来便利的同时&#xff0c;但因每运行出现错误&#xff0c;ROS不会…

风电机组的振动控制

文章目录 0. 背景1. 原文记录 0. 背景 混塔机组的频率大概是目前业内遇见的比较普遍的通病。最近在了解风电机组振动控制的知识&#xff0c;看到一篇科普性质的文章&#xff0c;感觉不错&#xff0c;所以记录下来。想要看原文的点击这里。感谢原作者。 1. 原文记录

一文带你了解.NET能做什么?

前言 在DotNetGuide技术社区微信交流群经常看到有小伙伴问&#xff1a;.NET除了能写桌面应用和Web应用还能做什么&#xff1f;今天大姚将通过本篇文章来简单讲讲.NET能做哪些开发&#xff0c;对.NET感兴趣的小伙伴也可以自行领取文末附带的.NET相关学习资料。 .NET简单介绍 .…

多层派生时的构造函数和派生类的析构函数

一、多层派生时的构造函数 目录 一、多层派生时的构造函数 二、派生类的析构函数 析构函数的作用&#xff1a; 例&#xff1a;多级派生情况下派生类的构造函数 #include <iostream> #include<string> using namespace std; class Student { public:Student(int…

【SpeedAI科研小助手】2分钟解决知网维普AIGC检测

2分钟搞定AIGC率&#xff1f;还能降到0%&#xff1f; 使用方法&#xff1a; 打开SpeedAI科研小助手&#xff0c;将功能模式换成降AIGC率&#xff0c;后面可以一段一段自己改&#xff0c;也可以直接上传论文文件&#xff0c;SpeedAI直接帮你全文修改&#xff08;主打一个用户友…

HTML5 基本框架

HTML5基本的内容 文章目录 系列文章目录前言一、HTML5 基本框架二、具体框架结构三、知识补充总结 前言 HTML5的介绍&#xff1a; HTML5 是一种用于构建网页内容的标准化语言。它是 HTML&#xff08;超文本标记语言&#xff09;的第五个版本&#xff0c;引入了许多新的功能和特…

Jmeter-使用手册(_5.5版本)

JMeter是一个Java桌面应用程序&#xff0c;具有使用Swing图形API的图形界面。可以进行接口、性能等测试&#xff0c;也可以对任何数据库进行同样的测试&#xff0c;具有可移植性&#xff0c;可跨平台支持Windows&#xff0c;Linux&#xff0c;Mac上使用。 JMeter运行场景不仅可…

模仿高效网络进行目标检测——知识蒸馏

摘要 链接&#xff1a;https://openaccess.thecvf.com/content_cvpr_2017/papers/Li_Mimicking_Very_Efficient_CVPR_2017_paper.pdf 当前的基于卷积神经网络&#xff08;CNN&#xff09;的目标检测器需要从预训练的ImageNet分类模型中初始化&#xff0c;这通常非常耗时。在本…

Java整合ELK实现日志收集 之 Elasticsearch、Logstash、Kibana

简介 Logstash&#xff1a;用于收集并处理日志&#xff0c;将日志信息存储到Elasticsearch里面 Elasticsearch&#xff1a;用于存储收集到的日志信息 Kibana&#xff1a;通过Web端的可视化界面来查看日志&#xff08;数据可视化&#xff09; Logstash 是免费且开放的服务器端数…

AI办公自动化:用kimi批量将word文档部分文件名保存到Excel中

文件夹中有很多个word文档&#xff0c;现在只要英文部分的文件名&#xff0c;保存到一个Excel文件中。 可以在kimi中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个编写Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹&#xff1a;…

数据集002:眼疾识别数据集 (含数据集下载链接)

说明 病理性近视&#xff08;Pathologic Myopia&#xff0c;PM&#xff09;的医疗类数据集&#xff0c;包含1200个受试者的眼底视网膜图片&#xff0c;训练、验证和测试数据集各400张。 说明&#xff1a; 如今近视已经成为困扰人们健康的一项全球性负担&#xff0c;在近视人…

斯坦福2024人工智能指数报告 1

《人工智能指数报告》由斯坦福大学、AI指数指导委员会及业内众多大佬Raymond Perrault、Erik Brynjolfsson 、James Manyika、Jack Clark等人员和组织合著&#xff0c;旨在追踪、整理、提炼并可视化与人工智能&#xff08;AI&#xff09;相关各类数据&#xff0c;该报告已被大多…

RedHat9 | DNS剖析-建立子域并进行区域委派

一、实验环境 1、委派DNS服务器 域名空间由多个域构成&#xff0c;DNS提供了将域名空间划分为1个或多个区域的方法&#xff0c;这样使得管理更加方便。在域的规模增大后&#xff0c;可以为域添加附加域&#xff0c;上级域为父域&#xff0c;下级域为子域&#xff0c;下列案例…

韩顺平0基础学Java——第11天

p234-249 又一个月了&#xff0c;时间过得好快啊&#xff0c;希望支棱起来 可变参数 public int sum(int ... nums){ } 这个nums是数组 细节&#xff1a; 1可变参数可以为0个&#xff0c;或任意个 2可变参数的实参可以为数组 3可变参数的本质就是数组 4可变参数可以和普通…

Golang | Leetcode Golang题解之第113题路径总和II

题目&#xff1a; 题解&#xff1a; type pair struct {node *TreeNodeleft int }func pathSum(root *TreeNode, targetSum int) (ans [][]int) {if root nil {return}parent : map[*TreeNode]*TreeNode{}getPath : func(node *TreeNode) (path []int) {for ; node ! nil; no…

冯喜运:5.27黄金暴跌大阴后出现“暂定符”今日黄金原油操作策略

【黄金消息面分析】&#xff1a;金价虽然有大阴线暴跌&#xff0c;但依然属于超买后的调整而非熊市&#xff0c;对中长线投资者来说只是市场洗牌。因此&#xff0c;在出现企稳迹象之后&#xff0c;随时关注反弹时机的启动。未来几日&#xff0c;黄金空头可能在进一步发力之前需…

【数据结构与算法 经典例题】相交链表

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录 一、问题描述 二、解题思路 方法一:双循环对比法 方…

LangChain llamaindex

LangChain 参考&#xff1a; 全流程 | Windows 系统本地部署开源模型阿里通义千问 QWEN 1.5&#xff0c;结合 LangChain-Chatchat 框架和向量数据库 FAISS、Milvus - 知乎

redis数据操作相关命令

1.list操作 1.1 rpush rpush&#xff1a;新的元素添加到list最右边 #从右边依次往List添加1,2,3 RPUSH name 1 RPUSH name 2 RPUSH name 3#查看列表&#xff1a;返回 1,2,3 LRANGE name 0 -1结果如下&#xff1a; 1.2 lpush lpush&#xff1a;新加的元素在list最左边 #从…