相比队列和栈,很多人可能对堆的概念比较陌生,下面个给出堆的本质概念

一、堆也是一种数据结构,从实际应用意义来说,他是一种最优级别数据永远在第一位的队列,本文皆以最小值为例(小顶堆),即它变相是一种会永远保持最小值先出队的队列。

二、堆的本质是一颗完全二叉树,树根永远为整个树的最小值,这也就是实现了①永远保持最小值先出队的队列这样的功能。

三、为了便于实现②树根为整个树的最小值,堆中某个节点的值总是大于其父节点的值,这样当树根出队以后,次小的值一定是树根的左右子节点的其中一个。并且堆每个结点的左子树和右子树也都是一个堆。即:当某一个节点发生改变之后,取代他的只有可能和他有直系关系的节点。

四、为了保证②的完全二叉树性质,当堆有元素入队时,我们先将其放在队尾,然后再对其做出调整。当有元素出队时,我们把最后一个节点的元素先放在队首,然后再对其做出调整。

五、结合③和④,我们可以得出结论,无论是入队还是出队,我们实际做的操作都是在某一路径下完成若干次子节点与父节点交换的过程(我们称之为上浮和下沉)

下面再说一下堆支持的基本操作

①入队,插入新元素

②出队,删除堆顶

③查找最小值(堆顶),其实也可以查找其次小值

④建堆,这里建队有两种方法,一种是将值一个一个的插入,另一种是先变成一个堆,再对其进行多次调整。

下面具体讲实现方法,首先定义一个小顶堆

/*
 * 使用数组实现堆
 *
 * capacity 数组的最大容量
 * size     数组的长度
 * elements 堆中的元素存放的数组
 */
struct MinHeap
{
    int capacity;
    int size;
    ElementType *elements; 
};

入队:

先将要插入的结点x放在最底层的最右边,插入后满足完全二叉树的特点,然后把x依次向上调整到合适位置满足堆的性质,即“结点上浮”。

for (i = ++pqueue->size; x < pqueue->elements[i / 2]; i /= 2)
            pqueue->elements[i] = pqueue->elements[i / 2]; //上浮
        pqueue->elements[i] = x;

出队:

树根出队,然后先把最后的叶子节点放在树根,再逐渐下沉到其合适的位置。

// 注意对节点进行下沉操作时,要判断该节点是有两个儿子还是一个儿子
    minElement = pqueue->elements[1];
    lastElement = pqueue->elements[pqueue->size--];
    for (i = 1; i * 2 <= pqueue->size; i = child)
    {
        child = i * 2;
         
        // 节点i只有一个儿子时必有i * 2 = pqueue->size
        if (child < pqueue->size && pqueue->elements[child] > pqueue->elements[child + 1])
            child++;
         
        if (lastElement < pqueue->elements[child])
            break;
        else
            pqueue->elements[i] = pqueue->elements[child]; 
    }
    pqueue->elements[i] = lastElement;
     
    return minElement; // 返回被删除的元素

查找

直接返回队首就可以了

return pqueue->elements[1];

建堆:

第一种方法:建立一个空堆,然后将一组数据依次做插入操作,时间复杂度O(NlogN)

pqueue = initialize(n * 2);
for (int i = 0; i < n; i++)
    insert(arr[i], pqueue);

第二种方法:先将数组转化为堆,再进行调整,时间复杂度为O(N)

for (i = 1; i <= n; i++)
        elements[i] = arr[i - 1];
    elements[0] = SentinelElement;//数组转换为堆
     
    for (i = n / 2; i > 0; i--)
        adjust(elements, n + 1, i);//从最后一个非叶子节点开始,做调整处理

void adjust(int *arr, int len, int i)
{
    int n = len - 1;
    int tmp;
if(i * 2 >n)                       //叶子节点跳出
return;
    if (i * 2 == n && arr[i] > arr[n]) // 只有左儿子的节点,并且左儿子比本节点的值要小,交换
    {
        tmp = arr[i];
        arr[i] = arr[n];
        arr[n] = tmp;
    }
    else // 有两个儿子的节点
    {
        if (arr[i * 2] > arr[i * 2 + 1]) // 右儿子较小
        {
            if (arr[i] > arr[i * 2 + 1]) // 如果本节点比右儿子大,交换
            {
                tmp = arr[i];
                arr[i] = arr[i * 2 + 1];
                arr[i * 2 + 1] = tmp;
adjust(arr, len, i * 2 + 1);
            }
        }
        else // 左儿子较小
        {
            if (arr[i] > arr[i * 2]) // 如果本节点比左儿子大,交换
            {
                tmp = arr[i];
                arr[i] = arr[i * 2];
                arr[i * 2] = tmp;
adjust(arr, len, i * 2 );
            }
        }
    }
}

再说一下堆排序,其实可以把他当作是原堆所有值都出队就好了。时间复杂度O(NlogN)

for (i = 1; i <= n; i++)
elements[i] = deleteMin(pqueue);
elements[0] = SentinelElement;

下面给出完整代码

MinHeap.h  头文件

#ifndef DataStructures_MinHeap_h
#define DataStructures_MinHeap_hstruct MinHeap;
typedef struct MinHeap * MinPriorityQueue;
typedef int ElementType;// 初始化堆
MinPriorityQueue initialize(int maxElements);// 销毁堆
void destroy(MinPriorityQueue pqueue);// 清空堆中的元素
void makeEmpty(MinPriorityQueue pqueue);// 插入操作
void insert(ElementType x, MinPriorityQueue pqueue);// 删除最小者操作,返回被删除的堆顶元素
ElementType deleteMin(MinPriorityQueue pqueue);// 查找最小者(堆顶)
ElementType findMin(MinPriorityQueue pqueue);// 判断堆是否为空
int isEmpty(MinPriorityQueue pqueue);// 判断堆是否满
int isFull(MinPriorityQueue pqueue);// 通过一个数组来建堆,相当于将用数组实现的无序树转换为堆序树——插入法
MinPriorityQueue buildHeap_insert(int *arr, int n);
// 通过一个数组来建堆,相当于将用数组实现的无序树转换为堆序树——调整法
MinPriorityQueue buildHeap_percolate(int *arr, int n);//堆排序
MinPriorityQueue buildHeap_sort(MinPriorityQueue pqueue);// 打印堆
void printMinPriorityQueue(MinPriorityQueue pqueue);#endif

MinHeap.cpp  实现函数

#include <stdio.h>
#include <stdlib.h>
#include "MinHeap.h"/* 标记节点,类似于链表中的表头节点* 该值必须小于所有最小堆中的元素,设其值为-1*/
#define SentinelElement -1/** 使用数组实现堆** capacity 数组的最大容量* size     数组的长度* elements 堆中的元素存放的数组*/
struct MinHeap
{int capacity;int size;ElementType *elements; // 堆的元素个数为size,实际上用来存储的数组的长度为size + 1,还包括一个sentinel元素
};void PQueueNULLWarning()
{printf("Warning: Minimum Priority Queue is NULL");
}void outOfSpaceFatalError()
{printf("Fatal Error: Out of space");abort();
}MinPriorityQueue initialize(int maxElements)
{MinPriorityQueue pqueue;if (maxElements <= 0){printf("Fail to initialize: maxElements <= 0");return NULL;}pqueue = (MinPriorityQueue)malloc(sizeof(struct MinHeap));if (pqueue == NULL)outOfSpaceFatalError();// 数组的第0个元素是个sentinel标记节点,计入数组容量中,但不计入capcaity或size中pqueue->size = 0;pqueue->capacity = maxElements;pqueue->elements =  (ElementType *)malloc(sizeof(ElementType) * (pqueue->capacity + 1));if (pqueue->elements == NULL)outOfSpaceFatalError();elsepqueue->elements[0] = SentinelElement;return pqueue;
}void destroy(MinPriorityQueue pqueue)
{if (pqueue != NULL){// 在GNU99标准中,free(NULL)什么都不做直接返回,所以不用判断pqueue->elements是否为NULLfree(pqueue->elements);free(pqueue);}
}void makeEmpty(MinPriorityQueue pqueue)
{if (pqueue != NULL)pqueue->size = 0;elsePQueueNULLWarning();
}/** 插入的时间复杂度为O(log N),N为最小堆中的元素个数*/
void insert(ElementType x, MinPriorityQueue pqueue)
{if (pqueue == NULL)PQueueNULLWarning();if (isFull(pqueue)){printf("Fail to insert: Priority Queue is Full");return;}else{int i;for (i = ++pqueue->size; x < pqueue->elements[i / 2]; i /= 2)pqueue->elements[i] = pqueue->elements[i / 2]; //上浮pqueue->elements[i] = x;}
}/** 删除操作的平均时间为O(log N)*/
ElementType deleteMin(MinPriorityQueue pqueue)
{if (pqueue == NULL){PQueueNULLWarning();return SentinelElement;}if (isEmpty(pqueue)){printf("Fail to delete: Priority Queue is Empty");return SentinelElement;}int i, child;ElementType minElement, lastElement;// 注意对节点进行下沉操作时,要判断该节点是有两个儿子还是一个儿子minElement = pqueue->elements[1];lastElement = pqueue->elements[pqueue->size--];for (i = 1; i * 2 <= pqueue->size; i = child){child = i * 2;// 节点i只有一个儿子时必有i * 2 = pqueue->sizeif (child < pqueue->size && pqueue->elements[child] > pqueue->elements[child + 1])child++;if (lastElement < pqueue->elements[child])break;elsepqueue->elements[i] = pqueue->elements[child]; }pqueue->elements[i] = lastElement;return minElement; // 返回被删除的元素
}/** 执行时间:O(1)*/
ElementType findMin(MinPriorityQueue pqueue)
{if (pqueue == NULL){PQueueNULLWarning();return SentinelElement;}elsereturn pqueue->elements[1];
}int isEmpty(MinPriorityQueue pqueue)
{if (pqueue == NULL){PQueueNULLWarning();return -1;}elsereturn (pqueue->size == 0);
}int isFull(MinPriorityQueue pqueue)
{if (pqueue == NULL){PQueueNULLWarning();return -1;}elsereturn (pqueue->size == pqueue->capacity);
}void adjust(int *arr, int len, int i)
{int n = len - 1;int tmp;if(i * 2 >n)                       //叶子节点跳出return;if (i * 2 == n && arr[i] > arr[n]) // 只有左儿子的节点,并且左儿子比本节点的值要小,交换{tmp = arr[i];arr[i] = arr[n];arr[n] = tmp;}else // 有两个儿子的节点{if (arr[i * 2] > arr[i * 2 + 1]) // 右儿子较小{if (arr[i] > arr[i * 2 + 1]) // 如果本节点比右儿子大,交换{tmp = arr[i];arr[i] = arr[i * 2 + 1];arr[i * 2 + 1] = tmp;adjust(arr, len, i * 2 + 1);}}else // 左儿子较小{if (arr[i] > arr[i * 2]) // 如果本节点比左儿子大,交换{tmp = arr[i];arr[i] = arr[i * 2];arr[i * 2] = tmp;adjust(arr, len, i * 2 );}}}
}/*先将数组转化为堆,再进行调整,时间复杂度为O(N)正确的证明方法应当如下:
具有n个元素的平衡二叉树,树高为㏒n,我们设这个变量为h。
最下层非叶节点的元素,只需做一次线性运算便可以确定大根,而这一层具有2^(h-1)个元素,我们假定O(1)=1,那么这一层元素所需时间为2^(h-1) × 1。
由于是bottom-top建立堆,因此在调整上层元素的时候,并不需要同下层所有元素做比较,只需要同其中之一分支作比较,而作比较次数则是树的高度减去当前节点的高度。因此,第x层元素的计算量为2^(x-1) × (h-x)。
又以上通项公式可得知,构造树高为h的二叉堆的精确时间复杂度为:
S = 2^(h-1) × 1 + 2^(h-2) × 2 + …… +1 × (h-1) ①
通过观察第四步得出的公式可知,该求和公式为等差数列和等比数列的乘积,因此用错位想减发求解,给公式左右两侧同时乘以2,可知:
2S = 2^h × 1 + 2^(h-1) × 2+ …… +2 × (h-1) ②用②减去①可知: S =2^h × 1 - h +1 ③将h = ㏒n 带入③,得出如下结论:S = n - ㏒n +1 = O(n)*/MinPriorityQueue buildHeap_percolate(int *arr, int n)
{if (arr == NULL){printf("Error: Array is NULL");return NULL;}MinPriorityQueue pqueue;pqueue = ( MinPriorityQueue)malloc(sizeof(struct MinHeap));if (pqueue == NULL)outOfSpaceFatalError();ElementType *elements = ( ElementType *)malloc(sizeof(ElementType) * (n + 1));if (elements == NULL)outOfSpaceFatalError();int i;for (i = 1; i <= n; i++)elements[i] = arr[i - 1];elements[0] = SentinelElement;for (i = n / 2; i > 0; i--)adjust(elements, n + 1, i);pqueue->elements = elements;pqueue->size = n;pqueue->capacity = n * 2;return pqueue;
}/** 通过n次插入元素建立堆,由于每次插入的平均执行时间为O(logN),所以建堆平均时间为O(NlogN)*/
MinPriorityQueue buildHeap_insert(int *arr, int n)
{MinPriorityQueue pqueue;if (arr == NULL){printf("Array is NULL, fail to build heap");return NULL;}pqueue = initialize(n * 2);for (int i = 0; i < n; i++)insert(arr[i], pqueue);return pqueue;
}/**通过n次出队操作把其值输出到另一个堆里,时间复杂度O(NlogN)*/
MinPriorityQueue buildHeap_sort(MinPriorityQueue pqueue)
{MinPriorityQueue pqueuetemp;if (pqueue == NULL){PQueueNULLWarning();}int n=pqueue->size;pqueuetemp = ( MinPriorityQueue)malloc(sizeof(struct MinHeap));if (pqueuetemp == NULL)outOfSpaceFatalError();ElementType *elements = ( ElementType *)malloc(sizeof(ElementType) * (n + 1));if (elements == NULL)outOfSpaceFatalError();int i;for (i = 1; i <= n; i++)elements[i] = deleteMin(pqueue);elements[0] = SentinelElement;pqueuetemp->elements = elements;pqueuetemp->size = n;pqueuetemp->capacity = n * 2;return pqueuetemp;}void printMinPriorityQueue(MinPriorityQueue pqueue)
{if (pqueue == NULL){PQueueNULLWarning();return;}if (pqueue->elements == NULL){printf("Fail to print: Elements of priority queue is NULL");return;}if (isEmpty(pqueue)){printf("Empty Prioirty Queue\n");return;}printf("Priority Queue\n");for (int i = 1; i <= pqueue->size; i++)printf("Element[%d] = %d\n", i, pqueue->elements[i]);printf("\n");
}

main.cpp   主函数调用

#include <stdio.h>
#include <stdlib.h>
#include "MinHeap.h"int main(int argc, const char * argv[])
{int a[5] = {5, 4, 3, 2, 1};MinPriorityQueue pqueue_ins = buildHeap_insert(a, 5);printMinPriorityQueue(pqueue_ins);MinPriorityQueue pqueue_per = buildHeap_percolate(a, 5);printMinPriorityQueue(pqueue_per);MinPriorityQueue pqueue_sort=buildHeap_sort(pqueue_ins);printMinPriorityQueue(pqueue_sort);
/*deleteMin(pqueue_ins);printMinPriorityQueue(pqueue_ins);deleteMin(pqueue_ins);printMinPriorityQueue(pqueue_ins);insert(9, pqueue_ins);printMinPriorityQueue(pqueue_ins);insert(1, pqueue_ins);printMinPriorityQueue(pqueue_ins);*/system("pause");return 0;
}/*转载与http://www.2cto.com/kf/201404/293694.html*/

注:代码部分转载与http://www.2cto.com/kf/201404/293694.html,并对其代码做了一些修正与改进。

小顶堆数据结构C/C++代码实现相关推荐

  1. python树结构实现小顶堆_数据结构和算法入门之小顶堆和大顶堆Python实现

    首先简单提一下小顶堆和大顶堆,其本质是一颗完全二叉树,不同点在于:除叶子节点外,小顶堆的每个父节点的key都要比其左右两个子节点的key小:大顶堆的每个父节点的key都要比其左右两个子节点的key大. ...

  2. [数据结构]Python Heapq库--小顶堆

    一.heapq库简介 heapq 库是Python标准库之一,提供了构建小顶堆的方法和一些对小顶堆的基本操作方法(如入堆,出堆等),可以用于实现堆排序算法. 堆是一种基本的数据结构,堆的结构是一棵完全 ...

  3. 数据结构之堆——C++实现大顶堆和小顶堆

    大小顶堆的实现 什么是大顶堆和小顶堆 大小顶堆的底层实现 代码实现小顶堆 定义小顶堆类 构造函数 插入 扩大堆数组容量 删除 析构函数 代码实现大顶堆 测试 什么是大顶堆和小顶堆 堆是一种完全二叉树. ...

  4. 数据结构 小顶堆建堆过程 构建过程

    [一]简介 最小堆是一棵完全二叉树,非叶子结点的值不大于左孩子和右孩子的值.本文以图解的方式,说明最小堆的构建.插入.删除的过程.搞懂最小堆的相应知识后,最大堆与此类似. 最小堆示例: [二]最小堆的 ...

  5. 堆排序之 大顶堆和小顶堆 c语言

    百度得到的堆定义如下: 堆的定义如下:n个元素的序列{k1,k2,ki,-,kn}当且仅当满足下关系时,称之为堆. (ki <= k2i,ki <= k2i+1)或者(ki >= k ...

  6. 谈谈堆排序,大顶堆,小顶堆

    目录 1.前言 2.使用堆的原因 3.堆的特点 4.堆和普通树的区别 5.堆排序的过程 6.堆排序的代码实现 来源: jianshu.com/p/15a29c0ace73 1.前言 堆是一种非线性结构 ...

  7. 海量数据处理的 Top K算法(问题) 小顶堆实现

    问题描述:有N(N>>10000)个整数,求出其中的前K个最大的数.(称作Top k或者Top 10) 问题分析:由于(1)输入的大量数据:(2)只要前K个,对整个输入数据的保存和排序是相 ...

  8. Java堆排序(大顶堆小顶堆及应用实例)

    自己理解所得,如有错误欢迎随时指摘: 目录: 堆概念 堆结构 堆排序步骤 大顶堆代码.小顶堆代码 实际应用及实例代码 小顶堆删除图解代码.插入代码 小顶堆插入图解 时间复杂度分析 1.百度->概 ...

  9. C++大顶堆和小顶堆

    C++大顶堆和小顶堆 原理 大顶堆 小顶堆 大顶堆和小顶堆对比图 大顶堆和小顶堆的实现代码 vector和push_heap.pop_heap实现堆 建堆 调整堆 priority_queue实现堆 ...

最新文章

  1. 二维“玄”如何“抖动”出三维世界?
  2. 云安全课程:云平台使用安全
  3. vue+element-ui实现表格的增删改查
  4. Go之Beego报错RegisterModel must be run before BootStrap
  5. 什么是跨域?什么是CSRF?
  6. pycharm写python字典_pythonpycharm安装基础语法
  7. MySQL 亿级数据需求的优化思路(二),100亿数据,1万字段属性的秒级检索
  8. 日志文件和mysql同步到kafka_logstash_output_kafka:Mysql 同步 Kafka 深入详解
  9. centos7安装python3.8_centos7 安装python3.8
  10. 数据中心的重要服务器如何保护?
  11. Linux内核快速处理路径尽量多用slab而慎用kmalloc
  12. Spring揭秘(一)spring框架的由来
  13. typedef用法总结
  14. 计算机 科研进度安排,研究计划进度安排及预期目标-浙江大学现代教务管理系统.doc...
  15. java获取小数位数_Java获取小数位数 | 学步园
  16. php合成flv,用php将任何格式视频转为flv
  17. LOJ10144宠物收养所
  18. 【Pandas学习笔记Task05】:变形
  19. 命运被转折改变--掌握java高性能分布式服务和海量大数据技术体系(第二期)
  20. 地铁框架保护的原理_浅析地铁直流框架保护原理及应急处置

热门文章

  1. InceptionNet V3整理总结
  2. model 提交表信息java,Angular6+antd+java+SpringMVC,表单提交自动接收模型对象?
  3. pycharm console日志如何输出到txt_日志记录——logging模块
  4. python版本年份_Python问题:至今的年份和年份?
  5. VS2019配置opencv详细图文教程和测试代码
  6. 学习opencv之cvtColor
  7. Android-Socket的最基础实现以及遇见在2.3可用4.3不可用的解决方法
  8. console.log打印值,颜色 - 解决篇
  9. phpcms内容页 调用 上一级栏目id,catname等信息 - 代码篇
  10. 萝卜视频源码前后端带视频演示更换播放内核到3.2.6