数据结构之堆——算法与数据结构入门笔记(六)

简介: 数据结构之堆——算法与数据结构入门笔记(六)

f8b2b59133134bdbb21101207cb05bd5.png

本文是算法与数据结构的学习笔记第六篇,将持续更新,欢迎小伙伴们阅读学习。有不懂的或错误的地方,欢迎交流


引言


当涉及到高效的数据存储和检索时,堆(Heap)是一种常用的数据结构。上一篇文章中介绍了树和完全二叉树,堆就是一个完全二叉树,可以分为最大堆和最小堆两种类型。在这篇博客中,我们将深入探讨堆的概念、特点、常见应用、操作以及实现。


什么是堆?


在计算机科学中,堆是一种具有特殊属性的树形数据结构。堆通常被用来实现优先队列(Priority Queue),它允许快速地找到具有最高(或最低)优先级的元素。


堆的特点


堆的主要特点如下:


1.堆是一种完全二叉树结构,即除了最后一层外,其他层的节点都是满的,并且最后一层的节2.点从左到右依次填满,不能有间隔。在最大堆中,每个节点的值都大于或等于其子节点的值。根节点的值是最大的。在最小堆中,每个节点的值都小于或等于其子节点的值。根节点的值是最小的。

3.堆通常被表示为一个数组,可以通过索引直接访问堆中的元素,堆的根节点通常是数组中的第一个元素。

4.堆的插入和删除操作的时间复杂度都为 O ( log ⁡ n ) O(\log n)O(logn),其中 n nn 是堆中元素的数量。


堆的应用


堆在计算机科学中有广泛的应用,其中一些主要应用包括:


1.堆排序

堆排序是一种高效的排序算法,它利用堆的性质进行排序。它的时间复杂度为 O ( n log ⁡ n ) O(n\log n)O(nlogn),并且具有原地排序的特性。

2.队列

堆可以实现高效的优先级队列,允许以常数时间复杂度找到具有最高优先级的元素,并支持快速的插入和删除操作。

3.Top K 问题

在一组元素中,查找前 K 个最大(或最小)的元素是一个常见的问题。使用堆可以高效地解决这个问题,通过维护一个大小为 K 的最小堆或最大堆,可以快速地找到前 K 个元素。

4.图算法

在图算法中,堆常用于实现最短路径算法(如Dijkstra算法)和最小生成树算法(如Prim和Kruskal算法)。

5.数据流中的中位数

对于一个不断变化的数据流,查找其中的中位数也是一个常见的问题。使用两个堆(一个最大堆和一个最小堆),可以高效地实现对数据流中的中位数的查找。



为什么使用数组实现堆


用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间上都是很高效的。

我们准备将上面图中的大根堆这样存储:

50, 45, 40, 20, 25, 35, 30, 10, 15 ]

就这么多!我们除了一个简单的数组以外,不需要任何额外的空间。

如果我们不允许使用指针,那么我们怎么知道哪一个节点是父节点,哪一个节点是它的子节点呢?问得好!节点在数组中的位置 index 和它的父节点以及子节点的索引之间有一个映射关系。

如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:

parent(i) = floor((i - 1)/2)
left(i)   = 2i + 1
right(i)  = 2i + 2

注意:right(i) 就是简单的 left(i) + 1。左右节点总是处于相邻的位置。

我们将这些公式放到前面的例子中验证一下。

1690469231303.png


注意:根节点(50)没有父节点,因为 -1 不是一个有效的数组索引。同样,节点(25),(35),(30),(10)和(15)没有子节点,因为这些索引已经超过了数组的大小,所以我们在使用这些索引值的时候需要保证是有效的索引值。

复习一下,在最大堆中,父节点的值总是要大于(或者等于)其子节点的值。这意味下面的公式对数组中任意一个索引 i 都成立:

array[parent(i)] >= array[i]


可以用上面的例子来验证一下这个堆属性。

如你所见,这些公式允许我们不使用指针就可以找到任何一个节点的父节点或者子节点。


堆的基本操作


以下是堆的一些基本操作:

1.插入:将一个元素插入到堆中,并保持堆的特性。

2.删除根节点:删除堆的根节点,并保持堆的特性。

3.获取根节点:获取堆的根节点的值,通常是堆中最大或最小的值。

4.堆化(Heapify):对一个无序的数组进行堆化操作,将其转换为一个堆。


C语言


以下是使用C语言实现堆(包括创建堆、插入数据、删除根结点、获取根节点和堆化等基础操作)的示例代码:

#include <stdio.h>
#define MAX_HEAP_SIZE 100
typedef struct {
    int heap[MAX_HEAP_SIZE];
    int size;
} Heap;
void initializeHeap(Heap *h) {
    h->size = 0;
}
void insert(Heap *h, int value) {
    if (h->size >= MAX_HEAP_SIZE) {
        printf("Heap is full.\n");
        return;
    }
    int i = h->size;
    h->heap[i] = value;
    h->size++;
    // 调整堆的结构
    while (i > 0 && h->heap[(i - 1) / 2] < h->heap[i]) {
        int temp = h->heap[i];
        h->heap[i] = h->heap[(i - 1) / 2];
        h->heap[(i - 1) / 2] = temp;
        i = (i - 1) / 2;
    }
}
int removeRoot(Heap *h) {
    if (h->size <= 0) {
        printf("Heap is empty.\n");
        return -1;
    }
    int root = h->heap[0];
    h->size--;
    h->heap[0] = h->heap[h->size];
    // 调整堆的结构
    int i = 0;
    while (2 * i + 1 < h->size) {
        int leftChild = 2 * i + 1;
        int rightChild = 2 * i + 2;
        int largerChild = leftChild;
        if (rightChild < h->size && h->heap[rightChild] > h->heap[leftChild]) {
            largerChild = rightChild;
        }
        if (h->heap[i] >= h->heap[largerChild]) {
            break;
        }
        int temp = h->heap[i];
        h->heap[i] = h->heap[largerChild];
        h->heap[largerChild] = temp;
        i = largerChild;
    }
    return root;
}
void heapify(Heap *h, int arr[], int n) {
    initializeHeap(h);
    // 将数组元素逐个插入堆中
    for (int i = 0; i < n; i++) {
        insert(h, arr[i]);
    }
}
int getRoot(Heap *h) {
    if (h->size <= 0) {
        printf("Heap is empty.\n");
        return -1;
    }
    return h->heap[0];
}
int main() {
    Heap h;
    initializeHeap(&h);
    insert(&h, 5);
    insert(&h, 2);
    insert(&h, 10);
    insert(&h, 8);
    int root = removeRoot(&h);
    printf("Root: %d\n", root);
    int arr[] = {9, 4, 7, 1, 3};
    int arrSize = sizeof(arr) / sizeof(arr[0]);
    heapify(&h, arr, arrSize);
    printf("Root: %d\n", getRoot(&h));
    return 0;
}


结论


堆是一种重要的数据结构,它提供了高效的数据存储和检索方式。我们可以使用数组来实现堆,并实现插入、删除和堆化等操作。堆在排序、优先队列、Top K 问题、图算法以及中位数查找等方面具有广泛的应用。

希望这篇博客能够帮助你理解堆的概念、应用和实现。

相关文章
|
24天前
|
算法
【初阶数据结构】复杂度算法题篇
该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 kmodn 个元素会移动至数组头部,其余元素向后移动 kmodn 个位置。
|
25天前
|
机器学习/深度学习 人工智能 算法
【人工智能】线性回归模型:数据结构、算法详解与人工智能应用,附代码实现
线性回归是一种预测性建模技术,它研究的是因变量(目标)和自变量(特征)之间的关系。这种关系可以表示为一个线性方程,其中因变量是自变量的线性组合。
37 2
|
10天前
|
存储 程序员 C语言
堆和栈之间有什么区别
【9月更文挑战第1天】堆和栈之间有什么区别
77 0
|
14天前
|
数据采集 算法
基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真
该程序利用PSO算法优化5个4*20矩阵中的模块采集轨迹,确保采集的物品数量及元素含量符合要求。在MATLAB2022a上运行,通过迭代寻优,选择最佳模块组合并优化轨道,使采集效率、路径长度及时间等综合指标最优。具体算法实现了粒子状态更新、需求量差值评估及轨迹优化等功能,最终输出最优轨迹及其相关性能指标。
|
22天前
|
算法 机器人
【数据结构】什么是堆
【数据结构】什么是堆
31 0
|
24天前
|
算法
【初阶数据结构篇】二叉树算法题
二叉树是否对称,即左右子树是否对称.
|
24天前
|
算法
【初阶数据结构篇】堆的应用(堆排序与Top-K问题)
即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。
|
24天前
|
存储 算法 测试技术
【初阶数据结构篇】实现顺序结构二叉树(堆的实现方法)
注意传过去的参数是插入的位置,即插入前的size,在调整完后再将size++
|
24天前
|
算法 索引
【初阶数据结构篇】单链表算法题进阶
深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。
|
24天前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。