1. 熟悉堆的概念以及特性
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值; 堆总是一棵完全二叉树。
2. 总结数据结构中堆与内存堆区的区别
数据结构中的一般称“栈(stack)”,是一种后进先出的数据结构。它是一种概念,或者说是一种逻辑技术,与语言、平台无关。
内存管理中的“堆栈”其实是分为堆(heap)和栈(stack)的,以引用变量为例,引用变量本身存储在栈中,引用变量指向的值存储在堆中。
如int[] arr = {1, 2, 3};
变量arr(数组名)存储在栈中,变量arr的值(数组元素)存储在堆中(普通结构)。
内存管理中的栈采用的就是数据结构中的栈的思想,即遵循后进先出的管理方法。
好比数据结构中的栈是一项先进的技术,在内存管理中采用了该技术,在CPU的调度中可能也采用这种技术。
3. 实现堆的以下操作
// 要求:用户可以控制创建大堆还是小堆
typedef int DataType;
typedef struct Heap
{
DataType* _array;
int _capacity;
int _size;
}Heap;
// 创建堆
void CreatHeap(Heap* hp, int* array, int size);
//交换
void Swap(DataType *pLeft, DataType *pRight)
{
int temp = *pLeft;
*pLeft = *pRight;
*pRight = temp;
}
//向下调整
void AdjustDown(int *array, int size, int parent)
{
//默认parent的左孩子比较小
int child = parent * 2 + 1;
while (child < size)
{
//找左右孩子中较小的孩子
if (((child + 1) < size) && (array[child + 1] < array[child]))
child += 1;
//检测parent是否满足堆的性质
if (array[child] < array[parent])
{
Swap(&array[child], &array[parent]);
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
// 创建堆 O(Nlog2(N))
void CreatHeap(Heap* hp, int* array, int size)
{
//初始化Heap
hp->_array = (DataType *)malloc(size * sizeof(DataType));
if (hp->_array == NULL)
{
assert(0);
return;
}
//O(N)
hp->_capacity = size;
for (int i = 0; i < size; ++i)
hp->_array[i] = array[i];
hp->_size = size;
//找到倒数第一个非叶子节点
int last = (size - 2) / 2;
while (last >= 0)
{
AdjustDown(hp->_array, hp->_size, last); //log2(N) 以2为底
last--;
}
}
// 向堆中插入值为data的元素
void InsertHeap(Heap* hp, DataType data);
void CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->_capacity == hp->_size)
{
int newCapacity = 2 * hp->_capacity;
//1.申请新空间
DataType *pTemp = (DataType*)malloc(sizeof(DataType)*newCapacity);
if (pTemp == NULL)
{
assert(pTemp);
return;
}
//2.拷贝元素
for (int i = 0; i < hp->_size; ++i)
pTemp[i] = hp->_array[i];
//3.释放旧空间
free(hp->_array);
hp->_array = pTemp;
hp->_capacity = newCapacity;
}
}
//向上调整
void AdjustUp(DataType* array, int size, int child)
{
int parent = (child - 1) / 2;
while (child != 0)
{
if (array[child] < array[parent])
{
Swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
return;
}
}
// 向堆中插入值为data的元素 log2(N)
void InsertHeap(Heap* hp, DataType data)
{
CheckCapacity(hp);
hp->_array[hp->_size] = data;
hp->_size++;
AdjustUp(hp->_array, hp->_size, hp->_size - 1);
}
// 删除堆顶元素
void EraseHeap(Heap* hp);
// 删除堆顶元素 log2(N)
void EraseHeap(Heap* hp)
{
if (EmptyHeap(hp))
return;
Swap(&hp->_array[0], &hp->_array[hp->_size - 1]);
hp->_size--;
AdjustDown(hp->_array, hp->_size, 0);
}
// 获取堆中有效元素个数
int SizeHeap(Heap* hp);
// 获取堆中有效元素个数
int SizeHeap(Heap* hp)
{
assert(hp);
return hp->_size;
}
// 检测堆是否为空
int EmptyHeap(Heap* hp);
// 检测堆是否为空
int EmptyHeap(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
// 获取堆顶元素
DataType TopHeap(Heap* hp);
// 获取堆顶元素
DataType TopHeap(Heap* hp)
{
assert(hp);
return hp->_array[0];
}
// 销毁堆
void DestroyHeap(Heap* hp);
// 销毁堆
void DestroyHeap(Heap* hp)
{
assert(hp);
free(hp->_array);
hp->_capacity = 0;
hp->_size = 0;
}
测试:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Heap.h"
void Printf(Heap* hp)
{
for (int i = 0; i < hp->_size; ++i)
{
printf("%d ", hp->_array[i]);
}
printf("\n");
}
int main()
{
Heap hp;
int array[] = { 27,15,34,25,28,19,65,49,18,37 };
//创建堆
CreatHeap(&hp, array, sizeof(array) / sizeof(array[0]));
Printf(&hp);
//15 18 19 25 28 34 65 49 27 37
printf("%d\n", SizeHeap(&hp)); //10
printf("%d\n", TopHeap(&hp)); //15
//插入元素5
InsertHeap(&hp, 5);
Printf(&hp);
//5 15 19 25 18 34 65 49 27 37 28
printf("%d\n", SizeHeap(&hp)); //11
printf("%d\n", TopHeap(&hp)); //5
//删除堆顶元素
EraseHeap(&hp);
Printf(&hp);
//15 18 19 25 28 34 65 49 27 37
printf("%d\n", SizeHeap(&hp)); //10
printf("%d\n", TopHeap(&hp)); //15
//销毁
DestroyHeap(&hp);
return 0;
}
4. 用函数指针对堆进行扩展,使用户根据需要去创建大堆还是小堆
给堆结构中加入函数指针
typedef int DataType;
int Less(DataType left, DataType right);
int Greater(DataType left, DataType right);
//函数指针
typedef int(*PCMP)(DataType left, DataType right);
typedef struct Heap
{
DataType* _array;
int _capacity;
int _size;
PCMP _pPCMP;
}Heap;
// 创建堆
void CreatHeap(Heap* hp, int* array, int size,PCMP pCMP);
需要修改的函数
我们需要通过函数指针去控制构建的是大堆还是小堆,在函数里面需要使用到比较大小的部分都使用
int Less(DataType left, DataType right)
{
return left < right;
}
int Greater(DataType left, DataType right)
{
return left > right;
}
//交换
void Swap(DataType *pLeft, DataType *pRight)
{
int temp = *pLeft;
*pLeft = *pRight;
*pRight = temp;
}
//向下调整
void AdjustDown(int *array, int size, int parent, PCMP pCMP)
{
//默认parent的左孩子比较小
int child = parent * 2 + 1;
while (child < size)
{
//找左右孩子中较小的孩子
if (((child + 1) < size) && (pCMP(array[child + 1], array[child])))
child += 1;
//检测parent是否满足堆的性质
if (pCMP(array[child], array[parent]))
{
Swap(&array[child], &array[parent]);
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
// 创建堆 O(Nlog2(N))
void CreatHeap(Heap* hp, int* array, int size, PCMP pCMP)
{
//初始化Heap
hp->_array = (DataType *)malloc(size * sizeof(DataType));
if (hp->_array == NULL)
{
assert(0);
return;
}
//O(N)
hp->_capacity = size;
hp->_pPCMP = pCMP;
for (int i = 0; i < size; ++i)
hp->_array[i] = array[i];
hp->_size = size;
//找到倒数第一个非叶子节点
int last = (size - 2) / 2;
while (last >= 0)
{
AdjustDown(hp->_array, hp->_size, last, hp->_pPCMP); //log2(N) 以2为底
last--;
}
}
void CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->_capacity == hp->_size)
{
int newCapacity = 2 * hp->_capacity;
//1.申请新空间
DataType *pTemp = (DataType*)malloc(sizeof(DataType)*newCapacity);
if (pTemp == NULL)
{
assert(pTemp);
return;
}
//2.拷贝元素
for (int i = 0; i < hp->_size; ++i)
pTemp[i] = hp->_array[i];
//3.释放旧空间
free(hp->_array);
hp->_array = pTemp;
hp->_capacity = newCapacity;
}
}
//向上调整
void AdjustUp(DataType* array, int size, int child, PCMP pCMP)
{
int parent = (child - 1) / 2;
while (child != 0)
{
if (pCMP(array[child], array[parent]))
{
Swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
return;
}
}
// 向堆中插入值为data的元素 log2(N)
void InsertHeap(Heap* hp, DataType data)
{
CheckCapacity(hp);
hp->_array[hp->_size] = data;
hp->_size++;
AdjustUp(hp->_array, hp->_size, hp->_size - 1, hp->_pPCMP);
}
// 删除堆顶元素 log2(N)
void EraseHeap(Heap* hp)
{
if (EmptyHeap(hp))
return;
Swap(&hp->_array[0], &hp->_array[hp->_size - 1]);
hp->_size--;
AdjustDown(hp->_array, hp->_size, 0, hp->_pPCMP);
}
运行
CreatHeap(&hp, array, sizeof(array) / sizeof(array[0]) Less); //小堆
CreatHeap(&hp, array, sizeof(array) / sizeof(array[0]) Greater); //大堆
5. 堆的应用:堆排序&TOPK问题等
堆排序
void HeapAdjust(int *array, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
//在左右孩子中找较小的孩子
if ((child + 1) < size && (array[child + 1] < array[child]))
child += 1;
if (array[child] < array[parent])
{
Swap(&array[child], &array[child]);
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
//时间复杂度:O(Nlog2(N))
//空间复杂度:O(1)
void HeapSort(int* array, int size)
{
//1.建堆
//从倒数第一个非叶子节点的位置开始,使用向下调整,一直到根节点
int last = (size - 2) / 2;
while (last >= 0)
{
HeapAdjust(array, size, last); //向下调整
last--;
}
//2.排序
int end = size - 1;
while (end != 0)
{
Swap(&array[0], &array[end]);
HeapAdjust(array, end, 0);
end--;
}
}
TopK问题
需要一组数据前K个最大的元素,先用K个元素创建小堆,取元素与堆顶元素比较,大于则替换堆顶元素并重新调整堆,最后堆内的K个元素就是前K个最大的
需要一组数据前K个最小的元素,先用K个元素创建大堆,取元素与堆顶元素比较,小于则替换堆顶元素并重新调整堆,最后堆内的K个元素就是前K个最小的