堆
●将完全二叉树的结构特点套用在一个一维数组上,堆的实质是一个一维数组。
什么样的一维数组才称为堆?
●将数组中的元素按照完全二叉树的方式排列起来(如图):
使其满足任一结点的数值均小于(大于)等于它的左右孩子结点的数值,位于堆顶结点的数值最小(最大),从根结点到每个结点的路径上数组元素组成的序列都是递增(递减)的
就称这个一维数组为堆。
●若位于堆顶结点的数值最大,则为大堆
●若位于堆顶结点的数值最小,则为小堆
堆存储与下标为0开始的一维数组中,因此在堆中给定下标为i的结点时:
●如果i=0,则结点i是根结点,没有双亲结点;否则结点i的双亲结点为结点(i-1)/2
●如果2*i+1<=n-1,则结点i的左孩子为结点2*i+1,否则结点i无左孩子
●如果2*i+2<=n-1,则结点i的右孩子为结点2*i+2,否则结点i无右孩子
堆的创建
给出堆的结构体:
#define MAXSIZE 50
typedef int(*Compare)(HDataType, HDataType);//函数指针,用来确定要创建大堆还是小堆
typedef int HDataType;
typedef struct Heap
{
HDataType* _array;//存放数据的一维数组
int _capacity;//容量
int _size;//已经使用的大小
Compare _com;//创建大堆还是小堆
}Heap;
●首先要以完全二叉树的结构套用所给定的一维数组,0号位置为根结点,1为根结点的左孩子,2为根结点的右孩子……
●将二叉树调整为最小(大)堆的原理:
从最后一个非叶子结点开始调整,一直到根结点为止,将每个结点及其子树调整到满足最小堆的性质即可
int Less(HDataType Left, HDataType Right)//若要创建小堆,则就将此函数的地址(函数名)作为参数传递给创建堆的函数
{
return Left < Right;
}
int Greater(HDataType Left, HDataType Right)//若要创建大堆,则就将此函数的地址(函数名)作为参数传递给创建堆的函数
{
return Left>Right;
}
//创建堆
void CreateHeap(Heap* hp, HDataType* array, int size, Compare com)
{
int i = 0;
int Root = 0;
if (NULL == hp)
{
assert(0);
return;
}
//指明堆的调整方式(大堆/小堆)
hp->_com = com;
//给堆开辟空间
hp->_capacity = MAXSIZE;
hp->_array = (HDataType*)malloc(sizeof(HDataType)*hp->_capacity);
if (NULL == hp->_array)
{
assert(0);
return;
}
//将数组赋给堆
hp->_size = size;
for (i = size - 1; i >= 0; i--)
{
hp->_array[i] = array[i];
}
//com指明要创建一个小堆
//整理堆
Root = (size - 2) / 2;
for (; Root >= 0; --Root)
{
_AdjustDown(hp, Root);
}
}
int Less(HDataType Left, HDataType Right)
{
return Left < Right;
}
int Greater(HDataType Left, HDataType Right)
{
return Left>Right;
}
//小/大堆的向下调整函数(向下调整法)
void _AdjustDown(Heap* hp, int Root)
{
int Parent = 0;
int Child = 0;
Parent = Root;
Child = Parent * 2 + 1;
while (Child < hp->_size)
{
//判断他的右孩子是否存在
if (Child + 1 <= hp->_size - 1)
{
//找出两个孩子中较小的一个
if (Child+1<hp->_size && hp->_com(hp->_array[Child + 1] , hp->_array[Child]))
Child += 1;
}
//较小的孩子与双亲节点比较,如果双亲节点较大就交换
if (hp->_com(hp->_array[Child], hp->_array[Parent]))
{
Swap(&hp->_array[Parent], &hp->_array[Child]);
}
Parent = Child;
Child = Parent * 2 + 1;
}
}
给堆中插入元素
●检测堆中是否还有可用空间,若无则需给堆增容
●将要插入的数据作为一个叶子节点,插在堆的末尾
●从插入的结点的双亲结点开始向上调整堆,直至根结点
//在堆中插入一个元素
void InsertHeap(Heap* hp, HDataType data)
{
int Parent = 0;
int Child = 0;
assert(hp);
if (hp->_size >= hp->_capacity)
return;
//插入时,首先要检测堆中是否还有可用空间
_CheckCapacity(hp);
//将要插入的数据连接在堆的末尾
hp->_array[hp->_size] = data;
hp->_size++;
//使用向上调整法调整堆
Child = hp->_size - 1;
while (Child > 0)
{
_AdjustUp(hp, Child);
Child = ((Child - 1) >> 1);
}
}
//向上调整法
void _AdjustUp(Heap* hp, int Root)
{
int Parent = 0;
int Child = 0;
//让child指向新插入的指针
Child = Root;
Parent = ((Child - 1) >> 1);
//调整堆
while (0 <= Parent)
{
if (hp->_com(hp->_array[Child], hp->_array[Parent]))
{
Swap(&hp->_array[Child], &hp->_array[Parent]);
}
Child = Parent;
Parent = ((Parent - 2) >> 1);
}
}
//检测堆中是否还有可用空间
void _CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->_capacity == hp->_size)
{
realloc(hp->_array, sizeof(HDataType)*(hp->_capacity * 2));//增容时,默认将容量增加到原容量的两倍
if (NULL == hp->_array)
{
assert(0);
return;
}
}
}
删除堆顶元素
●用堆的最后一个叶子结点覆盖堆顶结点
●释放堆的最后一个叶子结点
●重新进行堆调整
//删除堆顶元素
//直接删除堆顶的元素,然后把堆中最末尾的元素放在堆顶,再进行堆调整
void DeleteHeapTop(Heap* hp)
{
assert(hp);
if (EmptyHeap(hp))
{
assert(0);
return;
}
//用堆末尾的元素覆盖堆顶的元素
hp->_array[0] = hp->_array[hp->_size - 1];
hp->_size--;
//进行堆调整
_AdjustDown(hp, 0);//因为其他位置原先已经是调整好的堆,改动了根结点,所以只需从根结点开始向下调整
}
销毁堆
●释放所开辟的空间
●销毁所有数值
//销毁堆
void DestroyHeap(Heap* hp)
{
assert(hp);
free(hp->_array);
hp->_array = NULL;
hp->_capacity = 0;
hp->_size = 0;
hp->_com = NULL;
}
堆的应用
堆排序
●先将数据调整为一个小(大)堆
●交换堆顶元素与堆末尾的元素(将最小(大)的数据放置在堆尾)
●将堆的size-1,即将堆尾的元素不在视为堆的一份子
●循环此过程,直至堆中只剩下一个元素
//堆排序
//1.先交换堆顶元素与末尾元素(即已经把最大的元素放在末尾)
//2.下次调整就不包括堆中的最后一个元素
void SortHeap(Heap* hp)
{
int Parent = 0;
int Child = 0;
int size = 0;
assert(hp);
size = hp->_size;
while (hp->_size > 0)
{
//调整堆
Parent = (hp->_size - 2) / 2;
Child = Parent * 2 + 1;
while (0 <= Parent)
{
_AdjustDown(hp, Parent);
Parent = ((Parent - 1) >> 1);
}
//PrintHeap(*hp);
//交换堆顶元素和末尾元素
Swap(&hp->_array[0], &hp->_array[hp->_size - 1]); //吐出最后一个元素 --(hp->_size); } hp->_size = size; } //交换节点所存储的值 void Swap(HDataType* first, HDataType* second) { HDataType tmp = 0; tmp = *first; *first = *second; *second = tmp; }
用堆解决Top-K问题
eg:100亿个数据中找出最大的前K个数
●用数据的前10个元素创建一个小堆
●遍历剩下的元素,若大于堆顶元素,则用它覆盖原本的堆顶元素,再调整堆,若小于,则遍历下一个元素,当遍历完成时,堆中的所有元素即就是所要求的元素
//用堆解决Top_k问题
//先用数据的前K个元素建立一个小堆
//剩余的数据与堆顶元素比较
//如果大于堆顶元素就覆盖堆顶元素然后重新调整堆
//如果小于就继续查看下一个元素
//循环此过程直至数据遍历完成
void TopKByHeap(HDataType* array, int K, int size)
{
int i = 0;
//初始化堆
Heap hp;
InitHeap(&hp, Less);
//建立有K个节点的小堆
while (hp._size < K)
{
InsertHeap(&hp, array[i]);
i++;
}
//调整后续元素
while (i < size)
{
//如果后续元素大于堆顶元素
if (array[i]>hp._array[0])
{
//覆盖堆顶元素
hp._array[0] = array[i];
//调整堆
_AdjustDown(&hp, 0);
}
i++;
}
printf("Top-K:");
PrintHeap(hp);//打印堆
}
优先级队列
优先级队列是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。
//堆的初始化
void InitHeap(Heap* hp, Compare com)
{
assert(hp);
hp->_array = NULL;
hp->_size = 0;
hp->_capacity = MAXSIZE;
hp->_array = (HDataType*)malloc(sizeof(HDataType)*hp->_capacity);
hp->_com = com;
}
//优先级队列的初始化
void PriorityQueueInit(PriorityQueue* q, Compare com)
{
assert(q);
//初始化优先级队列中的堆
InitHeap(&q->_hp, com);
}
//在堆中插入一个元素
void InsertHeap(Heap* hp, HDataType data)
{
int Parent = 0;
int Child = 0;
assert(hp);
if (hp->_size >= hp->_capacity)
return;
//插入时,首先要检测堆中是否还有可用空间
_CheckCapacity(hp);
//将要插入的数据连接在堆的末尾
hp->_array[hp->_size] = data;
hp->_size++;
//使用向上调整法调整堆
Child = hp->_size - 1;
while (Child > 0)
{
_AdjustUp(hp, Child);
Child = ((Child - 1) >> 1);
}
}
//向队列中插入元素
void PriorityQueuePush(PriorityQueue* q, HDataType data)
{
assert(q);
InsertHeap(&q->_hp, data);
}
//直接删除堆顶的元素,然后把堆中最末尾的元素放在堆顶,再进行堆调整
void DeleteHeapTop(Heap* hp)
{
assert(hp);
if (EmptyHeap(hp))
{
assert(0);
return;
}
//用堆末尾的元素覆盖堆顶的元素
hp->_array[0] = hp->_array[hp->_size - 1];
hp->_size--;
//进行堆调整
_AdjustDown(hp, 0);
}
//删除优先级最高的元素
void PriorityQueuePop(PriorityQueue* q)
{
assert(q);
DeleteHeapTop(&q->_hp);
}
//获取堆中元素个数
int SizeHeap(Heap* hp)
{
assert(hp);
return hp->_size;
}
//获取优先级队列元素个数
int PriorityQueueSize(PriorityQueue* q)
{
assert(q);
return SizeHeap(&q->_hp);
}
//检测一个堆是否为空堆
int EmptyHeap(Heap* hp)
{
assert(hp);
return 0 == hp->_size;
}
//检测优先级队列是否为空
int PriorityEmpty(PriorityQueue* q)
{
assert(q);
return EmptyHeap(&q->_hp);
}
//获取优先级队列的堆顶元素
HDataType PriorityQueueTop(PriorityQueue* q)
{
assert(q);
return (q->_hp)._array[0];
}
//销毁堆
void DestroyHeap(Heap* hp)
{
assert(hp);
free(hp->_array);
hp->_array = NULL;
hp->_capacity = 0;
hp->_size = 0;
hp->_com = NULL;
}
//销毁优先级队列
void PriorityQueueDestroy(PriorityQueue* q)
{
assert(q);
DestroyHeap(&q->_hp);
}
完