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

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

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月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
49 1
|
2月前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
117 4
|
11天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
49 20
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
65 1
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
236 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
68 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。