最近笔者花了几天时间研究了堆,下面做一些分享。
首先什么是堆。
堆在逻辑上是特殊的完全二叉树,在存储结构上是顺序表。一般用数组实现。
堆只分为大堆和小堆
所谓大堆是指:在堆中所有的最小二叉树的父亲节点必须比它的叶子节点大。
所谓小堆是指:在堆中所有的最小二叉树中父亲节点比它的叶子小。只有这两种特殊的情况下一个数组才能被称为堆。
堆的自顶向下算法:
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整
成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
如图所示
我们可以发现,此时看到的是一个小堆,就是不断地对比父节点 和它的叶子节点中较小的值进行交换。
接下来我们来看代码,笔者将详细介绍:
#define INIT_ARRAY_SIZE 50
int heap_size; //堆大小
int heap_cap_size; //堆容量大小
int main() {
int i = 0;
int a[] = { 27,15,19,18,28,34,65,49,25,37 };
int *array = NULL;
array = (int *)malloc(INIT_ARRAY_SIZE * sizeof(int));
int length = sizeof(a) / sizeof(int);
printf("数组的长度是:%d\n", length);
for (i = 0; i < length; ++i) {
array[i] = a[i];
}
printf("原始数组为\n");
print_array(array, length);
}
首实现先是一些准备工作,定义堆的大小,容量、用数组模拟一个假堆,用在实现我们的自顶向下算法中。
来看算法
/*返回以index为根的完全二叉树的左子树的索引,整个二叉树索引以0为开始*/
int left(int index) {
return ((index << 1) + 1);
}
/*返回以index为根的完全二叉树的右子树的索引,整个二叉树索引以0为开始*/
int right(int index) {
return ((index << 1) + 2);
}
/*两个数的交换*/
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
return;
}
void max_heap_adjust(int array[], int index) {//index是位置
int left_index = left(index);
int right_index = right(index);
int largest = index;
//左子树和父节点进行对比
if (left_index <= (heap_size - 1) && array[left_index] > array[largest]) {
largest = left_index;
}
//右子树和父节点进行对比
if (right_index <= (heap_size - 1) && array[right_index] > array[largest]) {
largest = right_index;
}
if (largest == index) {
/*判断是否要进行递归调用,没交换一次最小二叉树的时候,可能会破坏前面已经调整好的堆
的结构,所以交换一次需要从当前父亲节点开始重新进行自顶向下算法,重新调整堆*/
/*这里的递归退出条件是左右子树下标都大于二叉树的总长度,即调整不能调整为止
*/
return;
}
else {
//需要交换
swap(&array[index], &array[largest]);
//递归调用
max_heap_adjust(array, largest);
}
}
调整为大堆
看到的是传入堆头和要开始的位置,接下来计算它的左叶子和右叶子。再用largest记录它的父节点位置,然后分别判断左右子树是否大于父节点。
如果大于,下标赋值给largest,然后判断largest和index是否相等,如果不相等,进行交换,并且将largest(较大的位置)作为下一次要进行算法的起始点传入算法函数中,调整下面的假堆。
在这里值得一提的是,是否进入递归的判断条件,从程序的设计方式可以看到,如果函数体内所有的语句都不执行,判断就会成真,跳出函数,完成所有的调整,隐含意思是走到正数第一个叶子节点为止。
实现了自顶向下算法之后,我们构建一个堆,
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算
法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的
子树开始调整,一直调整到根节点的树,就可以调整成堆。
下面我们来看代码实现:
void build_heap(int array[], int length) {
heap_size = length;
for (int i = ((heap_size - 1) >> 1); i >= 0; --i) {
max_heap_adjust(array, i);
}
}