二叉树——堆的排序 TOP-K算法

简介: 二叉树——堆的排序 TOP-K算法

堆的排序

这里排序无非就是升序和降序,那么,之前用的冒泡排序时间复杂度是很高的,所以这次来了解一个更加高效率的。

建堆

建堆的过程有两种,一是向上调整法,另一种是向下调整法,但是更快的是向下调整法,因为堆是类似于一个三角形,越往下,元素就越多,向上调整,堆顶的结点一定不用在向上调整了,因为堆顶上面没有父节点了,而向下调整法是叶子结点不用在向下调整了,因为叶子结点是最后的结点,下面没有子结点。

那么,肯定是向下调整法的时间复杂度比较低。

代码实现

void swap(int* p1, int* p2)//交换数据
{
  int p = *p1;
  *p1 = *p2;
  *p2 = p;
}
void upward(int* a,int size,int parent)//向下调整
{
  int minchild = 2 * parent + 1;//表示最小的孩子,第一次先假设左孩子最小
  while (minchild < size)//防止数组越界
  {
    if (minchild + 1 < size && a[minchild + 1] > a[minchild])//防止右孩子出界
    {
      minchild++;//如果右孩子比左孩子小就让右孩子等于最小
    }
    if (a[minchild] > a[parent])//判断是否需要向下调整
    {
      swap(&a[minchild], &a[parent]);
      parent = minchild;
      minchild = 2 * parent + 1;
    }
    else
    {
      break;
    }
  }
}
void sort()
{
  int arr[] = { 10,6,3,4,9,10,81 };//随机数组
  int n = sizeof(arr) / sizeof(arr[0]);//数组有效数的长度
  for (int i = (n - 1 - 1) / 2; i >= 0; --i)//这里(n-1-1)/2是因为找下标需要-1,然后剩下公式的是找到最后一个叶子节点的父母
  {//遍历除叶子结点外所有结点
    upward(arr, n, i);
  }
}

利用堆删除思想来进行排序

建堆的时间复杂度是O(N),之前说过向下查找法和向上查找法,时间复杂度是O(logN).

大堆是父结点比子结点大,小堆则相反,我们用堆排序的核心思路是类似于删除的逻辑,因为在堆顶不是最大的就是最小的,我们把堆顶的元素和尾部交换然后再进行排序就可以了。

那么,降序要用小堆,升序用大堆。

例:降序,图像理解

然后让5之前的数进行向下调整,从而不打乱小堆的结构。

再让堆顶的10与30交换

最后以此类推。

例:升序,代码实现

void swap(int* p1, int* p2)//交换数据
{
  int p = *p1;
  *p1 = *p2;
  *p2 = p;
}
void upward(int* a,int size,int parent)//向下调整
{
  int maxchild = 2 * parent + 1;//表示最大的孩子,第一次先假设左孩子最小
  while (maxchild < size)//防止数组越界
  {
    if (maxchild + 1 < size && a[maxchild + 1] > a[maxchild])//防止右孩子出界
    {
      maxchild++;//如果右孩子比左孩子大就让右孩子等于最大
    }
    if (a[maxchild] > a[parent])//判断是否需要向下调整
    {
      swap(&a[maxchild], &a[parent]);
      parent = maxchild;
      maxchild = 2 * parent + 1;
    }
    else
    {
      break;
    }
  }
}
void sort()
{
  int arr[] = { 10,6,3,4,9,10,81 };//随机数组
  int n = sizeof(arr) / sizeof(arr[0]);//数组有效数的长度
  for (int i = (n - 1 - 1) / 2; i >= 0; --i)//这里(n-1-1)/2是因为找下标需要-1,然后剩下公式的是找到最后一个叶子节点的父母
  {//遍历除叶子结点外所有结点
    upward(arr, n, i);//建大堆
  }
  int i = 1;
  while (i < n)//因为最终要排序n-1次
  {
    swap(&arr[0],&arr[n-i]);//交换首尾
    upward(arr, n - i, 0);//向下调整,保持小堆
    ++i;
  }
  int j = 0;
  for (j = 0; j < n; ++j)
  {
    printf("%d ", arr[j]);
  }
}

TOP-K算法

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于Top-K问题,能想到的最简单直接的方式就是排序O(N*logN),但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

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

假如数组里面的数有N个(非常大),然后找K(非常小)前面的元素,那么就用建立大堆然后再进行删除的操作,也就是首尾交换。

例如:如果是寻找数组中前K个最大元素,那么时间复杂度就是建堆和找数的时间,找数只要找K次就可以了。O(N+logN*K)

但是如果N很大,K很小时间复杂度也是很高,所以这里创建小堆。

小堆方法

用数组中前K个数建立一个小堆,小堆的容量也就是我们需要的数量,这时就需要交换小堆里面的数了,因为堆顶的数是最小的,所以我们遍历K后面的数,也就是N-K个数,如果比堆顶的大就和堆顶交换,之后再进行向下调整。

时间复杂度为O(K+logK*(N-K))

因为K很小的原因,所以就约等于O(N)

结论如下:

  1. 前k个最大的元素,则建小堆
  2. 前k个最小的元素,则建大堆

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

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

代码实现

void swap(int* p1, int* p2)//交换数据
{
  int p = *p1;
  *p1 = *p2;
  *p2 = p;
}
void upward(int* a,int size,int parent)//向下调整
{
  int minchild = 2 * parent + 1;//表示最小的孩子,第一次先假设左孩子最小
  while (minchild < size)//防止数组越界
  {
    if (minchild + 1 < size && a[minchild + 1] < a[minchild])//防止右孩子出界
    {
      minchild++;//如果右孩子比左孩子小就让右孩子等于最小
    }
    if (a[minchild] < a[parent])//判断是否需要向下调整
    {
      swap(&a[minchild], &a[parent]);
      parent = minchild;
      minchild = 2 * parent + 1;
    }
    else
    {
      break;
    }
  }
}
void random(char* p, int n)//向文件中写入随机数
{
  FILE* p1 = fopen(p, "w");//写文件,让p1指向文件位置
  if (p1 == NULL)
  {
    perror("fopen fail");
    exit(-1);
  }
  srand(time(0));//随机数的起点
  for (int i = 0; i < n; ++i)//向文件中写入随机数
  {
    fprintf(p1, "%d ", rand()%1000);//这里不让随机数超过1000方便观看
  }
  fclose(p1);
}
void TestTopk(char* p, int k)
{
  FILE* p1 = fopen(p, "r");//打开文件,让p1指向文件位置
  if (p1 == NULL)
  {
    perror("fopen fail");
    exit(-1);
  }
  int* p2 = (int*)malloc(sizeof(int) * k);//开辟堆的空间
  if (p2 == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  for (int i = 0; i < k; i++)//读取k之前的数
  {
    fscanf(p1, "%d", &p2[i]);//把文件前k歌数读取到新建堆中
  }
  for (int j = (k - 1 - 1) / 2; j >= 0; --j)
  {
    upward(p2, k, j);//建小堆
  }
  int a = 0;
  while (fscanf(p1, "%d", &a) != EOF)//读取K之后的数
  {
    if (a > p2[0])
    {
      p2[0] = a;
      upward(p2, k, 0);//向下调整
    }
  }
  for (int i = 0; i < k; ++i)
  {
    printf("%d ", p2[i]);//打印k之前的数据
  }
  fclose(p1);
  free(p2);
}
int main()
{
  const char* p = "TOP-K.txt";//自己创建一个文件
  int k = 10;
  int n = 100;
  random(p, n);
  TestTopk(p, k);
  return 0;
}


相关文章
|
12天前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
43 5
|
2月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
2月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
150 7
|
2月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
124 8
|
2月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
67 5
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
110 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
52 0
|
3月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
33 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
3月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
36 0
|
12天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
145 80

热门文章

最新文章