堆原理及C语言实现

一、堆的概念及特性

相对来说,堆的概念虽然也源于生活,但是又高于生活。因为他保留了现实中的一些特征,又新增了一些新的特征。

(1) 现实中的堆

 

 

现实中,我们熟悉的堆一般如上图所示,比如木堆。

一般具有这个规律:每层的元素数量比上一层多一个,如果用公式表示则为 f(n) = f(n-1) + 1,初值 f(1) = 1, n表示自顶向下的层数。

其实很容易就得出 f(n) = n 这个公式了,这不是重点,重点是理解现实中的堆。

 

(2)数据结构中的堆

 

对比现实中的堆,虽然每层的元素数量相较于上一层都增加了,但是不再是增加固定的数值1了。

观察其规律:每层的元素数量都为上一层的两倍,用公式表示则为 f(n) = f(n-1) * 2, 初值f(1) = 1, n表示自顶向下的层数。

同样可以得出 f(n) = 2^(n-1), 也就是数据结构中的堆是这样一个结构。

我们可以换一种形式理解,这样更加直观,如下:

 

这样看起来是否更加清晰了呢?

其实从上述的性质也知道,每层的元素数量都为上层的2倍,这其实就是二叉树的节点的性质,所以:

堆本身其实就是一种二叉树的结构,而且是完全二叉树。

 

 二、堆的实现

就如同二叉树一样,无序的树结构、堆结构是没啥实用性的,咱们用到的一般都是有序的树和堆。其中有序的树往往用于二叉查找树,而有序的堆则是指的大根堆、小根堆(这两个概念在其他高级语言中,往往叫优先队列 priority_queue)。

有了前面实现队列和栈的思路,堆的总体思路这里就不再赘述了,具体实现可以看后续代码,这里只谈其中的难点。

那么怎么实现堆的有序性呢?

如上述所知,堆实际上是一种完全二叉树,那么在二叉树中如何实现有序性的呢?这点我们可以借鉴二叉搜索树,二叉搜索树中,有序性是由每个节点的特性来保证的,对于每个节点来说, 左子节点  > 根节点 > 右子节点, 满足该特性,就保证了整个二叉树的数据有序性。

那么在堆中,就可以借鉴这种思想,由每个节点特性来保证整个堆的数据有序性。

由于堆本身的需求和二叉搜索树不同,我们只需要保证每次从堆中取数据,取的值都是堆中最大(或者最小),那么就满足我们的需要了。大根堆(小根堆)的概念就是由此而来。

 

(1)上述过程中,我们说到了根节点、左子节点、右子节点的关系,那么他们之间是如何联系起来的?也就是说,如何通过根节点,确定左子节点和右子节点呢?

就如同队列和栈一样,堆的实现也是使用的数组进行实现。观察上面的图,其实我们就能发现,根节点和左子节点、右子节点的下标之间是有联系的。如下:

left = root * 2

right = root * 2 + 1

left 为左子节点下标, right为右子节点下标,root为根节点下标。

注意:堆中元素位置从1开始,数组首位一般都是填充位。

 

 

(2)往堆中新增元素时,如何保证堆的有序性? (这里用大根堆进行说明)

这个问题其实就是为新元素寻找合适的位置。由于在插入新元素之前,这个堆本身是有序的(也就是对每个节点来说,都有根节点 > 左右子节点)。

那么,我们就可以在堆末尾插入新的元素,这样只需要比较新插入的元素和他的根节点,然后交换位置,直到找到合适的位置,这样就完成了插入新元素的操作。(这个操作,我们称之为自底向上堆化)

如下:

void AddToHeap(Heap *obj, int val)
{
    obj->arr[++obj->num] = val; // 先在末尾插入
    
    // 然后调整顺序
    int cur = obj->num;
    while(cur / 2 > 0 && obj->arr[cur / 2] < obj->arr[cur])
    {
        Swap(&obj->arr[cur / 2], &obj->arr[cur]);
        cur = cur / 2;
    }
}
 
 
(3)从堆中取出元素时,如何保证堆的有序性? (同样用大根堆进行说明)
由于直接取出堆顶元素,然后再对剩余元素进行堆化操作复杂度过高,一般不采用这种方式。我们可以交换堆顶元素和堆底元素,然后再对堆顶元素进行堆化操作。(这个操作,我们称之为自顶向下堆化)
如下:
void DeleteFromHeap(Heap *obj)
{
    // 先交换首尾元素
    Swap(&obj->arr[1], &obj->arr[obj->num]);

    // 然后调整堆内顺序
    obj->num--;
    int index = 1;
    while(true)
    {
        int maxIndex = index;
        if(index * 2 <= obj->num && obj->arr[maxIndex] < obj->arr[index * 2])
        {
            maxIndex = index * 2;
        }
        if(index * 2 + 1 <= obj->num && obj->arr[maxIndex] < obj->arr[index * 2 + 1])
        {
            maxIndex = index * 2 + 1;
        }
        if(maxIndex == index)
        {
            break;
        }
        Swap(&obj->arr[index], &obj->arr[maxIndex]);
        index = maxIndex;
    }
}
 
三、代码实现
 
typedef struct
{
    int *arr; // 实现堆的数组,申请的大小为size + 1
    int size; // 堆的size,也是最大的下标值
    int num;  // 堆中的元素个数
}Heap; // 大根堆

Heap *CreatHeap(int size)
{
    Heap *newHeap = malloc(sizeof(Heap));
    if(newHeap == NULL)
    {
        return NULL;
    }
    newHeap->arr = malloc(sizeof(int) * (size + 1));
    if(newHeap->arr == NULL)
    {
        free(newHeap);
        return NULL;
    }
    newHeap->size = size;
    newHeap->num = 0;
    return newHeap;
}

void FreeHeap(Heap *obj)
{
    free(obj->arr);
    free(obj);
}

int GetHeapTop(Heap *obj)
{
    return obj->arr[1];
}

void Swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void AddToHeap(Heap *obj, int val)
{
    obj->arr[++obj->num] = val; // 先在末尾插入
    
    // 然后调整顺序
    int cur = obj->num;
    while(cur / 2 > 0 && obj->arr[cur / 2] < obj->arr[cur])
    {
        Swap(&obj->arr[cur / 2], &obj->arr[cur]);
        cur = cur / 2;
    }
}

void DeleteFromHeap(Heap *obj)
{
    // 先交换首尾元素
    Swap(&obj->arr[1], &obj->arr[obj->num]);

    // 然后调整堆内顺序
    obj->num--;
    int index = 1;
    while(true)
    {
        int maxIndex = index;
        if(index * 2 <= obj->num && obj->arr[maxIndex] < obj->arr[index * 2])
        {
            maxIndex = index * 2;
        }
        if(index * 2 + 1 <= obj->num && obj->arr[maxIndex] < obj->arr[index * 2 + 1])
        {
            maxIndex = index * 2 + 1;
        }
        if(maxIndex == index)
        {
            break;
        }
        Swap(&obj->arr[index], &obj->arr[maxIndex]);
        index = maxIndex;
    }
}
posted @ 2022-04-13 17:39  守得云开月未明  阅读(76)  评论(0编辑  收藏  举报