数据结构-堆 接口定义与实现分析(详细注释与图解)

如果想了解堆的概念,可以点击此处查看前面关于堆的定义的随笔。

堆的操作接口包括初始化堆、销毁堆、向堆中插入元素、从堆顶移除元素、堆的结点个数。

我们用heap来命名一个堆。下面是对以上接口的定义:

heap_init



void heap_init(Heap *heap,int (*compare)(const void key1,const void key2),void (*destroy)(void *data));

返回值:无

描述初始化堆heap

在对堆执行其他操作之前,必须要对其进行初始化。

函数compare用来比较堆中的结点大小,如果堆为最大值堆,当key1>key2时,函数返回1;当key1=key2时,函数返回0;当key1<key2时,函数返回-1。在最小值堆中返回值正好相反。

函数指针destroy,通过调用heap_deatroy来释放动态分配的内存空间。如果堆中的数据不需要释放,那么destroy应该指向NULL。

复杂度:O(1)

heap_destroy



void heap_destroy(Heap *heap);

返回值:无

描述销毁堆heap

在调用heap_destroy之后不再允许进行其他操作。

heap_destroy会删除堆中的所有结点,在删除的同时调用heap_init中的destroy所指向的销毁函数(此函数指针不为NULL)。

复杂度:O(n),n代表堆中的结点个数。

heap_insert



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

返回值:如果插入元素成功则返回0,否则返回-1。

描述向堆heap中插入一个结点

新结点包含一个指向data的指针,只要结点仍然存在于堆中,此指针就一直有效。与data有关的内存空间将由函数的调用者来管理。

复杂度:O(lg n),n代表堆中的结点个数。

heap_extract



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

返回值:如果结点释放成功则返回0,否则返回-1。

描述从堆heap中释放堆顶部的结点

返回时,data指向被释放结点中存储的数据。与data相关的内存空间将由函数的调用者来管理。

复杂度:O(lg n),n代表堆中的结点个数。

heap_size



int heap_size(const Heap *heap);

返回值:堆中结点的个数。

描述:这是一个获取堆heap中结点个数的宏。

复杂度:O(1)。

下面来分析一下如何实现堆:

我们用来叉树来实现堆,将其结点按照树的层次结构存放在一个数组中。我们令Heap作为堆的数据结构,此结构体包含4个成员:

1、size指明堆中的结点个数;2、compare与3、destroy是用于封闭传入heap_init的函数的指针;4、tree是堆中存储结点的数组。

堆的头文件示例如下:

/*heap.h*  堆的头文件/
#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;

/*公共接口部分*/
void heap_init(Heap *heap,int (*compare)(const void *key1,const void key2),void (*destroy)(void *data));
void heap_destroy(Heap *heap);
int heap_insert(Heap *heap,const void *data);
int heap_extract(Heap *heap,void **data);

#define heap_size(heap)((heap)->size)

#endif // HEAP_H

堆的实现示例如下:

/*heap.c*/
#include <stdlib.h>
#include <string.h>

#include "heap.h"
/*定义heap执行中需要使用的私有宏*/
#define heap_parent(npos) ((int)(((npos)-1)/2))  /*npos的父结点*/
#define heap_left(npos) (((npos)*2)+1)           /*npos的左兄弟结点*/
#define heap_right(npos) (((npos)*2)+2)          /*npos的右兄弟结点*/

/*heap_init 堆的初始化*/
void heap_init(Heap *heap,int (*compare)(void *key1,void *key2),void (*destroy)(void *data))
{
    /*只需要将size设为0,destroy成员指向destroy,将tree指针设置为NULL*/
    heap->size = 0;
    heap->compare = compare;
    heap->destroy = destroy;
    heap->tree = NULL;

    return ;
}
/*heap_destroy  销毁堆*/
void heap_destroy(Heap *heap)
{
    int i;
    /*移除堆中所有的结点*/
    if(heap->destroy != NULL)
    {
        for(i=0; i<heap_size(heap);i++)
        {
            /*调用用户自定义函数释放动态分配的数据*/
            heap->destroy(heap->tree[i]);
        }
    }
    /*释放为堆分配的空间*/
    free(heap->tree);

    memset(heap,0,sizeof(Heap));
    return;
}

/*heap_insert  向堆中插入结点*/
int heap_insert(Heap *heap,const void *data)
{
    void *temp;
    int  ipos;
         ppos;

    /*为结点分配空间*/
    if((temp = (void **)realloc(heap->tree,(heap->size(heap)+1)*sizeof(void *))) == NULL)
    {
        return -1;
    }
    else
    {
        heap->tree = temp;
    }
    /*将结点插入到堆的最末端*/
    heap->tree[heap_size(heap)] = (void *)data;
    /*将新结点向上推动,恢复堆的排序特点*/
    ipos = heap_size(heap);    /*堆结点数的数值*/
    ppos = heap_parent(ipos);  /*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;
}

/*heap_extract 释放堆顶部的结点*/
int heap_extract(Heap *heap,void **data)
{
    void *save,
         *temp;
    int  ipos,lpos,rpos,mpos;

    /*不允许从空的堆中释放结点*/
    if(heap->size(heap) == 0)
        return -1;

    /*释放堆顶部的结点*/
    /*首先将data指向将要释放结点的数据*/
    *data = heap->tree[0]

    /*将save指向未位结点*/
    save = heap->tree[heap_size(heap)-1];

    if(heap_size(heap)-1 > 0)
    {   /*为堆分配一个稍小一点的空间*/
        if((temp = (void **)realloc(heap->tree,(heap_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 < heap_size(heap) && heap->compare(heap->tree[lpos],heap->tree[ipos])>0)
        {
            mpos = lpos;  /*将左子结点的位置赋给mpos(最大位置)*/
        }
        else
        {
            mpos = ipos;
        }

        if(rpos < heap_size(heap) && heap->compare(heap->tree[rpos],heap->tree[mpos])>0)
        {
            mpos = rpos;
        }

        /*当mpos和ipos相等时,堆特性已经被修复,结束循环*/
        if(mpos == ipos)
        {
            break;
        }
        else
        {
            /*交换当前结点与被选中的结点的内容*/
            temp = heap->tree[mpos];
            heap->tree[mpos] = heap->tree[ipos];
            heap->tree[ipos] = temp;

            /*下移一层,以继续执行堆排序*/
            ipos = mpos;
        }
    }
    return 0;
}

下图是heap_insert,向最大值堆中插入结点的过程图解示例

下图是将一个元素从最大值堆中释放的过程图解示例:

原文地址:https://www.cnblogs.com/idreamo/p/8564426.html

时间: 2024-10-18 16:51:39

数据结构-堆 接口定义与实现分析(详细注释与图解)的相关文章

数据结构 链表_双向链表的接口定义

双向链表介绍 双向链表中的每一个元素都由3部分组成:除了数据成员.next指针外,每个元素还包含一个指向其前驱元素的指针,称为prev指针.双向链表的组成是这样的:将一些元素链接在一起,使得每个元素的next指针都指向其后继的元素,而每个元素的prev指针都指向其前驱元素. 为了标识链表的头和尾,将第一个元素的prev指针和最后一个元素的next指针设置为NULL. 要反向遍历整个双向链表,使用prev指针以从尾到头的顺序连续访问各个元素.当我们知道某个元素存储在链表在的某处时,我们可以选择按何

C 数据结构堆

引言 - 数据结构堆 堆结构都很耳熟, 从堆排序到优先级队列, 我们总会看见它的身影. 相关的资料太多了, 堆 - https://zh.wikipedia.org/wiki/%E5%A0%86%E7%A9%8D 无数漂亮的图片接二连三, 但目前没搜到一个工程中可以舒服用的代码库. 本文由此痛点而来. 写一篇奇妙数据结构堆的终结代码. 耳熟终究比不过手热 ->--- 对于 heap 接口思考, 我是这样设计 #ifndef _H_HEAP #define _H_HEAP // // cmp_f

数据结构--堆的实现(下)

1,堆作为优先级队列的应用 对于普通队列而言,具有的性质为FIFO,只要实现在队头删除元素,在队尾插入元素即可.因此,这种队列的优先级可视为按 时间到达 的顺序来衡量优先级的.到达得越早,优先级越高,就优先出队列被调度. 更一般地,很多应用不能单纯地按时间的先后来分优先级,比如按CPU占用时间或者其它方式……在这种情形下,使用堆更容易表达优先级队列. 2,堆的两个性质:①结构性质--堆从结构上看是一颗完全二叉树.然而,从物理存储上看,堆的实现基本上是使用一个一维数组存储堆中所有的结点.②orde

数据结构--堆的实现之深入分析

一,介绍 以前在学习堆时,写了两篇文章:数据结构--堆的实现(上)   和   数据结构--堆的实现(下),  感觉对堆的认识还是不够.本文主要分析数据结构 堆(讨论小顶堆)的基本操作的一些细节,比如 insert(插入)操作 和 deleteMin(删除堆顶元素)操作的实现细节.分析建堆的时间复杂度.堆的优缺点及二叉堆的不足. 二,堆的实现分析 堆的物理存储结构是一维数组,逻辑存储结构是完全二叉树.堆的基本操作有:insert--向堆中插入一个元素:deleteMin--删除堆顶元素 故堆的类

数据结构--树(定义与存储结构)

树基本定义 树的定义 数是具有n个节点的有限集.如图即是一个树形结构. 节点分类 节点的度:一个节点拥有的子节点即成为节点的度,比如A节点,有B和C两个子节点,那么A节点的度=2. 叶节点(终端节点):没有子节点的节点,比如G.H.I.... 如图: 节点间关系 孩子节点:某一个节点的子节点称为孩子节点.比如B.C节点是A节点的孩子节点. 双亲节点:与孩子节点相反.比如,A节点是B.C的双亲节点. 兄弟节点:同一个双亲节点的孩子节点,之间称为兄弟节点.比如,B.C为兄弟节点. 如图: 树的存储结

基本数据结构——堆(Heap)的基本概念及其操作

基本数据结构――堆的基本概念及其操作 小广告:福建安溪一中在线评测系统 Online Judge 在我刚听到堆这个名词的时候,我认为它是一堆东西的集合... 但其实吧它是利用完全二叉树的结构来维护一组数据,然后进行相关操作,一般的操作进行一次的时间复杂度在 O(1)~O(logn)之间. 可谓是相当的引领时尚潮流啊(我不信学信息学的你看到log和1的时间复杂度不会激动一下下)!. 什么是完全二叉树呢?别急着去百度啊,要百度我帮你百度: 若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1

数据结构之队列定义及基本操作实现

数据结构学着就是有意思,真诚推荐郝斌老师的数据结构视频,真的讲解的非常详细,容易理解. 一直在跟着郝斌老师的数据结构视频学习,看完了队列的视频,记录下来,总结一下. 队列的定义:队列是一种特殊的线性表,只允许在表的头部(front处)进行删除操作,在表的尾部(rear处)进行插入操作的线性数据结构,这种结构就叫做队列.进行插入操作的一端称为队尾,进行删除操作的一端称为队尾. 队列的类型:链式队列,即用链表实现的队列.静态队列:即用数组实现的队列.在这里,我们采用用数组实现的静态队列.因为用链表实

JAVA8新特性——接口定义增强

JAVA9都要出来了,JAVA8新特性都没搞清楚,是不是有点掉队哦~ 接口定义增强 在JDK1.8以前,接口是定义的: 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明.一个类通过继承接口的方式,从而来继承接口的抽象方法. 在JDK1.8之前,接口有如下特性: 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错). 接口中

第37条:用标记接口定义类型

标记接口是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口.考虑Serializable接口,通过实现这个接口,类表明它的实例可以被写到ObjectOutputStream. 标记接口相比标记注解的优点: 1.标记接口定义的类型是由被标记类的实例实现的:标记注解则没有定义这样的类型. 2. 可以被更精确地进行锁定.如果注解类型利用@Target(ElementType.TYPE)声明,它就可以被应用到任何类或者接口,假设有一个标记只是适用于特殊的接口实现,但它却可以被应用到类,如