数据结构之堆的应用

简介: 数据结构之堆的应用

在我们学习了堆之后我们知道,无论是大堆还是小堆,堆顶的元素要么最大,要么最小。

对于堆插入的时间复杂度为O(logn)也就是向上调整算法的复杂度,删除一个堆中的元素为O(logn),堆在我们日常生活中还是常用到的。

1.top k问题(优质筛选问题)

什么是top k问题?即求数据结合中前k个最大元素或最小元素,一般情况下数据量较大。

假设N是100万,k是100,求100 万中的前10个最大数,当数足够大,排序已经无法实现了,空间太大,无法内存中开辟,在磁盘上开辟(但磁盘中无法建堆),而且磁盘运行速度很慢。

如:求得大量数字中的前k个大或前k个小的数,普通的算法时间复杂度较高,效率较慢,数量太大,无法寻找。利用堆的特性,算法效率高,面对数量大的数据也适用。

1.用数据集合中前k个元素来建堆。

前k个最大元素  建大堆

前k个最小元素  建小堆


c327611d9259434cad2b8de0b64e51a1.png 我们这里设置随机数10个,这里你可以改变,越大越能体现堆的功能,找出其中的最大五个,

思路就是先读取五个数进入数组里,之后再从第k+1个数开始与堆顶比较,比堆顶大就替换,每一次都要向下调整,确保是一个小堆,到最后最大的五个数就是小堆的五个数,第一个是五个里最小的。

#include<stdlib.h> 
#include<stdio.h>
#include<time.h>
void Adjustdown1(int* a, int size, int parent)
{
  int child = parent * 2 + 1;
  while (child < size)
  {
    if ((child + 1 < size) && a[child + 1] < a[child])//若右孩子存在且小于左孩子
    {
      ++child;
    }
    if (a[child] < a[parent])
    {
      //交换
      int tmp = a[child];
      a[child] = a[parent];
      a[parent] = tmp;
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
void creatnDATA()
{
  int n = 10;
  srand(time(NULL));
  const char* file = "data.txt";
  FILE* fp = fopen(file, "w");
  if (fp == NULL)
  {
    perror("fopen fail");
    return;
  }
  for (size_t i = 0; i < n; i++)
  {
    int x = rand() % 100000;
    fprintf(fp, "%d\n", x);
  }
  fclose(fp);
}
void printtopk(int k)
{
  const char* file = "data.txt";
  FILE* fp = fopen(file, "r");
  if (fp == NULL)
  {
    perror("fopen fail");
    return;
  }
  int* kminheap = (int*)malloc(sizeof(int) * k);
  //从文件中读k个数
  for (size_t i = 0; i < k; i++)
  {
    fscanf(fp, "%d", &kminheap[i]);
  }
  //建小堆
  for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  {
    Adjustdown1(kminheap, k, i);
  }
  int val = 0;
  //比较 ,如果比堆顶大,则需要交换,之后再向下调整。
  while (!feof(fp))
  {
    //从第k+1个数处再开始读
    fscanf(fp, "%d", &val);
    //val用来存放读的数 
    if (val > kminheap[0])
    {
       kminheap[0]=val;
      Adjustdown1(kminheap, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", kminheap[i]);
  }
  printf("\n");
}
int main()
{
  creatnDATA();
  printtopk(5);
  return 0;
}

2.堆排序

正常的思维,所谓对堆排序就是用堆这个数据结构来实现排序,思路也很简单,取堆头存入数组,向下调整,在出堆,循环往复。

如下,在有堆的前提,我们可以实现堆排序:

//前提有堆的实现
void heapsort(int *a,int n)
{
  HP f;
  Heapinit(&f);
  for (int i = 0; i < n; i++)
  {
    Heappush(&f, a[i]);
  }
      int i = 0;
  while (!HeapEmpty(&f))
  {
    //进行排序
    int top = HeapTop(&f);
    a[i++] = top;
    Heappop(&f);
  }
}
int main()
{
  int a[] = {10,6,9,3,5,7,65};
  heapsort(a,sizeof(a)/sizeof(int));
  for (int i=0; i < sizeof(a) / sizeof(int); i++)
  {
    printf("%d ", a[i]);
  }
}

但我们也会发现其空间复杂度较高,需要拷贝数据,虽然这个堆排序效率还行,但是我们又看到想要以这种方法实现我们还的需要实现堆的所有操作!!!!这太麻烦了,那这种排序又有谁会用呢?

实则不然,真正的堆排序是利用堆的思路,我们是把传进来这个数组直接建堆,省去一部分操作,提高效率。因此建堆又有两种方式:

1.向上调整建堆

给出排序的数组,以此来建堆,根据堆的结构  

建小堆-----最后排序为降序


c4af32149fc1459db7faef6142eb4e6e.png

void Adjustup1(HPDATAtype* a, int child)
{
  //根据孩子找父亲
  int parent = (child - 1) / 2;
  while (child > 0)
  {
    if (a[child] < a[parent])
    {
      HPDATAtype p = a[child];
      a[child] = a[parent];
      a[parent] = p;
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}
void Adjustdown1(HPDATAtype* a, int size,int parent)
{
  int child =parent * 2 +1;
  while (child<size)
  {
    if ((child+1<size)&&a[child + 1] <a[child])//若右孩子存在且小于左孩子
    {
      ++child;
    }
    if (a[child] < a[parent])
    {
      //交换
      int tmp = a[child];
      a[child] = a[parent];
      a[parent] = tmp;
      parent = child;
      child = parent * 2 + 1;
    }else
    {
      break;
    }
  }
}
void heapsort1(int* a, int n)
{
  //向上调整建堆
  //从第一个元素开始就向上调整,保证前面都是堆结构,建小堆
  for (int i = 0; i < n; i++)
  {
    Adjustup1(a, i);
  }
  //在调整  ,选出一个个次小的数
  int end = n - 1;
  while (end > 0)
  {
    //把最小的数据交换到最后一个位置
    int tmp = a[0];
    a[0] = a[end];
    a[end] = tmp;
  Adjustdown1(a, end, 0);
  --end;
  }
}

建大堆------最后排序为升序


7fedda6da49a4ea0a0079600d2772937.png

void Adjustup2(HPDATAtype*a, int child)
{
  //根据孩子找父亲
  int parent = (child - 1) / 2;
  while (child>0)
  {
    if ( a[child]>a[parent])
    {
      HPDATAtype p = a[child];
      a [child] = a[parent];
      a[parent]=p;
      child = parent;
      parent = (child - 1) / 2;
    }
    else 
    {
      break;
    }
  }
}
void Adjustdown2(HPDATAtype* a, int size, int parent)
{
  int child = parent * 2 + 1;
  while (child < size)
  {
    if ((child + 1 < size) && a[child + 1] > a[child])//若右孩子存在且小于左孩子
    {
      ++child;
    }
    if (a[child] > a[parent])
    {
      //交换
      int tmp = a[child];
      a[child] = a[parent];
      a[parent] = tmp;
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
void heapsort2(int* a, int n)
{
  //向上调整建堆
  //从第一个元素开始就向上调整,保证前面都是堆结构,建大堆
  for (int i = 0; i < n; i++)
  {
    Adjustup2(a, i);
  }
  //在调整  ,选出一个个次小的数
  int end = n - 1;
  while (end > 0)
  {
    //把最小的数据交换到最后一个位置
    int tmp = a[0];
    a[0] = a[end];
    a[end] = tmp;
    Adjustdown2(a, end, 0);
    --end;
  }
}

利用向上调整算法建堆,时间复杂度O(N*logN),这里是以2为底N的对数。

2.向下调整建堆

建堆与向上调整建堆是相反的,向下调整建堆,从数组后面开始调整,也就是从二叉树后面开始从前调整,知道符合堆的特性。

向下调整建堆也分建大堆还是小堆,根据你设计向下调整算法时,取决于条件-孩子大与父亲还是父亲大于孩子。

这里建小堆,调整数组是下标从中间开始的。

建小堆--排序结果为降序

void Adjustdown1(HPDATAtype* a, int size,int parent)
{
  int child =parent * 2 +1;
  while (child<size)
  {
    if ((child+1<size)&&a[child + 1] <a[child])//若右孩子存在且小于左孩子
    {
      ++child;
    }
    if (a[child] < a[parent])
    {
      //交换
      int tmp = a[child];
      a[child] = a[parent];
      a[parent] = tmp;
      parent = child;
      child = parent * 2 + 1;
    }else
    {
      break;
    }
  }
}
//向下调整建堆
void heapsort4(int* a, int n)
{
  HP f;
  Heapinit(&f);
  //向下调整建小堆
  //与向生调整建堆相反,这里是从数组后面开始调整堆,也就是从二叉树的下面开始
  for (int i =(n-1-1)/2 ; i >=0; i--)
  {
    Adjustdown1(a, n, i);
  }
  int end = n-1;
  while (end>0)
  {
    //进行排序
    int tmp = a[0];
    a[0] = a[end];
    a[end] = tmp;
    Adjustdown1(a, end, 0);
    --end;
  }
}

建大堆---排序结果为升序

void Adjustdown2(HPDATAtype* a, int size, int parent)
{
  int child = parent * 2 + 1;
  while (child < size)
  {
    if ((child + 1 < size) && a[child + 1] > a[child])//若右孩子存在且小于左孩子
    {
      ++child;
    }
    if (a[child] > a[parent])
    {
      //交换
      int tmp = a[child];
      a[child] = a[parent];
      a[parent] = tmp;
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
void heapsort5(int* a, int n)
{
  HP f;
  Heapinit(&f);
  //向下调整建大堆
  //与向生调整建堆相反,这里是从数组后面开始调整堆,也就是从二叉树的下面开始
  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  {
    Adjustdown2(a, n, i);
  }
  int end = n - 1;
  while (end > 0)
  {
    //进行排序
    int tmp = a[0];
    a[0] = a[end];
    a[end] = tmp;
    Adjustdown2(a, end, 0);
    --end;
  }
}

向上与向下比较

根据计算我们发现向下调整建堆是优于向上调整建堆的,这里的对于向下调整算法实现复杂度是O(N),向上调整算法的时间复杂度为(N*logN),应为向下调整算法,就如同在数组中插入,而向上调整算法经过错位相减或等比数列法计算大概为N*logN)。

相关文章
|
1月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
39 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
2月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
50 1
|
2月前
|
存储 算法 C语言
通义灵码在考研C语言和数据结构中的应用实践 1-5
通义灵码在考研C语言和数据结构中的应用实践,体验通义灵码的强大思路。《趣学C语言和数据结构100例》精选了五个经典问题及其解决方案,包括求最大公约数和最小公倍数、统计字符类型、求特殊数列和、计算阶乘和双阶乘、以及求斐波那契数列的前20项和。通过这些实例,帮助读者掌握C语言的基本语法和常用算法,提升编程能力。
89 4
|
24天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
45 5
|
23天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
40 5
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
81 16
|
2月前
|
机器学习/深度学习 存储 人工智能
数据结构在实际开发中的广泛应用
【10月更文挑战第20天】数据结构是软件开发的基础,它们贯穿于各种应用场景中,为解决实际问题提供了有力的支持。不同的数据结构具有不同的特点和优势,开发者需要根据具体需求选择合适的数据结构,以实现高效、可靠的程序设计。
116 7
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
100 1
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆