【数据结构】二叉树-堆(top-k问题,堆排序,时间复杂度)

简介: 【数据结构】二叉树-堆(top-k问题,堆排序,时间复杂度)

 


堆排序


第一种


假如左右子树都是小堆,我们只需要进行向下调整建堆即可。


下方是建大堆:


思路分析:这里的向上和向下调整都是建大堆的。如果我们想进行升序排序,我们就得先建大堆。因为小堆的同一层中,无法进行比较。 接着,交换根结点和最后一个结点,这时就已经将最大的数排在末尾了。然后再从根结点向下调整建大堆,这时第二大的数就排在了根结点,再swap进行交换。end--表示不再对已排好的数进行操作。如此循环,即可进行升序。



总结:如果是升序,就要建大堆。如果是降序,就要建小堆。 堆排序的本质是选择排序。


       建堆的时间复杂度:N*logN


       选数的时间复杂度:(N-1)*logN


第二种


当左右子树不一定都是小堆时,我们就要进行从下往上的向下调整建堆。


方法:从倒数第一个非叶子开始(即最后一个节点的父亲)。9,2,8,5不用调,我们从1开始,1和9满足小堆。接着往前一位数到2,此时也满足小堆。继续往前到6,1和5比,1更小,所以1和6交换。接着来到4,交换后的1和2,1更小,4就和1交换


此方法的时间复杂度是O(N),并且此方式只需要一个向下调整,不需要多写一个向上调整函数。



建小堆时,我们就会得到降序的数据。



TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

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


  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆


2.用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。


举例如下:


 


我们先生成一千万个随机数。


topk函数如下:


void PrintTopK(const char* file, int k)
{
  FILE* fout = fopen(file, "r");
  if (fout == NULL)
  {
  perror("fopen fail");
  return;
  }
  //建一个k个数的小堆
  int* minheap = (int*)malloc(sizeof(int) * k);
  if (minheap == NULL)
  {
  perror("malloc fail");
  return;
  }
  //读取前k个,建小堆
  for (int i = 0; i < k;i++)
  {
  fscanf(fout, "%d", &minheap[i]);
  AdjustUp(minheap, i);
  }
  int x = 0;
  while (fscanf(fout, "%d", &x) != EOF)
  {
  if (x > minheap[0])
  {
    minheap[0] = x;
    AdjustDown(minheap, k, 0);
  }
  }
  for (int i = 0; i < k; i++)
  {
  printf("%d ", minheap[i]);
  }
  printf("\n");
  fclose(fout);
}

我们先建立k个数的小堆,并将前k个数放进去。接着从第k+1开始的数开始与根结点比较,如果大于,就替换,然后进行向下调整,恢复成堆的形式。直到所有的数都比较完,我们就能找出前k个最大或最小的数了。


最后的运行结果如下:

 


建堆的时间复杂度

向下调整建堆的时间复杂度:



这里举例向下调整建堆的时间复杂度:


因为第h层是叶,就不需要向下移动了。具体计算过程如下图,因为是等差*等比,所以采用错位相减法进行计算。




因为最后的结果中的h是树的高度,不方便看出时间复杂度,替换成N(节点的个数)



最终,时间复杂度是O(N)。


向上调整建堆的时间复杂度:





上方是求向上调整建堆时间复杂度的计算过程,原理与向下调整的一样。


最终的时间复杂度是:O(N*logN)


补充


上方的过程的时间复杂度是O(N*logN),他跟上方的向上调整建堆相似,都是多*多,少*少的关系。因为最后一层有2^(h-1)个节点,每次交换后,最坏情况要向下调整(h-1)层,即多*多。第1层有一个节点,向下调整0层,即少*少。

目录
相关文章
|
1月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
39 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
25天前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
75 4
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
71 16
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
121 8
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
91 1
|
2月前
|
算法
[数据结构] -- 时间复杂度和空间复杂度
[数据结构] -- 时间复杂度和空间复杂度
17 0
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
159 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
29 1
|
18天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
41 5

热门文章

最新文章