堆的概念:如果有一个关键码的集合K = {K0,K1,K2,…,Kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2 * i + 1且Ki <= K2 * i + 2(Ki >= K2 * i + 1且Ki >= K2 * i + 2)i = 0,1,2…,则称为小堆(或大堆)。
小堆(大堆)中:任一结点的关键码均小于(大于)等于它的左右孩子的关键码,位于堆顶结点的关键码最小(最大),从根结点到每个结点的路径上数组元素组成的序列都是递增(递减)的。
堆存储在下标为0开始的数组中,因此在堆中给定下标为i的结点时(这些性质在下边计算root和child会用到):
* 如果 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无右孩子
我们先要定义一个结构体:
typedef int HPDataType;
typedef int(*Compare)(HPDataType left, HPDataType right);
typedef struct Heap
{
HPDataType* _hp;
int _capacity;
int _size;
Compare _compare;//一个函数指针,用来表示创建大堆还是小堆
}Heap;
堆的创建:
分析:我们要创建堆就要创建一个满足堆性质的一颗二叉树
void AdjustDown(Heap* hp, int root, Compare compare)
{
int child = (root << 1) + 1;//child标记最小的孩子,默认是左孩子 左移1位就是乘2
while (child < hp->_size)
{
//要保证左孩子存在并且找出最大/最小的那个
if (child + 1 < hp->_size && compare(hp->_hp[child + 1], hp->_hp[child]))
{
child += 1;
}
//检测root是否比最大/最小的孩子大/小
if (compare(hp->_hp[child], hp->_hp[root]))
{
Swap(&hp->_hp[root], &hp->_hp[child]);
root = child;
child = (root << 1) + 1;
}
else
return;//本身就满足堆的性质
}
}
void CreateHeap(Heap* hp, HPDataType* array, int size, Compare compare)//size是数组大小
{
assert(hp);
int root = (size - 2) >> 1;//找到最下边的一个堆的堆顶
hp->_hp = malloc(size*(sizeof(HPDataType)));
if (NULL == hp->_hp)
{
printf("初始化失败\n");
return;
}
//把数组中所有元素拷贝到堆的数组中
memcpy(hp->_hp, array, size*sizeof(HPDataType));
hp->_size = size;
hp->_capacity = size;
//调整,让堆中所有元素满足堆的性质
for (root; root >= 0; root--)
{
AdjustDown(hp, root, compare);
}
}
堆的插入:
分析:对堆插入一个数,在插入这个数之前,已经满足了堆的性质,但这个数插入之后就不一定满足了,所以我们就要进行调整,使它再次满足堆的性质,在这里的调整我们用的是向上调整,因为向上调整我们一次性就调整好了,如果是向下调整的话,上边的都满足堆的性质,前边那些调整都是无用功,如果插入的数比堆中任何一个数都小(在这里我们假设把它调成小堆),我们还得从后往前再调整一遍,把所有比要插入的数大的数向下调整,但如果我们是向上调整的话,我们从下到上一次就调好了。
Heap* CheckHeap(Heap* hp)
{
assert(hp);
HPDataType tmp;
if (hp->_capacity == hp->_size)//说明堆满了,需要再开辟空间
{
tmp = malloc((2 * hp->_size + 3)*sizeof(HPDataType));
if (NULL == tmp)
{
assert(0);
return;
}
//将原来堆中的数据拷贝到新扩展后的堆中
memcpy(tmp, hp->_hp , hp->_size*sizeof(HPDataType));
}
//释放原来的堆
free(hp->_hp);
hp->_hp = tmp;
hp->_capacity = 2 * hp->_size + 3;
return hp;
}
void AdjustUp(Heap* hp, Compare compare)
{
assert(hp);
int child = hp->_size - 1;
int root = (hp->_size - 2) >> 1;
while (root >= 0)
{
if (child + 1 < hp->_size && compare(hp->_hp[child + 1], hp->_hp[child]))
child += 1;
if (compare(hp->_hp[child], hp->_hp[root]))
{
Swap(&hp->_hp[child], &hp->_hp[root]);
child = root;
root = (child - 1) >> 1;(右移1为就是除2)
}
else
{
return;
}
}
}
void InsertHeap(Heap* hp, HPDataType data, Compare compare)
{
//将元素放入堆中(最后位置)
hp = CheckHeap(hp);//检测堆的空间是否足够,若不够,要扩充
hp->_hp[hp->_size++] = data;
//对堆进行调整(向上调整)
AdjustUp(hp, compare);
}
堆的删除:
分析:删除堆顶元素并不好删除,我们可以转变思想,把最后一个元素和堆顶元素交换,删除最后一个元素就相当于把堆顶 元素删除了,但交换后肯定不满足堆的性质了,所以我们需要把整个堆的元素向下调整,使其满足堆的性质。
void RemoveHeap(Heap* hp, Compare compare)//删除堆顶元素
{
int end = hp->_size - 1;
assert(hp);
//将堆顶元素与最后一个元素交换
Swap(&hp->_hp[0], &hp->_hp[end]);
//删除最后一个元素==size-1
hp->_size--;
int root = (hp->_size - 1) >> 1;
//向下调整堆顶元素
while (root)
{
AdjustDown(hp, root, compare);
root--;
}
}