基本概念
定义:一个满足某种特定关系的序列{k1,k2,…,kn}
堆实际上是一个序列,因此堆的存储结构一般为数组。
进一步来看,堆本质上是一个完全二叉树的顺序存储,结合堆的数学性质,这个二叉树的每个父亲结点大于等于(小于等于)其孩子结点。
堆的操作
基本操作:pop、push and Init
堆的性质决定,堆顶元素为整个堆的最值,因此对堆顶的元素研究才有意义。从外部看,堆的基本操作为:push( ),pop( )。
pop( )操作说明:将入堆元素插入到最后,然后自该结点向上调整结点的位置( 保证每个父亲结点大于等于(小于等于)其孩子结点 )。
void HeapPush(HP* php, HPDataType x) //小堆为例
{
assert(php);
if (php->size >= php->capacity) //动态扩容
{
size_t newcapacity = php->capacity + 5;
HPDataType* t = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->size);
if (t == NULL)
{
printf("errno\n");
exit(-1);
}
php->a = t;
php->capacity = newcapacity;
}
php->a[(php->size)++] = x;
//向上调整
AdjustUp(php->a, php->size - 1);
}
pop( )操作说明:实际上pop的是堆顶元素。错误的做法是:直接删除堆顶元素,后续元素补上(会破环堆的整体结构)。正确做法是,将堆顶元素与末尾元素交换,堆长度减 1,然后向下调整堆顶结点的位置。
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
堆是个整体的概念,任意位置元素都需要保证它的正确性,对元素的变动会破坏它的正确性,需通过调整操作来恢复其正确性,因此操作时要尽量只改变单个(少量)结点,保证堆的整体正确性
Init( )操作说明:将一个序列通过调整变为堆。
- 可自顶向下地向上调整每一个结点,也可以自下向上地向下调整每一个结点。(注意区分这里的向上和向下、向上建堆和向上调整)
- 向下建堆时间复杂度为O(n*logn),向上建堆时间复杂度为O(n)。
void HeapInit(HP* php, HPDataType* a, int k)
{
assert(php && a);
php->a = (HPDataType*)malloc(sizeof(HPDataType) * k);
php->size = k;
php->capacity = k;
memcpy(php->a, a, sizeof(HPDataType) * k);
//向下 O(n*logn)
//for (int i = 0; i < n; i++)
//{
// AdjustUp(a, i);
//}
//向上 O(n)
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶子结点开始
{
AdjustDown(a, n, i);
}
}
//该函数并不是建堆通用函数,重点是其中的思想。
核心操作:调整
刚才两个算法包含 向上调整AdjustUp( )和向下调整AdjustDown( ) 函数。
//自该结点向上调整
void AdjustUp(HPDataType* a, size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
//自该结点向下调整
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
child = child + 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
堆排序
堆排序说明: 建堆后,每次将堆顶元素(最值)与末尾元素交换,然后缩减堆大小,调整堆顶元素,循环改过程。
一般来说小堆排降序,大堆排升序,但也可以逆转序列来解决这个问题
void HeapSort(int* a, int n)
{
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶子结点开始
{
AdjustDown(a, n, i);
}
//注意:升序用小堆,降序用大堆
size_t end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
完整代码
#include<stdio.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap {
HPDataType* a;
size_t size;
size_t capacity;
}HP;
void HeapInit(HP* php);
void HeapInit(HP* hp, HPDataType* a, int k);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);//小堆为例
void HeapPop(HP* php);//小堆为例
HPDataType HeapTop(Heap* hp);
int HeapSize(Heap* hp);
int HeapEmpty(Heap* hp);
//核心
void AdjustDown(HPDataType* a, size_t size, size_t root);
void AdjustUp(HPDataType* a, size_t child);
void HeapSort(int* a, int size);
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HeapInit(HP* php, HPDataType* a, int n)
{
assert(php && a);
php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
php->size = n;
php->capacity = n;
memcpy(php->a, a, sizeof(HPDataType) * n);
//向下 O(n*logn)
//for (int i = 0; i < n; i++)
//{
// AdjustUp(a, i);
//}
//
//向上 O(n)
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶子结点开始
{
AdjustDown(a, n, i);
}
}
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType t = *p1;
*p1 = *p2;
*p2 = t;
}
//向上调整
void AdjustUp(HPDataType* a, size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
child = child + 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
void HeapPush(HP* php, HPDataType x) //小堆为例
{
assert(php);
if (php->size >= php->capacity) //动态扩容
{
size_t newcapacity = php->capacity + 5;
HPDataType* t = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->size);
if (t == NULL)
{
printf("errno\n");
exit(-1);
}
php->a = t;
php->capacity = newcapacity;
}
php->a[(php->size)++] = x;
//向上调整
AdjustUp(php->a, php->size - 1);
}
void HeapSort(int* a, int n)
{
//建堆
//向上 O(n*logn)
//for (int i = 0; i < size; i++)
//{
// AdjustUp(a, i);
//}
//向下 O(n)
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶子结点开始
{
AdjustDown(a, n, i);
}
//排序 降序,每次将Top(最小数)与末尾交换,然后调整Top size--
//注意:升序用小堆,降序用大堆
size_t end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->a[0];
}
int HeapSize(Heap* hp)
{
return hp->size;
}
int HeapEmpty(Heap* hp)
{
return hp->size == 0 ? 0 : 1;
}