25.6.19学习总结

什么是堆(Heap)?

堆是一种特殊的树形数据结构,它满足以下两个主要属性:

结构性(完全二叉树):

  • 堆总是一个完全二叉树 (Complete Binary Tree)。这意味着,除了最后一层,所有的层都是完全填充的,并且最后一层的所有节点都尽可能地向左填充。

  • 这个特性使得堆非常适合用数组来表示,因为节点之间的父子关系可以通过索引轻松计算。

堆序性(Heap Property):

  • 最大堆(Max-Heap): 在一个最大堆中,每个父节点的值都大于或等于其子节点的值。这意味着,根节点是整个堆中的最大元素。

  • 最小堆(Min-Heap): 在一个最小堆中,每个父节点的值都小于或等于其子节点的值。这意味着,根节点是整个堆中的最小元素。

总结: 堆是一个用数组实现的完全二叉树,并且满足特定的堆序性(父节点大于等于子节点或小于等于子节点)。

堆的ADT(抽象数据类型)操作

一个典型的堆通常支持以下操作:

  1. createHeap(capacity): 创建一个指定容量的空堆。

  2. isFull(heap): 检查堆是否已满。

  3. isEmpty(heap): 检查堆是否为空。

  4. insert(heap, value): 将新元素插入堆中,并保持堆的性质。

  5. deleteMax(heap) / deleteMin(heap): 删除堆中最大/最小元素(通常是根节点),并保持堆的性质。

  6. peekMax(heap) / peekMin(heap): 查看堆中最大/最小元素(根节点),但不删除。

  7. heapifyUp(heap, index): 向上调整堆,当子节点的值发生变化(通常是插入操作后)。

  8. heapifyDown(heap, index): 向下调整堆,当父节点的值发生变化(通常是删除操作或建堆操作后)。

  9. buildHeap(array, size): 将一个无序数组转换为一个堆。

堆的C语言实现原理(基于数组)

由于堆是完全二叉树,它最常用的实现方式是使用数组。这种实现方式的优点在于:

  • 空间效率高: 无需存储指针。

  • 计算效率高: 父子节点的关系可以通过简单的算术运算得出。

对于一个基于0索引的数组 arr

  • 根节点: arr[0]

  • 节点 i 的左子节点: arr[2 * i + 1]

  • 节点 i 的右子节点: arr[2 * i + 2]

  • 节点 i 的父节点: arr[(i - 1) / 2] (注意整数除法)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // For bool type

// -----------------------------------------------------------
// 1. 定义堆结构体
// -----------------------------------------------------------
typedef struct MaxHeap {
    int* arr;       // 存储堆元素的数组
    int capacity;   // 堆的最大容量
    int size;       // 堆当前元素的数量
} MaxHeap;

// -----------------------------------------------------------
// 2. 辅助函数
// -----------------------------------------------------------

// 交换两个整数的值
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// -----------------------------------------------------------
// 3. 核心堆操作函数
// -----------------------------------------------------------

// 创建一个空的MaxHeap
MaxHeap* createMaxHeap(int capacity) {
    MaxHeap* newHeap = (MaxHeap*)malloc(sizeof(MaxHeap));
    if (newHeap == NULL) {
        perror("Failed to allocate memory for MaxHeap structure");
        exit(EXIT_FAILURE);
    }
    newHeap->arr = (int*)malloc(sizeof(int) * capacity);
    if (newHeap->arr == NULL) {
        perror("Failed to allocate memory for MaxHeap array");
        free(newHeap); // Clean up partially allocated memory
        exit(EXIT_FAILURE);
    }
    newHeap->capacity = capacity;
    newHeap->size = 0;
    printf("MaxHeap created with capacity: %d\n", capacity);
    return newHeap;
}

// 释放堆占用的内存
void destroyMaxHeap(MaxHeap* heap) {
    if (heap != NULL) {
        free(heap->arr);
        heap->arr = NULL;
        free(heap);
        printf("MaxHeap destroyed.\n");
    }
}

// 检查堆是否为空
bool isEmpty(MaxHeap* heap) {
    return heap->size == 0;
}

// 检查堆是否已满
bool isFull(MaxHeap* heap) {
    return heap->size == heap->capacity;
}

// 获取父节点的索引
int getParentIndex(int i) {
    return (i - 1) / 2;
}

// 获取左子节点的索引
int getLeftChildIndex(int i) {
    return 2 * i + 1;
}

// 获取右子节点的索引
int ietRightChildIndex(int i) {
    return 2 * i + 2;
}

// 判断给定索引是否具有左子节点
bool hasLeftChild(MaxHeap* heap, int i) {
    return getLeftChildIndex(i) < heap->size;
}

// 判断给定索引是否具有右子节点
bool hasRightChild(MaxHeap* heap, int i) {
    return ietRightChildIndex(i) < heap->size;
}

/**
 * @brief 向上调整堆 (Heapify Up)
 * 当子节点的值大于父节点时,将其与父节点交换,直到满足堆性质或到达根节点。
 * 通常在插入新元素后调用。
 *
 * @param heap 指向MaxHeap的指针
 * @param index 待调整元素的当前索引
 */
void heapifyUp(MaxHeap* heap, int index) {
    // 当当前节点不是根节点,且当前节点的值大于其父节点的值时
    while (index > 0 && heap->arr[getParentIndex(index)] < heap->arr[index]) {
        swap(&heap->arr[index], &heap->arr[getParentIndex(index)]);
        index = getParentIndex(index); // 移动到父节点的位置,继续向上检查
    }
}

/**
 * @brief 向下调整堆 (Heapify Down)
 * 当父节点的值小于其子节点时,将其与最大的子节点交换,直到满足堆性质或到达叶子节点。
 * 通常在删除根元素或建堆时调用。
 *
 * @param heap 指向MaxHeap的指针
 * @param index 待调整元素的当前索引
 */
void heapifyDown(MaxHeap* heap, int index) {
    int maxIndex = index;
    int leftChild = getLeftChildIndex(index);
    int rightChild = ietRightChildIndex(index);

    // 检查左子节点是否存在且大于当前最大值
    if (hasLeftChild(heap, index) && heap->arr[leftChild] > heap->arr[maxIndex]) {
        maxIndex = leftChild;
    }

    // 检查右子节点是否存在且大于当前最大值
    if (hasRightChild(heap, index) && heap->arr[rightChild] > heap->arr[maxIndex]) {
        maxIndex = rightChild;
    }

    // 如果maxIndex不是当前index,说明子节点比父节点大,需要交换
    if (maxIndex != index) {
        swap(&heap->arr[index], &heap->arr[maxIndex]);
        heapifyDown(heap, maxIndex); // 递归向下调整
    }
}

/**
 * @brief 插入一个元素到最大堆
 * 将新元素放到数组末尾,然后向上调整以维护堆性质。
 *
 * @param heap 指向MaxHeap的指针
 * @param value 要插入的值
 * @return true 插入成功
 * @return false 堆已满,插入失败
 */
bool insert(MaxHeap* heap, int value) {
    if (isFull(heap)) {
        printf("Error: Heap is full. Cannot insert %d.\n", value);
        return false;
    }
    heap->arr[heap->size] = value; // 将新元素放到数组末尾
    heap->size++;                 // 堆大小加1
    heapifyUp(heap, heap->size - 1); // 从新元素位置向上调整
    printf("Inserted: %d\n", value);
    return true;
}

/**
 * @brief 从最大堆中删除最大元素(根元素)
 * 将根元素与最后一个元素交换,然后删除最后一个元素,最后向下调整新的根元素。
 *
 * @param heap 指向MaxHeap的指针
 * @param deletedValue 用于存储被删除的值的指针
 * @return true 删除成功
 * @return false 堆为空,删除失败
 */
bool deleteMax(MaxHeap* heap, int* deletedValue) {
    if (isEmpty(heap)) {
        printf("Error: Heap is empty. Cannot delete.\n");
        return false;
    }
    *deletedValue = heap->arr[0]; // 存储根元素的值
    heap->arr[0] = heap->arr[heap->size - 1]; // 将最后一个元素移动到根
    heap->size--;                            // 堆大小减1
    heapifyDown(heap, 0);                    // 从根节点向下调整
    printf("Deleted Max: %d\n", *deletedValue);
    return true;
}

/**
 * @brief 查看最大堆的根元素(最大值)
 *
 * @param heap 指向MaxHeap的指针
 * @param peekedValue 用于存储查看到的值的指针
 * @return true 查看成功
 * @return false 堆为空,查看失败
 */
bool peekMax(MaxHeap* heap, int* peekedValue) {
    if (isEmpty(heap)) {
        printf("Error: Heap is empty. No max element.\n");
        return false;
    }
    *peekedValue = heap->arr[0];
    printf("Peeked Max: %d\n", *peekedValue);
    return true;
}

/**
 * @brief 打印堆的当前元素(按数组顺序)
 * 注意:这只是打印数组内容,不代表堆的树形结构。
 */
void printHeap(MaxHeap* heap) {
    printf("Heap elements (array order): [");
    for (int i = 0; i < heap->size; i++) {
        printf("%d", heap->arr[i]);
        if (i < heap->size - 1) {
            printf(", ");
        }
    }
    printf("] (Size: %d, Capacity: %d)\n", heap->size, heap->capacity);
}

/**
 * @brief 将一个无序数组构建成一个最大堆
 * 从最后一个非叶子节点开始,依次向上调整每个节点。
 * 时间复杂度:O(n)
 *
 * @param heap 指向MaxHeap的指针
 * @param arr 待构建的原始数组
 * @param size 原始数组的大小
 * @return true 成功构建
 * @return false 原始数组大小超过堆容量
 */
bool buildHeap(MaxHeap* heap, int* arr, int size) {
    if (size > heap->capacity) {
        printf("Error: Array size (%d) exceeds heap capacity (%d).\n", size, heap->capacity);
        return false;
    }
    for (int i = 0; i < size; i++) {
        heap->arr[i] = arr[i];
    }
    heap->size = size;

    // 从最后一个非叶子节点开始向上调整
    // 最后一个非叶子节点的索引是 (size / 2) - 1
    for (int i = (heap->size / 2) - 1; i >= 0; i--) {
        heapifyDown(heap, i);
    }
    printf("Heap built from array. \n");
    return true;
}


// -----------------------------------------------------------
// 4. 主函数进行测试
// -----------------------------------------------------------
int main() {
    MaxHeap* myHeap = createMaxHeap(10); // 创建一个容量为10的堆

    // 插入操作测试
    insert(myHeap, 30);
    insert(myHeap, 10);
    insert(myHeap, 50);
    insert(myHeap, 20);
    insert(myHeap, 40);
    printHeap(myHeap); // 期望: [50, 40, 30, 10, 20] (或其他有效堆排列)

    // 查看最大值测试
    int maxVal;
    peekMax(myHeap, &maxVal); // 期望: 50

    // 删除操作测试
    deleteMax(myHeap, &maxVal); // 期望: 删除 50
    printHeap(myHeap); // 期望: [40, 20, 30, 10] (或其他有效堆排列)

    deleteMax(myHeap, &maxVal); // 期望: 删除 40
    printHeap(myHeap); // 期望: [30, 20, 10] (或其他有效堆排列)

    // 插入更多元素直到满
    insert(myHeap, 60);
    insert(myHeap, 70);
    insert(myHeap, 5);
    insert(myHeap, 90);
    insert(myHeap, 80);
    insert(myHeap, 100);
    insert(myHeap, 15);
    printHeap(myHeap); // 期望: 堆满,10个元素

    // 尝试插入超出容量
    insert(myHeap, 101); // 期望: 插入失败提示

    // 从数组建堆测试
    MaxHeap* anotherHeap = createMaxHeap(8);
    int arr[] = {3, 9, 2, 8, 1, 6, 7, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("\nBuilding heap from array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    buildHeap(anotherHeap, arr, n);
    printHeap(anotherHeap); // 期望: [9, 8, 7, 5, 1, 6, 2, 3] (或其他有效堆排列)

    deleteMax(anotherHeap, &maxVal);
    printHeap(anotherHeap);

    // 释放内存
    destroyMaxHeap(myHeap);
    destroyMaxHeap(anotherHeap);

    return 0;
}

代码解释:

MaxHeap 结构体:

  • arr: 指向存储堆元素的动态数组。

  • capacity: 数组的最大容量。

  • size: 堆中当前元素的数量,也是下一个要插入元素的索引。

辅助函数:

  • swap(): 简单的值交换函数。

  • getParentIndex(), getLeftChildIndex(), ietRightChildIndex(): 根据数组索引计算父子节点索引。

  • hasLeftChild(), hasRightChild(): 判断一个节点是否有对应的子节点,防止越界。

  • isEmpty(), isFull(): 检查堆的状态。

核心堆操作:

  • createMaxHeap() / destroyMaxHeap(): 堆的内存分配与释放。

  • heapifyUp(index) (向上调整):

    • 当新元素插入到堆的末尾时,它可能比其父节点大,违反了最大堆性质。

    • heapifyUp 将该元素与其父节点比较,如果大于父节点则交换位置。

    • 这个过程持续向上,直到元素回到正确位置(不大于父节点)或到达根节点。

    • 时间复杂度: O(log N),N 是堆的大小(因为每次操作向上移动一层)。

  • heapifyDown(index) (向下调整):

    • 当删除根节点(最大元素)后,或在建堆过程中,根节点被替换为最后一个元素,可能比其子节点小。

    • heapifyDown 将当前节点与其左右子节点比较,找出最大的子节点。

    • 如果当前节点小于最大的子节点,则与最大的子节点交换位置,然后对交换后的子节点位置递归调用 heapifyDown

    • 这个过程持续向下,直到元素回到正确位置(不小于任何子节点)或到达叶子节点。

    • 时间复杂度: O(log N)。

  • insert(value):

    • 将新元素添加到数组的末尾(heap->arr[heap->size])。

    • heap->size 增加。

    • 调用 heapifyUp 从新元素的位置向上调整,恢复堆性质。

    • 时间复杂度: O(log N)。

  • deleteMax(deletedValue):

    • 如果堆为空,返回失败。

    • 保存根节点的值(heap->arr[0])作为返回值。

    • 将数组的最后一个元素移动到根节点位置(heap->arr[0] = heap->arr[heap->size - 1])。

    • heap->size 减少。

    • 调用 heapifyDown 从根节点位置向下调整,恢复堆性质。

    • 时间复杂度: O(log N)。

  • peekMax(): 返回根元素的值,不删除。

  • buildHeap(arr, size) (建堆):

    • 将给定数组的元素直接复制到堆的内部数组中。

    • 最后一个非叶子节点 开始(索引为 (size / 2) - 1),向上遍历到根节点。

    • 对每个非叶子节点调用 heapifyDown,确保其子树满足堆性质。

    • 时间复杂度: O(N)。这比 N 次 insert 操作(O(N log N))更高效。

相关推荐
ggdpzhk1 分钟前
输入两个正整数,计算最大公约数和最小公倍数
java·算法
棱镜研途24 分钟前
学习笔记丨AR≠VR:透视沉浸式技术的“虚实象限”法则
学习·机器学习·ar·vr·虚拟现实
qq_3863226926 分钟前
华为网路设备学习-25(路由器OSPF - 特性专题 二)
网络·学习·华为
wsdchong之小马过河36 分钟前
2025角色建模学习一
学习
weixin_307779132 小时前
波动方程兼容性条件分析
算法
-qOVOp-2 小时前
408第二季 - 组成原理 - 流水线
数据结构·算法
HaiQinyanAN2 小时前
【学习笔记】锁+死锁+gdb调试死锁
c++·笔记·学习
Xの哲學2 小时前
hostapd状态机解析
linux·网络·算法·wireless
无名小猴2 小时前
Chapter15 cross-origin resource sharing
学习
旧物有情2 小时前
Unity2D 街机风太空射击游戏 学习记录 #12环射道具的引入
学习·游戏