数据结构 - 堆

246 阅读4分钟

在阅读本文前, 请先阅读以下一篇文章.

数据结构 - 顺序表 - 掘金 (juejin.cn)

一. 堆的定义 & 性质 & 结构

image.png

堆的定义

如果有一个关键码的集合 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, ..., 则称为大堆(小堆). 将根节点最大的堆叫做大根堆, 将根节点最小的堆叫做小根堆.

堆的性质

· 堆中某个节点的值总是不小于(大于等于)或不大于(小于等于)其父节点的值.

· 堆总是一棵完全二叉树

堆的结构

大根堆

任意节点的值  其子节点的值.

image.png

小根堆

任意节点的值  其子节点的值.

image.png

二. 堆各种接口的实现 (大根堆)

此处的堆采用顺序表的结构实现.

image.png

typedef int HPDataType;

typedef struct Heap
{
    HPDataType* a;    // 指向堆顶的指针
    int size;    // 堆中有效数据个数
    int capacity;    // 堆的容量大小
}HP;

1. 初始化堆

void HeapInit(HP* php)
{
    assert(php);

    php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
    if (php->a == NULL) {
        perror("malloc fail");
        return;
    }

    php->size = 0;
    php->capacity = 4;
}

2. 销毁堆

void HeapDestroy(HP* php)
{
    assert(php);

    free(php->a);
    php->a = NULL;
    php->capacity = php->size = 0;
}

3. 返回堆顶元素

HPDataType HeapTop(HP* php) 
{
    assert(php);
    return php->a[0];
}

4. 检测堆是否为空

bool HeapEmpty(HP* php)
{
    assert(php);
    return php->size == 0;
}

5. 获取堆中有效元素个数

int HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

6. 交换堆中两个元素

void Swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType x = *p1;
    *p1 = *p2;
    *p2 = x;
}

7. 向上调整 (大堆)

image.png

int parent = (child - 1) / 2;

image.png

此时 child 为 6, 大于 0, 进入 while 循环.

且 a[child] 大于 a[parent] (35 > 30).

Swap(&a[child], &a[parent]);

image.png

更新 child.

child = parent;

image.png

更新 parent.

parent = (child - 1) / 2;

image.png

此时 child 为 2, 大于 0, 进入 while 循环.

但是 a[child] 小于 a[parent] (35 < 70), break 跳出 while 循环, 向上调整完毕.

// 除了 child 下标对应的元素, 其他元素构成堆(大堆)
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;
        }
    }
}

8. 在堆底插入元素并向上调整 (大堆)

image.png

php->a[php->size] = x;

image.png

php->size++;

image.png

对堆底元素进行向上调整 (具体图示在上一个接口中).

AdjustUp(php->a, php->size - 1);

image.png

void HeapPush(HP* php, HPDataType x)
{
    assert(php);

    if (php->size == php->capacity) {
        HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
        php->a = tmp;
        php->capacity *= 2;
    }

    php->a[php->size] = x;
    php->size++;

    // 对堆底元素进行向上调整
    AdjustUp(php->a, php->size - 1);
}

9. 向下调整 (大堆)

image.png

child 记录左孩子下标 (此处默认左孩子大于右孩子)

int child = parent * 2 + 1;

image.png

此时 child 为 1, 小于 n(5), 进入 while 循环.

child + 1 为 2, 小于 n(5), 但a[child + 1] < a[child] (30 < 56), 不执行第一个 if 语句的内容.

a[child] > a[parent] (56 > 15), 进入第二个 if 语句.

交换 child 和 parent 对应的值.

Swap(&a[child], &a[parent]);

image.png

更新 parent.

parent = child;

image.png

更新 child.

child = parent * 2 + 1;

image.png

重复这一步骤.

image.png

此时 child 为 7, 大于 size (5), 跳出 while 循环.

// 左右子树都是大堆
void AdjustDown(HPDataType* a, int n, int parent)
{
    // child 记录左孩子下标 (此处默认左孩子大于右孩子)
    int child = parent * 2 + 1;
    while (child < n) {
        // 选出左右孩子中大的那一个
        if (child + 1 < n && a[child + 1] > a[child]) {
            ++child;
        }

        if (a[child] > a[parent]) {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        } else {
            break;
        }
    }
}

10. 删除堆顶元素并向下调整 (大堆)

image.png

交换堆顶元素与堆底元素.

Swap(&php->a[0], &php->a[php->size - 1]);

image.png

删除置于堆底的元素.

php->size--;

image.png

对堆顶元素进行向下调整 (具体图示在上一个接口中).

AdjustDown(php->a, php->size, 0);

image.png

void HeapPop(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));

    // 交换堆顶元素与堆底元素
    Swap(&php->a[0], &php->a[php->size - 1]);

    // 删除置于堆底的元素
    php->size--;

    // 对堆顶元素进行向下调整
    AdjustDown(php->a, php->size, 0);
}

三. 堆的源码

Heap.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap
{
    HPDataType* a;    // 指向堆顶的指针
    int size;    // 堆中有效数据个数
    int capacity;    // 堆的容量大小
}HP;

// 初始化堆
void HeapInit(HP* php);

// 销毁堆
void HeapDestroy(HP* php);

// 返回堆顶元素
HPDataType HeapTop(HP* php);

// 检测堆是否为空
bool HeapEmpty(HP* php);

// 获取堆中有效元素个数
int HeapSize(HP* php);

// 交换堆中两个元素
void Swap(HPDataType* p1, HPDataType* p2);

// 向上调整 (大堆)
void AdjustUp(HPDataType* a, int child);

// 在堆底插入元素并向上调整 (大堆)
void HeapPush(HP* php, HPDataType x);

// 向下调整 (大堆)
void AdjustDown(HPDataType* a, int n, int parent);

// 删除堆顶元素并向下调整 (大堆)
void HeapPop(HP* php);

Heap.c

#include "Heap.h"

void HeapInit(HP* php)
{
    assert(php);

    php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
    if (php->a == NULL) {
        perror("malloc fail");
        return;
    }

    php->size = 0;
    php->capacity = 4;
}

void HeapDestroy(HP* php)
{
    assert(php);

    free(php->a);
    php->a = NULL;
    php->capacity = php->size = 0;
}

HPDataType HeapTop(HP* php) 
{
    assert(php);
    return php->a[0];
}

bool HeapEmpty(HP* php)
{
    assert(php);
    return php->size == 0;
}

int HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType x = *p1;
    *p1 = *p2;
    *p2 = x;
}

// 除了 child 下标对应的元素, 其他元素构成堆(大堆)
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 HeapPush(HP* php, HPDataType x)
{
    assert(php);

    if (php->size == php->capacity) {
        HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }
        php->a = tmp;
        php->capacity *= 2;
    }

    php->a[php->size] = x;
    php->size++;

    // 对堆底元素进行向上调整
    AdjustUp(php->a, php->size - 1);
}

// 左右子树都是大堆
void AdjustDown(HPDataType* a, int n, int parent)
{
    // child 记录左孩子下标 (此处默认左孩子大于右孩子)
    int child = parent * 2 + 1;
    while (child < n) {
        // 选出左右孩子中大的那一个
        if (child + 1 < n && a[child + 1] > a[child]) {
            ++child;
        }

        if (a[child] > a[parent]) {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        } else {
            break;
        }
    }
}

void HeapPop(HP* php)
{
    assert(php);
    assert(!HeapEmpty(php));

    // 交换堆顶元素与堆底元素
    Swap(&php->a[0], &php->a[php->size - 1]);

    // 删除置于堆底的元素
    php->size--;

    // 对堆顶元素进行向下调整
    AdjustDown(php->a, php->size, 0);
}