系列文章目录
数据结构——顺序表的C语言代码实现
数据结构——八种链表的C语言代码实现
数据结构——栈的C语言代码实现
数据结构——队列的C语言代码实现
数据结构——堆的C语言代码实现
前言
本文主要学习如何实现最大队和最小堆的创建、插入、删除等。
一、堆的概念
堆是考虑特权的数据结构,被称作优先队列
在前面学习队列时,我们曾举例:在银行中排队处理业务,来解释队列“先进先出”的特点。
而在学习堆时,我们可以联想打印店排队:某一个用户需要打印数百页的资料,而排在他后面的顾客只需打印一两页的试题,那么此时店主和前客户一般都会同意优先处理后客户的需求。
堆是特殊的队列,最常用结构是完全二叉树又因为完全二叉树必的节点规律性极强,故常用数组实现堆的存储。
对于下标为i的节点,其父节点下标为i/2,其左右孩子节点下标为2i、2I+1
二、代码实现
1.Heap.h
主要实现创建、插入、删除和打印。最大堆与最小堆的接口函数一致。
注意在用数组存储堆时,我们使用的数组下标是从1开始的,空余的下标0用于存放规定的最大值或者最小值,便于插入时进行操作。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#define MAX 10000000
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Heap
{
DataType* arr;
int size;
int capacity;
}*Heap;
//创建堆
Heap CreateHeap(int maxsize);
//检测是否已满
bool CheckFull(Heap h);
//检测是否已空
bool CheckEmpty(Heap h);
//插入
void Insert(Heap h, DataType x);
//删除
DataType Delete(Heap h);
//打印
void Print(Heap h);
2.Heap.c
最大堆与最小堆的接口函数实现大同小异。重点注意实现插入与删除时。使用的过滤方法!
(1)创建堆
因为从下标1开始存储,所以申请空间时,要申请maxsize+1个空间
代码如下:
//创建堆
Heap CreateHeap(int maxsize)
{
Heap h = (Heap)malloc(sizeof(struct Heap));
h->arr = (DataType*)malloc(sizeof(DataType) * (maxsize + 1));
h->capacity = maxsize;
h->size = 0;
//数组从下标1开始存放数据,arr[0]存放最大值,
//便于插入时进行过滤操作
h->arr[0] = MAX;
return h;
}
(2)检测是否已满
代码如下:
//检测是否已满
bool CheckFull(Heap h)
{
return h->size == h->capacity;
}
(3)检测是否已空
代码如下:
//检测是否已空
bool CheckEmpty(Heap h)
{
return h->size == 0;
}
(4)插入
主要思路:
1、先判断数组是否已满
2、将数组的size加一赋值为i,然后从数组最后一个下标i开始,依次用父节点(下标为2/i)与插入的数X比较大小
3、在最大堆中,如果父节点小于X,则将父节点下移到孩子节点,然后将i迭代,继续上述比较;在最小堆中,如果父节点大于X,则将父节点移到孩子节点,然后将i迭代,继续上述操作;
4、最终当判断条件结果为假时,X就找到了要插入的位置下标。
代码如下:
//最大堆插入
void Insert(Heap h, DataType x)
{
if (CheckFull(h))
{
printf("最大堆已满!\n");
return;
}
else
{
//最大堆插入元素后,最后的数据位置下标
int i = ++h->size;
//从最后一个位置向上过滤父节点
//如果X大于父节点,则父节点下移
for (; h->arr[i / 2] < x; i /= 2)
{
h->arr[i] = h->arr[i / 2];
}
//当X小于父节点,此时i就是要插入的位置
h->arr[i] = x;
}
}
//最小堆的插入
void Insert(Heap h, DataType x)
{
if (CheckFull(h))
{
printf("\n");
return;
}
else
{
int i = ++h->size;
for (; h->arr[i / 2] > x; i /= 2)
{
h->arr[i] = h->arr[i / 2];
}
h->arr[i] = x;
}
}
(5)删除
主要思路:
1、先判断数组是否已空;
2、取出最大值或者最小值(下标为1的数组元素);
3、然后删除最后一个元素的空间,将原数组的最后一个元素num从上到下,与兄弟节点中较大的进行比较,找到要插入的位置;
即先从下标为1开始,它的两个孩子下标分别为2、3,先找到较大的孩子节点。假设是下标为2,用下标为2的元素与num相比。在最大堆中,如果num小,则将下标为1的元素赋值为下表为2的元素(节点上移),然后迭代继续比较,直到num较大,此时的父节点就是X要插入的位置。而在最小堆中,就是比较的结果恰恰相反。
代码如下:
//最大堆删除
DataType Delete(Heap h)
{
if (CheckEmpty(h))
{
printf("最大堆已空!\n");
exit(-1);
}
else
{
//取出最大元素
int Max = h->arr[1];
//用最后一个节点元素从上向下过滤
int num = h->arr[h->size--];
int parent, child;
for (parent = 1; 2 * parent <= h->size; parent = child)
{
child = parent * 2;
//找到两个孩子中较大的一个
if (child != h->size && h->arr[child + 1] > h->arr[child])
child += 1;
//最后一个节点大于孩子节点,则找到了正确位置
//最后一个节点插入到父节点
if (num >= h->arr[child])
break;
else
h->arr[parent] = h->arr[child];
}
h->arr[parent] = num;
return Max;
}
}
//最小堆删除
DataType Delete(Heap h)
{
if (CheckEmpty(h))
{
printf("ѿ!\n");
exit(-1);
}
else
{
DataType min = h->arr[1];
DataType num = h->arr[h->size--];
int parent, child;
for (parent = 1; parent * 2 <= h->size; parent = child)
{
child = parent * 2;
if (child != h->size && h->arr[child] > h->arr[child + 1])
child += 1;
if (h->arr[child] >= num)
break;
else
h->arr[parent] = h->arr[child];
}
h->arr[parent] = num;
return min;
}
}
(6)打印
代买如下:
//打印
void Print(Heap h)
{
int i = 1;
for (; i <= h->size; i++)
{
printf("%d ", h->arr[i]);
}
}
3.test.c
最大堆:
#include"MaxHeap.h"
int main()
{
int maxsize = 100;
Heap h = CreateMaxHeap(maxsize);
int input = 0;
int count = 0;
while ((scanf("%d", &input) == 1)&&count<=maxsize)
{
Insert(h, input);
count++;
}
printf("最大堆为:\n");
Print(h);
int max = Delete(h);
printf("\n最大值是:%d\n", max);
printf("最大堆为:\n");
Print(h);
return 0;
}
最小堆:
#include"Heap.h"
int main()
{
int maxsize = 100;
Heap h = CreateHeap(maxsize);
int count = 0;
int input = 0;
while (scanf("%d", &input) == 1 && count <= maxsize)
{
Insert(h, input);
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("最小堆是:\n");
Print(h);
int min = Delete(h);
printf("\n最小值是:%d\n", min);
printf("最小堆是:\n");
Print(h);
}
return 0;
}
总结
多分析代码,理解逐层过滤的方便之处。