堆(英语:heap)通常是一个可以被看做一棵树的数组对象。
堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)
若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)
如下图示:
小堆(大堆)中:任一结点的关键码均小于(大于)等于它的左右孩子的关键
码,位于堆顶结点的关键码最小(最大),从根节点到每个结点的路径上数
组元素组成的序列都是递增(递减)的
- 创建堆
void AdjustDownBig(DataType* a, size_t n, int root)//向下调整
{
size_t parent = root;
size_t child = 2 * parent + 1;//孩子节点
while (child < n)
{
if (a[child] < a[child + 1] && child + 1 < n)//取孩子节点最较大值
{
child++;
}
if (a[child] > a[parent])//孩子大于父亲,交换值
{
DataType tmp = a[child];
a[child] = a[parent];
a[parent] = tmp;
parent = child;//继续向下调整
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void MakeHeap(DataType* a, size_t n)//创建大堆
{
int i = (n - 2) >> 1;//从最后一个叶子节点的父亲节点开始调整
for (; i >= 0; i--)
{
AdjustDownBig(a, n, i);//i代表父亲节点,n代表调整节点的个数
}
}
2.100亿个数中找出最大的前K个数(海量数据top K问题)
void AdjustDownSmall(DataType* a, size_t n, int root)//向下调整小堆的创建
{
size_t parent = root;
size_t child = 2 * parent + 1;
while (child < n)
{
if (a[child] > a[child + 1] && child + 1 < n)
{
child++;
}
if (a[child] < a[parent])
{
DataType tmp = a[child];
a[child] = a[parent];
a[parent] = tmp;
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void TopK(DataType *a, size_t n, size_t k)//简意:取n中最大的前k个
{
int i = (k - 2) >> 1;
size_t _k = k;
for (; i >= 0; i--)//先创建前k个大小的小堆
{
AdjustDownSmall(a, k, i);
}
for (i = k; i < n; i++)//从第k+1个就是下标为k的数开始与小堆中根比较
{
if (a[i] > a[0])//大于根,替换它,并且向下调整,循环往复堆中就是n个数中最大的前k个
{
a[0] = a[i];
AdjustDownSmall(a, k, 0);
}
}
for (i = 0; i < k; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
- 堆排序
定义:利用堆进行对一组无序数进行排序
时间复杂度O(n*logn)
void HeapSort(DataType* a, size_t n)//堆排序
{
MakeHeap(a, n);//建大堆
for (int i = n ; i > 0; i--)
{
int tmp = a[i - 1];
a[i - 1] = a[0];
a[0] = tmp;
AdjustDownBig(a, i - 1, 0);
}
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
- 优先级队列
void PriorityQueueInit(PryQueue* q)//初始化
{
assert(q);
q->_size = 0;
for (int i = 0; i<1000; i++)
{
q->_a[i] = 0;
}
}
void AdjustUp(DataType* a, size_t n, int child)//向上调整
{
size_t parent = (child - 1) >> 1;
while (child > 0)
{
if (a[child] < a[parent])
{
DataType tmp = a[child];
a[child] = a[parent];
a[parent] = tmp;
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
void PriorityQueuePush(PryQueue* q, DataType x)//插入
{
if (q->_size == 0)
{
q->_a[q->_size] = x;
q->_size++;
}
else
{
q->_a[q->_size] = x;
q->_size++;
AdjustUp(q->_a, q->_size, q->_size - 1);//向上调整,从叶子节点开始,保持大堆
}
}
void PriorityQueuePop(PryQueue* q)//删除
{
assert(q);
q->_a[0] = q->_a[q->_size - 1];//最后一个节点替换根节点,并且节点个数减1,再向下调整
q->_size--;
AdjustDownSmall(q->_a,q->_size,0);
}
DataType PriorityQueueTop(PryQueue* q)//取最大的
{
assert(q);
return q->_a[0];//返回根节点
}
size_t PriorityQueueSize(PryQueue* q)//返回个数
{
assert(q);
return q->_size;
}
size_t PriorityQueueEmpty(PryQueue* q)//对列是否为空
{
assert(q);
return q->_size == 0 ? 0 : q->_size;
}
测试优先级队列:
void TestPriorityQueue()
{
PryQueue q;
PriorityQueueInit(&q);
PriorityQueuePush(&q, 5);
PriorityQueuePush(&q, 2);
PriorityQueuePush(&q, 3);
PriorityQueuePush(&q, 7);
PriorityQueuePush(&q, 6);
PriorityQueuePush(&q, 1);
PriorityQueuePush(&q, 4);
size_t size = PriorityQueueSize(&q);
while (PriorityQueueEmpty(&q) != 0)
{
printf("%d ", PriorityQueueTop(&q));//打印堆的根后,再删堆的根。
PriorityQueuePop(&q);
}
printf("\n");
}
测试结果:
需要源代码请戳这里GitHub