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

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

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 问题、图算法以及中位数查找等方面具有广泛的应用。

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

相关文章
|
2月前
|
存储 算法
算法入门:专题二---滑动窗口(长度最小的子数组)类型题目攻克!
给定一个正整数数组和目标值target,找出总和大于等于target的最短连续子数组长度。利用滑动窗口(双指针)优化,维护窗口内元素和,通过单调性避免重复枚举,时间复杂度O(n)。当窗口和满足条件时收缩左边界,更新最小长度,最终返回结果。
|
2月前
|
存储 算法
算法入门:专题一:双指针(有效三角形的个数)
给定一个数组,找出能组成三角形的三元组个数。利用“两边之和大于第三边”的性质,先排序,再用双指针优化。固定最大边,左右指针从区间两端向内移动,若两短边之和大于最长边,则中间所有组合均有效,时间复杂度由暴力的O(n³)降至O(n²)。
|
2月前
|
存储 算法 编译器
算法入门:剑指offer改编题目:查找总价格为目标值的两个商品
给定递增数组和目标值target,找出两数之和等于target的两个数字。利用双指针法,left从头、right从尾向中间逼近,根据和与target的大小关系调整指针,时间复杂度O(n),空间复杂度O(1)。找不到时返回{-1,-1}。
|
5月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
175 1
|
5月前
|
机器学习/深度学习 数据采集 算法
你天天听“数据挖掘”,可它到底在“挖”啥?——数据挖掘算法入门扫盲篇
你天天听“数据挖掘”,可它到底在“挖”啥?——数据挖掘算法入门扫盲篇
129 0
|
5月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
166 0
|
9月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
262 3
 算法系列之数据结构-Huffman树
|
9月前
|
机器学习/深度学习 算法 机器人
强化学习:时间差分(TD)(SARSA算法和Q-Learning算法)(看不懂算我输专栏)——手把手教你入门强化学习(六)
本文介绍了时间差分法(TD)中的两种经典算法:SARSA和Q-Learning。二者均为无模型强化学习方法,通过与环境交互估算动作价值函数。SARSA是On-Policy算法,采用ε-greedy策略进行动作选择和评估;而Q-Learning为Off-Policy算法,评估时选取下一状态中估值最大的动作。相比动态规划和蒙特卡洛方法,TD算法结合了自举更新与样本更新的优势,实现边行动边学习。文章通过生动的例子解释了两者的差异,并提供了伪代码帮助理解。
702 2
|
2月前
|
机器学习/深度学习 算法 机器人
【水下图像增强融合算法】基于融合的水下图像与视频增强研究(Matlab代码实现)
【水下图像增强融合算法】基于融合的水下图像与视频增强研究(Matlab代码实现)
260 0
|
2月前
|
数据采集 分布式计算 并行计算
mRMR算法实现特征选择-MATLAB
mRMR算法实现特征选择-MATLAB
198 2

热门文章

最新文章