堆
堆的概念
堆是一种特殊完全二叉树的数据储存方式,我利用数组来实现堆。将根节点最大的堆称为最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆中某个节点的值总是不大于(小根堆)或不小于(大根堆)其父节点的值。
堆的逻辑结构总是一棵完全二叉树,但是存储结构不是按照树的逻辑来存储的。
如果已知一棵树的父亲节点的下标为i
,那么他的子节点的坐标为2 * i + 1
和2 * i + 2
。同理,如果已知一棵树的子节点的下标为j
,那么其父亲节点的下标为(j - 1) / 2
。
堆的实现
堆的建立
int a[] = {1, 5, 3, 8, 7, 6};
上面的数组a
可以在逻辑上看作一棵完全二叉树,但是它不是一个堆。我们可以用堆向下调整算法来建立一个堆。
堆向下调整算法有一个前提条件,即树的左右子树必须是一个堆。而数组a
不满足这个条件,所以需要从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。这种建堆的时间复杂度为O(N)。
堆向下调整代码如下:
void AdjustDown(HPDataType* a, int size, int parent)//目前状态小堆
{
int child = parent * 2 + 1;
while (child < size)
{
//选出左右孩子中小/大的那个人
if (child + 1 < size && a[child + 1] < a[child]) // <是小堆, >是大堆
{
child++;
}
if (a[child] < a[parent])// <是小堆,>是大堆
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
利用数组a
建堆,n
为a
数组的长度。
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
堆的插入
在堆中插入数据在存储逻辑上是在对数组进行尾插。可是插入的数据不一定满足大根堆或小根堆的条件,所以需要比较新加入的节点和其父亲节点(必要时还要继续比较其和其其它祖先节点的大小关系)。由此引入堆向上调整算法。
堆向上调整算法的代码实现:
//非递归写法
void AdjustUp(HPDataType* a, int child)//目前状态大堆
{
int 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 AdjustUpR(HPDataType* a, int child)//目前状态大堆
{
/*if (child <= 0)
{
return;
}*///不需要这个条件
//printf("调用\n");
int parent = (child - 1) / 2;
if (a[child] > a[parent])//<是小堆,>是大堆
{
Swap(&a[child], &a[parent]);
child = parent;
//parent = (child - 1) / 2;
}
else
{
return;
}
AdjustUpR(a, child);
}
堆的插入代码:
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->_size == php->_capacity)//检查并扩容
{
int newCapcity = php->_capacity == 0 ? 4 : php->_capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * newCapcity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->_a = tmp;
php->_capacity = newCapcity;
}
php->_a[php->_size] = x;
php->_size++;
AdjustUp(php->_a, php->_size - 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);
}
其它接口实现
小结
堆的删除用向下调整!因为删完了左右子树依然是堆,重新构建即可。
堆的插入用向上调整!因为插入一个节点得和祖先节点比较大小,自然数据是”往上面走”的。