1.什么是堆?


2.堆数组下标中父亲、左孩子、右孩子之间的计算方式

1.已知父亲的下标,计算它左孩子和右孩子的下标:
2.已知左孩子和右孩子的下标,计算它俩父亲的下标:
3.堆的实现(小堆)
1.堆的定义
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
size记录的是有效数据的个数,capacity记录的是容量的大小。
2.堆的初始化
初始化部分建议开4个的空间,检查一下是否开辟成功后将size初始化为0,将capacity初始化为4
// 堆的初始化
void HeapInit(Heap* hp)
{
assert(hp);
hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
if(hp->_a == NULL)
{
perror("malloc fail");
exit(-1);
}
hp->_size = 0;
hp->_capacity = 4;
}
3.堆的销毁
将在堆上开辟的空间释放掉以后,将指针设置为空,将capacity和size设置为0
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_capacity = 0;
hp->_size = 0;
}
4.堆的插入
1.实现步骤:
2.向上调整:
如果不向上调整,插入数据后就不能保证这个数组是小堆了,看下面的分析步骤:
1.下图堆中,我们尾插了一个数据20:
在插入数据前,这棵树还是个小堆,但是插入数据后就不复合小堆的定义了,所以需要向上调整。
2.如果要确保它还是一个小堆,就需要去调整数据,为了不影响其他子树(如上图中的左子树),新插入的数据只能和父亲做调整,调整方式就是交换位置,如下图所示:
3.调整完后,不一定还是小堆,还需要继续向上调整直到它的父亲小于等于新插入的数据为止,或者是新插入的数据为根节点为止,如下图所示:
随着新插入的数据20的调整过程中,它的父亲也一直在变,如上面的图片中,它的父亲由40变成了20,最后20变成了祖先。
小堆向上调整的总结:
1.为了不影响其他子树,只能和父亲做调整;
2.当新插入的数据小于父亲时,需要交换它们两个的位置;
3.直到新插入的数据大于等于父亲或者是新插入的数据成为了根节点时调整才能停止。
向上调整的代码:
void Adjust_up(HPDataType* _a,int childi)
{
int parenti = (childi - 1) / 2;//计算出父亲下标的
while(childi > 0)//结束条件2,当新插入的数据成为根节点
{
if(_a[childi] < _a[parenti])//当父亲大于新插入的数据,需要调整
{
Swap(&_a[childi],&_a[parenti]);
//更新孩子和父亲下标,继续向上调整
childi = parenti;
parenti = (childi - 1) / 2;
}
else
break;//结束条件1,新插入的数据大于等于父亲时
}
}
3.堆的插入代码:
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if(hp->_size == hp->_capacity)//需要扩容
{
HPDataType* temp = (HPDataType*)realloc(hp->_a,sizeof(HPDataType) * 2 * hp->_capacity);
if(temp == NULL)
{
perror("realloc fail");
exit(-1);
}
hp->_capacity *= 2;
}
hp->_a[hp->_size] = x;
Adjust_up(hp->_a,hp->_size);
hp->_size++;
}
5.堆的删除

将记录有效数据个数的size--后,头部删除就完成了,从上图中可以看见,当头部和尾部的数据交换后堆就不再是小堆了,为了保证存储的数据的数组继续是小堆,就要进行向下调整操作。
1.向下调整
当头部的数据删除以后,左子树和右子树其实还是小堆,如下图所示:
要进行向下调整,就要从80它的两个孩子中选出一个来调整,因为我们要实现的小堆,只能选择一个最小的,也就是它的右孩子,下图是它们两个的顺序交换后:
交换完后,从上图中可以看见,这些数据依旧不是小堆,就要继续向下调整,直到它的左孩子和右孩子的大小大于等于它为止,或者是已经没有左右孩子可以来调整了,向下调整就完成了,如下图所示:
随着80的不断的向下调整,它的左右孩子都会变,最后可能就没有儿子了(变成孤家寡人了)
小堆向下调整的总结:
1.从左右孩子中选出最小的那一个去和它做调整(交换)。
2.调整完后,它的位置发生了改变,它对应的左右孩子也会发生改变。
3.当它的左右孩子的值大于等于它时或者是没有孩子时可以调整时,向下调整完成。
2.向下调整代码:
void Adjust_down(HPDataType* _a,int parenti,int len)
{
int left = parenti * 2 + 1;//左孩子的下标
while(parenti < len && left < len)//结束条件2:当没有孩子可以进行交换
{
if(left + 1 < len && _a[left] > _a[left + 1])//找出最小的孩子
{
left++;
}
if(_a[parenti] > _a[left])//如果父亲大于孩子,需要进行交换
{
Swap(&_a[left],&_a[parenti]);
//更新下标
parenti = left;
left = parenti * 2 + 1;
}
else
break;//结束条件1:父亲小于孩子
}
}
3.堆的删除代码:
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
assert(hp->_size > 0);
if(hp->_size == 1)//只有一个数据时,没有必要交换和向下调整
{
hp->_size--;
}
else
{
Swap(&hp->_a[0],&hp->_a[hp->_size - 1]);//交换头部和尾部的数据
hp->_size--;//删除数据
Adjust_down(hp->_a,0,hp->_size);//向下调整
}
}
6.取堆顶的数据
先检查数据的有效个数,返回数组0号下标的位置的值
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(hp->_size > 0);
return hp->_a[0];
}
7.堆的数据个数
返回size,size记录的是数据的有效个数
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
8.堆的判空
判断size,size为0就返回真,否则返回假
// 堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0;
}
9.用数组来初始化堆
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if(hp->_a == NULL)
{
perror("malloc fail");
exit(-1);
}
hp->_capacity = n;
hp->_size = n;
memcpy(hp->_a,a,sizeof(HPDataType) * n);//将a的数据拷贝进去
int len = n - 1;//最后一个数据的下标
for(int i = ( len - 1) / 2; i >= 0; i--)//向下调整建堆
{
Adjust_down(hp->_a,i,n);
}
}