【数据结构】模拟实现 堆

news2025/8/1 20:19:44

堆数据结构是一种数组对象,它可以被看作一颗完全二叉树的结构(数组是完全二叉树),堆是一种静态结构。

堆分为最大堆和最小堆。

最大堆:每个父结点都大于孩子结点。

最小堆:每个父结点都小于孩子结点。

堆的优势:效率高,可以找最大数最小数,增删的时间复杂度为lgN

怎么找最后一个叶子结点?

找到最后一个叶子结点(左孩子为空就是叶子结点),由于堆是静态结构,所以我们要通过计算下标的方法找,计算它的孩子是否超出了数组范围。

通过观察可以总结数组下标计算父子关系的公式:

leftchild = parent*2 +1

rightchild = parent*2+ 1

parent =( child -1) /2

1.代码实现:

typedef  int  HPDataType;

typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;

}HP;

Heap结构体中有一个指向动态数组的指针,size表示数组的当前元素个数,capacity表示数组的容量

将结构体的定义和函数的申明都放在Heap.h中:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef  int  HPDataType;

typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;

}HP;

//以数组的形式打印堆的元素
void PrintHeap(HP* php);
//初始化堆
void HeapInit(HP* php);
//销毁
void HeapDestory(HP* php);

//插入数据x 但是要依旧保持堆的形态
void HeapPush(HP* php, HPDataType x);

//删除堆顶的元素
void HeapPop(HP* php);

//返回堆顶的元素
HPDataType HeapTop(HP* php);
//判定堆是否为空
bool HeapEmpty(HP* php);
//返回堆当前元素的个数
int HeapSize(HP* php);

在Heap.c文件中完成接口的实现:

初始化:

void HeapInit(HP* php)
{
    assert(php);
    php->a = NULL;
    php->capacity = php->size = 0;

}

插入元素:

void Swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType tmp = *p1;
    *p1 = *p2;
    *p2 = 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(HP* 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);   
}

HeapPush函数::插入元素将对象的指针传过来并且将要插入的元素传过来,插入元素前检查容量当size ==capacity时就得扩容,我们采用的是将原来的容量扩大两倍,因为两倍比较合适。定义一个新的容量当之前的容量为0时给4个字节的大小,之前有容量时扩大到原来的两倍。这里需要注意的时realloc是在原来的基础上扩大的字节数所以要记得newcapacity*sizeof(HPDataType)!再将a指向扩容后的地址然后更新capacity的大小,在size位置插入元素x,然后size++。在这里模拟实现的是小堆,但是插入数据的位置在逻辑结构的数组的尾部,或者说是在完全二叉树的叶子节点位置。小堆结构的最小的元素都在较上方,所以说要向上调整。将数组的指针和插入元素的数组下标传给向上调整函数。

AdjustUp函数::通过公式计算出插入元素的数组下标的父节点的数组下标。

假设插入的位置在最下方的红色部分,小堆就是父节点要比子节点的值小,如果插入的元素的值很小,需要一直和当前位置的父节点交换位置,那交换最多的情况就是从叶子节点的位置交换到根节点的位置(也就是说child=0时),因为交换的次数不确定所以放到for不好控制,所以将向上调整的动作放到while循环里,当符合条件时跳出循环即可。当child>0时,a[child] < a[parent]所以进行交换,将parent的下标的值给child,然后再次计算出该节点位置的父节点的数组下标,再次比较该节点与其父节点的值的大小,如果符合调整的条件就进行交换,一直循环。当child==0或者a[parent]>=a[parent]的值就不需要就跳出循环,向上调整结束,新插入的元素就到了应该到的位置。

Swap函数::取出要交换的值的地址交给该函数,创建一个同类型的变量tmp存储p1指向的内容,p1再指向p2所指向的内容,最后再将p2指向tmp就完成了值的交换。

通过这三个函数就完成了数据的插入,同时保持了堆的形态。

删除堆顶的元素:

首先明白小堆和大堆的应用时为了找到前n个最小或者最大的元素,删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

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(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));
    Swap(&php->a[0], &php->a[php->size - 1]);  //将根部元素与最后一个元素交换,然后再向下调整
    php->size--;  //相当于删除了最后一个元素
    AdjustDown(php->a, php->size, 0);
}

删除堆顶元素,首先保证堆有元素,再交换堆顶元素和最后一个元素,size--就相当于删除了最后一个元素,再调用向下调整函数。将数组的地址和数组的元素个数还有数组的首元素下标传给向下调整函数,开始调整。模拟实现的是小堆所以较大的元素在堆的下方,小的元素在较上方,每一个父节点都有两个子节点所以该和谁交换使得根节点的数据往下调整呢?? 首先根据父节点的数组下标得出左孩子的数组下标,默认最小孩子minChild为左孩子,如果右孩子比左孩子小那就让minChild+1将这个调整最小孩子的动做放到while循环中,因为每一次调整都得和最小孩子交换位置。循环调整的停止条件是minChild小于n因为要保证交换的元素下标是在数组范围内的不能越界交换(这个条件也是循环最多次的条件)。

当a[minChild] < a[parent]时就进行交换调整。当父亲和最小孩子的值相等或者小于最小孩子的值 那就跳出循环 向下调整完成。

打印堆元素(以数组的形式打印)

void PrintHeap(HP* php)
{
    for (int i = 0; i < php->size; i++)
    {
        printf("%d ", php->a[i]);
    }

    printf("\n");
}

判空

bool HeapEmpty(HP* php)
{
    assert(php);
    return php->size == 0;
}

取堆顶元素

//返回堆顶元素
HPDataType HeapTop(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));
    return php->a[0];
}

元素个数

int HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

销毁

void HeapDestory(HP* php)
{
    assert(php);
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0;
}

2.测试用例:

#include"Heap.h"

int main()
{
    HP hp;
    HeapInit(&hp);
    int arr[] = { 65,100,70,32,50,60 };
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }

    while (!HeapEmpty(&hp))  //不为空时
    {
        printf("%d ", HeapTop(&hp));  //打印堆顶的元素

        HeapPop(&hp);   //删除堆顶的元素  三行代码就实现了出堆,调整,打印堆顶元素
    }                    //一直循环直到堆为空时循环停止

    HeapDestory(&hp);  //销毁堆

    return 0;
}

3.整体代码:

test.c:

#include"Heap.h"

int main()
{
    HP hp;
    HeapInit(&hp);
    int arr[] = { 65,100,70,32,50,60 };
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }

    while (!HeapEmpty(&hp))
    {
        printf("%d ", HeapTop(&hp));

        HeapPop(&hp);
    }

    HeapDestory(&hp);

    return 0;
}

Heap.c:

#include"Heap.h"


void HeapInit(HP* php)
{
    assert(php);
    php->a = NULL;
    php->capacity = php->size = 0;

}
void HeapDestory(HP* php)
{
    free(php->a);
    php->a = NULL;

}

void PrintHeap(HP* php)
{
    for (int i = 0; i < php->size; i++)
    {
        printf("%d ", php->a[i]);
    }

    printf("\n");
}

void Swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType tmp = *p1;
    *p1 = *p2;
    *p2 = 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 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;
        }

    }


}


//插入x  保持堆形态
void HeapPush(HP* 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 HeapPop(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));
    Swap(&php->a[0], &php->a[php->size - 1]);  //将根部元素与最后一个元素交换,然后再向下调整
    php->size--;  //相当于删除了最后一个元素
    AdjustDown(php->a, php->size, 0);
}

//返回堆顶的元素
HPDataType HeapTop(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));

    return php->a[0];
}

bool HeapEmpty(HP* php)
{
    assert(php);

    return php->size == 0;

}

int HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

Heap.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef  int  HPDataType;

typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;

}HP;

void PrintHeap(HP* php);

void HeapInit(HP* php);
void HeapDestory(HP* php);

//插入数据x 继续保持堆的形态
void HeapPush(HP* php, HPDataType x);

//删除堆顶的元素
void HeapPop(HP* php);

//返回堆顶的元素
HPDataType HeapTop(HP* php);

bool HeapEmpty(HP* php);

int HeapSize(HP* php);

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

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

相关文章

前端脚手架搭建(part 1)

本篇主要介绍如何搭建前端脚手架&#xff0c;一步一步地实现通过搭建的脚手架下载对应的项目模板。通过脚手架的创建&#xff0c;可以快速搭建项目的基础配置和模板&#xff0c;在部门项目开发的规范中尤其总要。初始化项目&#xff1a;创建一个文件夹&#xff0c;命名随便&…

【LeetCode】螺旋矩阵 [M](数组)

54. 螺旋矩阵 - 力扣&#xff08;LeetCode&#xff09; 一、题目 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,…

OceanBase 生态产品:时序数据库CeresDB 正式发布 1.0 版本

欢迎访问OceanBase官网获取更多信息&#xff1a;https://www.oceanbase.com/ CeresDB 是一款拥有计算存储分离架构的分布式时序数据库&#xff0c;其存储层可以基于 OceanBase KV、OSS 等。经过近一年的开源研发工作&#xff0c;CeresDB 1.0 现已正式发布&#xff0c;达到生产…

tcsh常用配置

查看当前的shell类型 在 Linux 的世界中&#xff0c;有着许多 shell 程序。常见的有&#xff1a; Bourne shell (sh) C shell (csh) TC shell (tcsh) Korn shell (ksh) Bourne Again shell (bash) 其中&#xff0c;最常用的就是bash和tcsh&#xff0c;本次文章介绍tcsh的…

HACKTHEBOX——Help

nmap可以看到对外开放了22,80,3000端口可以看到80端口和3000端口都运行着http服务&#xff0c;先从web着手切入TCP/80访问web提示无法连接help.htb&#xff0c;在/etc/hosts中写入IP与域名的映射打开只是一个apache default页面&#xff0c;没什么好看的使用gobuster扫描网站目…

配电室常见6大隐患,你中了几个?

当今社会&#xff0c;电力的重要性不言而喻&#xff0c;在变电站、配电室等一些场景中放置有大量的电气设备。 为保障这些设备正常运行&#xff0c;以免因为这些设备故障导致无法正常进行电力供应&#xff0c;严重影响生产和生活&#xff0c;我们需要运行一套动环监控系统。 配…

【MySQL】约束

文章目录1. 约束2. 非空约束 NOT NULL3. 唯一性约束 UNIQUE4. 主键约束 PRIMARY KEY5. 自增约束 AUTO_INCREMENT6. 外键约束FOREIGN KEY7. 默认值约束 DEFAULT8. 小结1. 约束 为了保证数据的完整性&#xff0c;SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面…

达人合作加持品牌布局,3.8女神玩转流量策略!

随着迅猛发展的“她经济”&#xff0c;使社区本就作为内容种草的平台&#xff0c;自带“营销基因”。在3.8女神节即将到来之际&#xff0c;如何充分利用平台女性资源优势&#xff0c;借助达人合作等手段&#xff0c;实现迅速引流&#xff0c;来为大家详细解读下。一、小红书节日…

数据的标准化处理

假设各个指标之间的水平相差很大&#xff0c;此时直接使用原始指标进行分析时&#xff0c;数值较大的指标&#xff0c;在评价模型中的绝对作用就会显得较为突出和重要&#xff0c;而数值较小的指标&#xff0c;其作用则可能就会显得微不足道。 因此&#xff0c;为了统一比较的标…

60% 程序员大呼:我要远程办公!

近几年数字化的普及&#xff0c;白领们从挤地铁、打卡、开会、写日报转变成“早上9点视频会议”&#xff0c;企业的办公场所也从写字楼、会议室、工位变成了手机、电脑中的线上会议室&#xff0c;远程办公已经成为一种流行的办公形式。《财富》杂志发现&#xff0c;75%的员工表…

全网火爆,软件测试面试题大全,接口测试题+回答 (18k+的offer)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 面试测试工程师的时…

UML全解

目录UML结构性图表类图类图中具体类、抽象、接口和包的表示法在UML类图中表示具体类在UML类图中表示抽象类在UML类图中表示接口在UML类图中表示包关系泛化&#xff08;Generalization&#xff09;实现&#xff08;Realization&#xff09;关联&#xff08;Association&#xff…

Pyspark基础入门7_RDD的内核调度

Pyspark 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase Hi…

20230307 LS-DYNA易拉罐变形学习笔记

一、定义材料 定义铝合金材料 在左侧把density拖至alumium处 即可在下方输入密度值 同样的道理,将各向同性弹性拉到材料处,即可定义杨氏模量、泊松比等参数

TDengine | 04 | TDengine3.0.2.6二进制包部署

1 前期准备 1.1 版本 TDengine :3.0.2.6 Linux&#xff1a;centos 7 1.2 机器规划 机器IPhostname192.168.3.21master.bafang.com192.168.3.22node1.bafang.com192.168.3.23node2.bafang.com 1.3 修改这三个节点上的hostname hostnamectl set-hostname master.bafang.com…

HBase常用Shell命令

HBase提供了一个非常方便的命令行交互工具HBase Shell。通过HBase Shell&#xff0c;HBase可以与MySQL命令行一样创建表、索引&#xff0c;也可以增加、删除和修改数据&#xff0c;同时集群的管理、状态查看等也可以通过HBase Shell实现。 一、数据定义语言 数据定义语言&…

Acwing: 一道关于线段树的好题(有助于全面理解线段树)

题目链接&#x1f517;&#xff1a;2643. 序列操作 - AcWing题库 前驱知识&#xff1a;需要理解线段树的结构和程序基本框架、以及懒标记的操作。 题目描述 题目分析 对区间在线进行修改和查询&#xff0c;一般就是用线段树来解决&#xff0c;观察到题目一共有五个操作&…

查看python第三方库的依赖pkgs

课题组的服务器不给连外网&#xff0c;安装python第三方库就只能手动离线安装。但是python第三方库可能会迭代依赖&#xff0c;单纯的pip show [pkg]是看不出来的…… 参考链接&#xff1a;查看python第三方库的依赖 https://blog.csdn.net/qq_38316655/article/details/127943…

【多层交叉transformer:高光谱和多光谱图像融合】

MCT-Net: Multi-hierarchical cross transformer for hyperspectral and multispectral image fusion &#xff08;MCT-Net&#xff1a;用于高光谱和多光谱图像融合的多层交叉transformer&#xff09; 考虑到光学成像的局限性&#xff0c;图像采集设备通常在空间信息和光谱信…

04-SQL基础(表管理,约束,多表连接,子查询)

本文章主要内容 1、表的管理&#xff1a;创建表&#xff0c;修改表结构&#xff0c;删除字段&#xff0c;修改字段&#xff0c;添加字段&#xff0c;删除表&#xff0c;添加表约束&#xff1b; 2、数据管理&#xff1a;新增记录&#xff0c;修改记录&#xff0c;删除记录&…