目录
一,堆的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们把堆(一种二叉树)使用顺序结构的数组来存储,需要注意是是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
二,堆的概念及结构
如果有一个关键码的集合K = { k0,k1 ,k2 ,...,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: kn<=k2*n 且 kn<=k2n+2 (kn >=k2*n+1 且 kn>=k2*n+2 ) i = 0,1, 2...,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
三,堆的实现
因为堆分为大根堆和小根堆,结构不一样,但思路相同,这里我们以大根堆为例。
3.1堆的结构
这里用数组来实现,然后结构体中包含下标,容量。容量不够可以扩容。
cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* a; //数组存储数据
int size; //最后一个元素的下标
int capacity;//容量
}Heap;
3.2堆的初始化
这里申请一块数组的地址,申请失败则打印错误返回,然后将堆的指针指向数组的地址,将容量初始化为申请地址的大小,里面没有数据,将size置为0;
cpp
void heapInit(Heap* pa)
{
HeapDataType* tmp = (HeapDataType*)malloc(sizeof(HeapDataType)*4);
if (tmp == NULL)
{
printf("malloc fail");
return;
}
pa->a = tmp;
pa->capacity = 4;
pa->size = 0;
}
3.3堆的插入数据
这里涉及一个向上调整算法,它前提是数据的上面结点都为堆,然后插入大于2或者等于2后的数据都要与前面比较,如果是大根堆儿子比父亲大就交换(小根堆儿子节点比父亲小交换)数据,然后重复向上走,直到满足大根堆中父节点都比子节点大(小根堆父节点都比子节点小)后停止跳出循环。
cpp
void swap(HeapDataType* p1, HeapDataType* p2)//两个数据互换
{
HeapDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HeapDataType *a,int child)//child子节点的数组元素下标
{
int parent = (child - 1) / 2;//二叉树中儿子结点与父亲结点的关系:父=(子-1)/2
while (parent>=0)
{
//if(a[child]<a[parent])//小根堆
if (a[child] > a[parent])//大根堆
{
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
有了上面的向上调整算法,直接用在堆的插入中来建大根堆(小根堆)。先断言一下判断pa是否为空,不为空,继续执行。接着比较容量是否需要扩容,然后将数据插入进去,每进去一个数据都需要向上调整,以保证为大根堆(小根堆)。
cpp
void heapPush(Heap* pa, HeapDataType x)
{
assert(pa);
if (pa->capacity == pa->size)
{
HeapDataType* tmp = (HeapDataType*)realloc(pa->a, sizeof(HeapDataType) * pa->capacity * 2);
pa->a = tmp;
pa->capacity *= 2;
}
pa->a[pa->size] = x;
pa->size++;
AdjustUp(pa->a, pa->size - 1);
}
3.3堆的删除数据
堆的删除数据不能直接将数组的第一个数据删除,否则堆的结构顺序将发生改变。这里我们可以将数组第一个数据与数组最后一个数据交换,然后将最后一个数据删除,如果在大根堆中此时第一个数据将比下面的小,但左右子树的结构不变(还是大根堆),这时候就从第一个数据开始想下调整,这里就涉及到向下调整的算法了,向下调整的算法前提是左右子树都是堆,这里先找到左右子结点中最大的结点(最小的结点),然后与子结点进行比较,如果符合条件就进行交换,不断重复上述过程,然后当子结点的数组下标大于等于数组的元素个数就结束。
cpp
void swap(HeapDataType* p1, HeapDataType* p2)//两个数据交换
{
HeapDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustDown(HeapDataType* a,int size,int parent)//向下调整
{
int child = 2 * parent + 1;//二叉树中儿子结点与父亲结点的关系:父=(子-1)/2
while (child < size)
{
if (child + 1 <= size && a[child] < a[child + 1])//child+1 元素的个数
{
child++;
}
if (a[child] > a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void heapPop(Heap* pa)
{
assert(pa);//断言pa是否为空
assert(!heapEmpty(pa));//断言pa中是否有数据
swap(&pa->a[0],& pa->a[pa->size - 1]);//交换第一个与最后一个数据
pa->size--;//删除的第一个数据(现在位于数组最后一个位置)
AdjustDown(pa->a,pa->size-1,0);//pa->size 元素的个数,现在将位于数组第一个位置的数据向下调整
}
3.4堆的取顶数据,返回堆数据大小,判断非空
这些根据堆的结构中的size可以轻易取出,与之前一样,不再过多阐述。
cpp
int heapEmpty(Heap* pa)//判断非空
{
assert(pa);
return pa->size == 0;
}
HeapDataType heapTop(Heap* pa)//取顶数据
{
assert(pa);
return pa->a[0];
}
int heapSize(Heap* pa)//取堆的数据大小
{
assert(pa);
return pa->size;
}
3.5堆的销毁
这里与其他销毁完全一样,不再叙述。
cpp
void heapDestory(Heap* pa)
{
assert(pa);
free(pa->a);
pa->size = 0;
pa->capacity = 0;
}
四,总代码
cpp
//heap.h
//存放头文件,接口声明
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* a;
int size;
int capacity;
}Heap;
//以大根堆为例
void heapInit(Heap* pa);
void heapPush(Heap* pa,HeapDataType x);
void heapPop(Heap* pa);
int heapEmpty(Heap* pa);
HeapDataType heapTop(Heap* pa);
int heapSize(Heap* pa);
void heapDestory(Heap* pa);
//heap.c
//这里存放堆接口的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"heap.h"
void swap(HeapDataType* p1, HeapDataType* p2)
{
HeapDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustDown(HeapDataType* a,int size,int parent)
{
int child = 2 * parent + 1;
while (child < size)
{
if (child + 1 <= size && a[child] < a[child + 1])//child+1 元素的个数
{
child++;
}
if (a[child] > a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void AdjustUp(HeapDataType *a,int child)
{
int parent = (child - 1) / 2;
while (parent>=0)
{
if (a[child] > a[parent])
{
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void heapInit(Heap* pa)
{
HeapDataType* tmp = (HeapDataType*)malloc(sizeof(HeapDataType)*4);
if (tmp == NULL)
{
printf("malloc fail");
return;
}
pa->a = tmp;
pa->capacity = 4;
pa->size = 0;
}
void heapPush(Heap* pa, HeapDataType x)
{
assert(pa);
if (pa->capacity == pa->size)
{
HeapDataType* tmp = (HeapDataType*)realloc(pa->a, sizeof(HeapDataType) * pa->capacity * 2);
pa->a = tmp;
pa->capacity *= 2;
}
pa->a[pa->size] = x;
pa->size++;
AdjustUp(pa->a, pa->size - 1);
}
void heapPop(Heap* pa)
{
assert(pa);
assert(!heapEmpty(pa));
swap(&pa->a[0],& pa->a[pa->size - 1]);
pa->size--;
AdjustDown(pa->a,pa->size-1,0);//pa->size 元素的个数
}
int heapEmpty(Heap* pa)
{
assert(pa);
return pa->size == 0;
}
HeapDataType heapTop(Heap* pa)
{
assert(pa);
return pa->a[0];
}
int heapSize(Heap* pa)
{
assert(pa);
return pa->size;
}
void heapDestory(Heap* pa)
{
assert(pa);
free(pa->a);
pa->size = 0;
pa->capacity = 0;
}
//test.c
//这里存放堆的测试代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"heap.h"
void testpush()
{
Heap hp;
heapInit(&hp);
heapPush(&hp, 12);
heapPush(&hp, 21);
heapPush(&hp, 14);
heapPush(&hp, 51);
heapPush(&hp, 11);
heapPush(&hp, 23);
while (!heapEmpty(&hp))
{
printf("%d\n", heapTop(&hp));
heapPop(&hp);
}
}
int main()
{
testpush();
return;
}
好了,到这里就结束了,下一篇来个效率更高的排序。