首页 > 其他分享 >数据结构--堆

数据结构--堆

时间:2024-01-23 23:36:19浏览次数:23  
标签:结点 -- void tree int heap 数据结构 ipos

前言

​ 在实际很多的应用场景中,我们对数据进行处理的时候,比如插入数据和删除数据时,我们常常需要快速的知道数据中最大值和最小值。而处理这种问题的方法之一,就是使用一个已经排序好的数据集合。通过这种方式,数据的最大值或最小值总是在数据集合的头部或者尾部(这取决于使用时升序排列还是降序排列)。然而,将数据集合继续有序排序需要代价是非常高的。并且在许多情况下,我们并不是相对数据集合所有的元素进行排序,可能我能只是希望可以快速的找数据集合中的最大值或者最小值而已,只需要让元素排序在原本的储存位置就行。堆和优先队列这种结构便是可以处理这种问题的有效方法。

堆的描述

​ 堆的本质是一颗二叉树,通常其子结点存储的值比父结点存储的值小。所以,根结点是树中最大的结点。同样,我们也可以让堆有另外一种形式,即子结点储存值都比父结点存储的值来的大。这样根结点就是树种最小的值。这样的规则,让二叉树在局部是有序的,任何一个结点和其兄弟结点之间没有必然的大小关系,但是和它的父结点有必然的大小关系。如果子结点比父结点值小,我们称这种结构为最大堆,反之子结点比父结点大为最小堆。

​ 堆的本质是二叉树,随着结点的增加,树会逐级从左向右增长。因此对于堆而言,一个比较好的表示左平衡树的方式,将结点通过水平遍历的方式放置在一个数组中。这样对于一个数组中处于位置\(i\)的结点,其父结点位于\(|i-1|/2\)的位置,计算的时候要忽略\(|i-1|/2\)的小数点部分。其左结点和右结点分别位于\(2i+1\)和\(2i+2\)的位置。这样的结构对于堆是十分重要的,因为可以快速的定位堆的最后一个结点位置。最后一个结点就表示位于树的最深处的最右边的结点。这个在实现堆的某些操作的时候是很重要的。为了方便理解,我这里画了一幅图来进行说明。

image

堆的接口定义

heap_init

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

函数原型:void heap_init(Heap* heap, int);

返回值:

函数说明: 初始化堆heap。在对堆进行其他的操作前必须先调用当前函数对堆进行初始化。在堆进行初始化的过程中主要做这几个行为。

  • 将heap的成员size设置为0
  • 将heap的成员函数destroy指向你给予的摧毁函数
  • 将heap的tree指针设置为NULL

时间复杂度:heap_init的时间复杂度为\(O(1)\) 因为初始化堆的几个步骤在固定的几个时间完成。

heap_destroy

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

函数原型:void heap_destroy(heap_t *heap);

返回值:

函数说明: 摧毁堆heap,该函数的主要作用是移除堆中的所有的结点。当heap结构中的指针不为NULL时,destroy将使用指向的函数对堆的结点进行移除。

时间复杂度:heap_destroy函数的时间复杂度为\(O(N)\),这是由于必须遍历所有堆中的所有的结点。当然如果堆的destroy函数为NULL。时间复杂度为\(O(1)\)

heap_Insert

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

函数原型:int heap_insert(heap_t *heap, const void *data);

返回值: 如果函数插入成功返回0,否则返回-1

函数说明: 将heap中插入一个结点。新的结点包含一个指向data的指针,只要结点存在于堆中,这个指针便一直存在。和data相关的内存空间给函数的使用进行调用。

时间复杂度:时间复杂度为\(O(lgN)\),其中N为堆的结点

heap_extract

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

函数原型:int heap_extract(heap_t *heap, void **data);

返回值: 如果函数释放成功返回0,否则返回-1

函数说明: 从堆heap中释放顶点的结点

时间复杂度:时间复杂度为\(O(lgN)\),其中N为堆的结点

以上为我们声明堆的结点,现在要分析堆的实现方案,只有知道堆的实现方案,我们才可以开始进行堆的C语言代码的实现。

堆的接口实现原理

堆的插入heap_Insert

​ 堆使用heap_insert函数进行结点的插入。函数将新结点加入到用户指定的堆中,首先我们需要对新的结点进行内存的分配,保证树可以容纳这个结点。将新插入的结点放在数组的末尾。这个时候插入数据会破坏堆的排序规则,所以我们必须调节树的结构,对结点进行重新排序.

image

image

image

​ 在插入结点时,为了重新的排序一颗树,只需要考虑新的结点插入的是那个分支,因为这是形成堆的局部的开始分支。从新结点开始,将结点向树的上方层层移动,比较每个子结点和它的父结点。在每一层上,如果父结点和子结点的位置不正确,就交换两个结点的内容。这个交换的结果会不断的进行直到某一层满足了堆的规则为止。最后要堆的size进行更新。

​ 堆的插入过程的时间复杂度为\(O(lgN)\),这是因为在最糟糕的情况下,需要将新的结点中的内容从树的最底层移动到树的顶层,这个是一个\(lgN\)级别的遍历过程。

堆的顶部释放heap_extract

​ 堆有heap_extracy函数来对堆的顶部元素进行释放,首先,将data指向要释放的结点的数据进行释放。接下去,保存最后一个结点的内容,并且将二叉树的大小减去一,将树的大小减去1,为树分配较小的内存空间。完成以上工作后,我们将最后一个结点的内容拷到根结点中。显然,这个过程会破坏堆的固有大小规则,所以我们必须重新的调节堆的结构。

image

image

image

实现代码

​ 实现的代码以上为原理进行C语言编写

#ifndef __HEAP_H__
#define __HEAP_H__ 

/***************************************************************************************
 * 定义堆的结构体
***************************************************************************************/
typedef struct __heap
{
    int  size;
    int  (*compare)(const void *key1, const void *key2);
    void (*destroy)(void *data);
    void **tree;
}heap_t;

/***************************************************************************************
 * 堆的公共接口
***************************************************************************************/
void heap_init(heap_t *heap, int (*compare)(const void *key1, const void *key2),  void (*destroy)(void *data));

void heap_destroy(heap_t *heap);

int heap_insert(heap_t *heap, const void *data);

int heap_extract(heap_t *heap, void **data);

#define HEAD_SIZE(__heap)       ((__heap)->size)

#endif /* heap.h */
#include <stdlib.h>
#include <string.h>
#include "heap.h"

#define HEAP_PARENT(__npos)       ((int)(((__npos)-1)) / 2)
#define HEAP_LEFT(__npos)         ((int)(((__npos)*2)) + 1)
#define HEAP_RIGHT(__npos)        ((int)(((__npos)*2)) + 2)

void heap_init(heap_t *heap, int (*compare)(const void *key1, const void *key2),  void (*destroy)(void *data))
{
    /* 初始化堆 */
    heap->size = 0;
    heap->compare = compare;
    heap->destroy = destroy;
    heap->tree = NULL;
    
    return;
}

void heap_destroy(heap_t *heap)
{
    /* 从堆中将所有的结点移除 */
    if(heap->tree != NULL)
    {
        for(int i = 0; i < HEAD_SIZE(heap); ++i)
        {
            heap->destroy(heap->tree[i]);
        }
    }
    /* 释放堆的分配内存 */
    free(heap->tree);
    memset(heap, 0, sizeof(heap_t));

    return;
}

int heap_insert(heap_t *heap, const void *data)
{
    void *temp;
    int ipos;
    int ppos;

    if((temp = (void**)realloc(heap->tree, (HEAD_SIZE(heap)+1)*sizeof(void*))) == NULL)
    {
        return -1;
    }
    else
    {
        heap->tree = temp;
    }

    /* 在后结点插入结点 */
    heap->tree[HEAD_SIZE(heap)] = (void*)data;
    ipos = HEAD_SIZE(heap);
    ppos = HEAP_PARENT(ipos);

    while (ipos > 0 && heap->compare(heap->tree[ppos], heap->tree[ipos]) < 0)
    {
        temp = heap->tree[ppos];
        heap->tree[ppos] = heap->tree[ipos];
        heap->tree[ipos] = temp;

        ipos = ppos;
        ppos = HEAP_PARENT(ipos);
    }
    heap->size++;

    return 0;

}

int heap_extract(heap_t *heap, void **data)
{
    void *save;
    void *temp;

    int ipos;
    int lpos;
    int rpos;
    int mpos;

    if(HEAD_SIZE(heap) == 0)
        return -1;
    *data = heap->tree[0];
    save = heap->tree[HEAD_SIZE(heap) - 1];
    if((HEAD_SIZE(heap) - 1) > 0)
    {
        if((temp == (void**)realloc(heap->tree, (HEAD_SIZE(heap) - 1) * sizeof(void*))) == NULL)
            return -1;
        else
            heap->tree = temp;
        heap->size--;
    }
    else
    {
        free(heap->tree);
        heap->tree = NULL;
        heap->size = 0;
        return 0;
    }
    heap->tree[0] = save;
    ipos = 0;
    lpos = HEAP_LEFT(ipos);
    rpos = HEAP_RIGHT(ipos);

    while (1)
    {
        lpos = HEAP_LEFT(ipos);
        rpos = HEAP_RIGHT(ipos);

        if(lpos < HEAD_SIZE(heap) && heap->compare(heap->tree[lpos], heap->tree[ipos]) > 0)
            mpos = lpos;
        else
            mpos = ipos;
        if(rpos < HEAD_SIZE(heap) && heap->compare(heap->tree[rpos], heap->tree[mpos]) > 0)
            mpos = rpos;

        if(mpos == ipos)
            break;
        else
        {
            temp = heap->tree[mpos];
            heap->tree[mpos] = heap->tree[ipos];
            heap->tree[ipos] = temp;
            ipos = mpos;
        }
    }
    return 0;
}

标签:结点,--,void,tree,int,heap,数据结构,ipos
From: https://www.cnblogs.com/Kroner/p/17983650

相关文章

  • 大语言模型的架构及其训练(目标函数和优化算法)
    先占坑24号早上起来补大模型的架构 大模型的训练模型训练=目标函数+优化算法可用任何模型将token序列映射到上下文嵌入中一、目标函数1.Decoder-only模型①映射到上下文嵌入②用嵌入矩阵获得每个token得分③指数化、归一化得预测分布用负对数最大似然作为目标函数2.Encoder-o......
  • Debian grub丢失后修复的方法
    第一种,命令方式通过Debianrescue模式重建grub制作debian的U盘安装盘进入debian的U盘安装盘的rescue模式(急救模式)选择语言/键盘/输入姓名/配置网络等信息后,进入急救模式->选择在安装程序环境中运行shell->选择请不要使用根文件系统(方便手动挂载已有系统文件系统)。#由于/......
  • [Ynoi2000] pri
    问题等价于区间翻转区间顺序对数,显然没有复杂度比较好的做法。将操作序列每\(B\)个分一组,对每组进行处理。\(B\)个操作会将序列划分为\(B\)个连续段,在每次操作后都是连续段的一个排列,以及每个连续段内部可能翻转。我们称每个连续段为一个等价类。将值域按\(C\)大小分块......
  • 新手开发小程序教程怎么去开发小程序基本教程分享 -24软件网
    下面是一篇关于新手程序员开发小程序的教程:第一步:注册微信小程序账号进入微信公众平台首页,点击"立即注册"按钮进行注册。选择注册账号类型为小程序。填写账号信息,确保填写的邮箱是未被微信公众平台注册、未被个人微信号绑定的邮箱。第二步:了解基本语法小程序使用的是类似......
  • 2023 的一些总结
    2023的一些总结李宗盛在山丘开头里面写道,"想说却还没说的还很多攒着是因为想写成歌"。同样的,在印象中在2023好像做了很多东西,接触了很多技术,但是一直没有整理,攒着攒着就到年底了。细细思考了一下,有两个板块是今年主要发力的点对网络的探索对业务代码优化的思考对网络......
  • 无涯教程-CSS - 文字效果
    您可以使用CSS过滤器将特殊效果添加到文本,图像和网页的其他方面,而无需使用图像或其他图形。AlphaChannelAlpha通道滤镜会更改对象的透明度,从而使其融合到背景中,以下参数可以在此过滤器中使用-Sr.No.Parameter&Remark1opacity透明度。0是完全透明的,100是完全不透明的......
  • 第二篇博客(依旧摆烂)
    2024.1.23哦豁,恍惚间又过了一个月,依旧没有动笔(悲!!!);自放假以来一直在oi里埋头苦读;然而Lyn学长讲的dp是一点也不会,状态转移方程到底是什么鬼东西啊!!!(写不了一点);极度怀疑gg说的精兵强将是否属实(只对本人来说);!!!;啊啊啊啊啊啊啊啊啊啊啊!!!!(发疯ing);果然依旧是蒟蒻oier;希望能成......
  • SV 随机化(Randomization)
    CoverageDriverVerification可约束的随机化验证,用于测试的值可以再一定范围内进行随机,具体的范围可以进行约束,比如可以跑100次,然后查看覆盖率,可以通过覆盖率进行度量验证的进度内容随机化的变量往往需要添加一定的约束,通过添加约束让值在一定的范围内进行随机随......
  • 故事补全计划
    以前年少时候看过的小说总是残缺不全,那些故事被切成了一段一段,如同琉璃碎瓦片。最近倒是又开始看起了杂书,把那些故事补全了。虽然不再有那些眼里有光的心气,但也没有完全消磨掉自己原来的样子。随笔就是随便写写,记下自己当下的感受,等以几百年后的人看到这段文字,他也能懂一个古代......
  • Java流程控制
    Java流程控制1、用户交互Scanner之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入。java.util.Scanner是Java5的新特性,我们可以通过Scanner类来获取用户的输入。基本语法Scanners=newScanner(System.in)......