【数据结构】如何应用堆解决海量数据的问题

简介: 堆(Heap数据结构堆在计算机科学中有着广泛的应用,今天来介绍两种堆的应用:堆排序、Top-k问题🍉堆排序​ 堆排序是一种基于堆数据结构的排序算法。它的基本思想是,将待排序的序列构建成一个大根堆(或小根堆),然后依次取出堆顶元素(即最大值或最小值),将其放入已排序序列的末尾,再将剩余的元素重新调整为一个新的堆。重复这个过程,直到所有元素都被取出并放入已排序序列中。

987f32ef7a3c441295346b01e82a1fe2.png

堆(Heap数据结构堆在计算机科学中有着广泛的应用,今天来介绍两种堆的应用:堆排序、Top-k问题🍉

堆排序

 排序是一种基于堆数据结构的排序算法。它的基本思想是,将待排序的序列构建成一个大根堆(或小根堆),然后依次取出堆顶元素(即最大值或最小值),将其放入已排序序列的末尾,再将剩余的元素重新调整为一个新的堆。重复这个过程,直到所有元素都被取出并放入已排序序列中。

具体来说,堆排序的过程如下:

  1. 将待排序的长度为n序列构建成一个大根堆(或小根堆)。这个过程可以从最后一个非叶子节点开始,依次向前进行,保证每个子树都是一个大根堆(或小根堆)。
  2. 取出堆顶元素(即最大值或最小值),将其放入已排序序列的末尾。
  3. 将剩余(n-1)的元素重新调整为一个新的堆。
  4. 重复步骤 2 和步骤 3,直到所有元素都被取出并放入已排序序列中。最终得到的序列就是排好序的。

最终得到的序列就是排好序的。

堆排序的时间复杂度为 O(nlogn),空间复杂度为 O(1)。


5df9c0522583457cb35dc31bbbe59932.png

向下调整法

从非叶节点的最后一个数据的下标开始,每次取出孩子中较大或较小的数(看是大堆还是小堆)向下进行调整,由于每多一层,下层是上层的二倍,这种办法直接可以省略掉最后一层,也可以达到建堆的目的,所以这种办法为更优的办法。

由于需要向下调整,所以这种办法需要找到子节点,我们已经知道父结点的运算了,子结点就是父节点的逆运算。

结合上面所说,实现代码如下:

void AdjustDown(HeapDataType* arr, int n, int parent)//向下调整
{
  assert(arr);
  int child = parent * 2 + 1;
  while (child < n)
  {
    if (child<n - 1 && arr[child] > arr[child + 1])
    {
      child = child + 1;
    }
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
    }
    parent = child;
    child = child * 2 + 1;
  }
}
void HeapSort(int* a,int n)//堆排序
{
  for (int i = (n - 2) / 2; i >= 0; i--)
  {
    AdjustDown(a, n,i);
  }
  for (int i = n-1; i > 0; i--)
  {
    swap(&a[0], &a[i]);
    AdjustDown(a, i, 0);
  }
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
}
int main()
{
  int arr[] = { 1,4,6,2,4,8,5,8,3,111,4,5,32,44 };
  HeapSort(arr, sizeof(arr) / sizeof(int));
}

Top-k问题

Top-k 问题是指在一个数据集中找到前 k 个最大(或最小)的元素。一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

下面是使用堆排序实现 Top-k 问题的具体步骤:

  1. 创建一个大小为 k 的小根堆,用于存储当前的前 k 个最大元素。
  2. 将前 k 个元素插入小根堆中。
  1. 遍历剩余的元素,对于每个元素执行以下操作:
  • 如果当前元素比堆顶元素大,则将堆顶元素弹出,再将当前元素插入堆中。
  1. 遍历完所有元素后,小根堆中剩余的 k 个元素就是前 k 个最大元素。

使用堆排序实现 Top-k 问题的时间复杂度为 O(nlogk),空间复杂度为 O(k),其中 n 是数据集的大小。这种方法适用于数据集较大的情况,但需要额外的空间来存储堆。

代码实现

  • 生成一个有10000随机数的文件
void CreateNDate()  //生成一个有10000个随机数的文件
{
  int n = 10000;
  srand(time(0));
  const char* file = "data.txt";
  FILE* fin = fopen(file, "w");
  if (fin == NULL)
  {
    perror("fopen error");
    return;
  }
  for (int i = 0; i < n; i++)
  {
    int x = rand() % 10000;
    fprintf(fin, "%d\n", x);
  }
  fclose(fin);
}

按上述步骤进行排序

void AdjustDown(HeapDataType* arr, int n, int parent)//向下调整
{
  assert(arr);
  int child = parent * 2 + 1;
  while (child < n)
  {
    if (child<n - 1 && arr[child] > arr[child + 1])
    {
      child = child + 1;
    }
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
    }
    parent = child;
    child = child * 2 + 1;
  }
}
void PrintTopK(int k)
{
  const char* file = "data.txt";
  FILE* fin = fopen(file, "r");
  if (fin == NULL)
  {
    perror("fopen error");
    return;
  }
  int* heap = (int*)malloc(k * sizeof(int));
  int x;
  for (int i = 0; i < 5; i++)
  {
    fscanf(fin,"%d",&heap[i] );//将前k个元素放到数组里
  }
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)  //将k个元素建立一个小堆
  {
    AdjustDown(heap, k, i);
  }
  while (!feof(fin))
  {
    fscanf(fin, "%d", &x);
    if (heap[0] < x)
    {
      heap[0] = x;    //将剩余n-k个元素依次与堆顶元素交换,不满则则替换
      AdjustDown(heap,k,0);
    }
  }
  fclose(fin);
  for (int i = 0; i < k; i++)
  {
    printf("%d  ", heap[i]);
  }
}
int main()
{
  CreateNDate();
  PrintTopK(10);
}


d7d3c9764adc43d09c554697b8c3b851.gif

✨本文收录于数据结构理解与实现

当你喜欢一篇文章时,点赞、收藏和关注是最好的支持方式。如果你喜欢我的文章,请不要吝啬你的支持,点赞👍、收藏⭐和关注都是对我最好的鼓励。感谢你们的支持!









































相关文章
|
11天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
21 5
【数据结构】优先级队列(堆)从实现到应用详解
|
22天前
|
存储 机器学习/深度学习
【数据结构】二叉树全攻略,从实现到应用详解
本文介绍了树形结构及其重要类型——二叉树。树由若干节点组成,具有层次关系。二叉树每个节点最多有两个子树,分为左子树和右子树。文中详细描述了二叉树的不同类型,如完全二叉树、满二叉树、平衡二叉树及搜索二叉树,并阐述了二叉树的基本性质与存储方式。此外,还介绍了二叉树的实现方法,包括节点定义、遍历方式(前序、中序、后序、层序遍历),并提供了多个示例代码,帮助理解二叉树的基本操作。
42 13
【数据结构】二叉树全攻略,从实现到应用详解
|
23天前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
41 10
【数据结构】链表从实现到应用,保姆级攻略
|
1天前
|
JSON 前端开发 JavaScript
一文了解树在前端中的应用,掌握数据结构中树的生命线
该文章详细介绍了树这一数据结构在前端开发中的应用,包括树的基本概念、遍历方法(如深度优先遍历、广度优先遍历)以及二叉树的先序、中序、后序遍历,并通过实例代码展示了如何在JavaScript中实现这些遍历算法。此外,文章还探讨了树结构在处理JSON数据时的应用场景。
一文了解树在前端中的应用,掌握数据结构中树的生命线
|
19天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
20天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
23 0
【数据结构】栈和队列的深度探索,从实现到应用详解
|
1月前
栈的几个经典应用,真的绝了
文章总结了栈的几个经典应用场景,包括使用两个栈来实现队列的功能以及利用栈进行对称匹配,并通过LeetCode上的题目示例展示了栈在实际问题中的应用。
栈的几个经典应用,真的绝了
|
1月前
|
机器学习/深度学习 人工智能 算法
【人工智能】线性回归模型:数据结构、算法详解与人工智能应用,附代码实现
线性回归是一种预测性建模技术,它研究的是因变量(目标)和自变量(特征)之间的关系。这种关系可以表示为一个线性方程,其中因变量是自变量的线性组合。
43 2
|
1月前
|
存储 算法
【数据结构】堆
【数据结构】堆
|
1月前
|
搜索推荐 算法 Go
深入探索堆:Go语言中的高效数据结构
深入探索堆:Go语言中的高效数据结构