对二叉树堆排序的升级&TOPK问题(跑路人笔记)

简介: 对二叉树堆排序的升级&TOPK问题(跑路人笔记)

前言

对堆排序进行一下升级,我们之前使用堆需要先实现一下整个堆的函数而且还要将数据导入到堆结构单独开辟的空间内,非常没必要所以我们为什么不直接将我们的原数组内的元素直接当做堆来进行堆排序的所需呢?

说搞就搞


堆的应用

简易实现堆

之前我们通过实现堆的代码然后用堆代码来实现我们的堆排序,这样的堆排序的


空间复杂度O(N)


时间复杂度为O(N)


代码如下:


void HeapSort(int* a, int size)
{
  Heap hp;
  HeapCreat(&hp);
  for (int i = 0; i < size; ++i)
  {
  HeapPush(&hp, a[i]);
  }
  size_t j = 0;
  while (!HeapEmpty(&hp))
  {
  a[j] = HeapTop(&hp);
  j++;
  HeapPop(&hp);
  }
  HeapDestory(&hp);
}



而且我们想使用这样的堆排序甚至要专门搞个堆来实现.非常痛苦.


其实我们完全可以通过稍微更改一下向上和向下调整函数来实现堆排序


调整后的函数我通过注释的方式进行讲解吧=-=.毕竟之前就讲过

void Swap(HPDataType* a, HPDataType* b)
{
  HPDataType tmp = *a;
  *a = *b;
  *b = tmp;
}
void AdjustDown(HPDataType* a, size_t size, size_t root)//root就是我们要调整的父亲节点.
{
  size_t parent = root;
  size_t child = parent * 2 + 1;//公式得到的child是parent的左孩子
  while (child < size)
  {
    // 1、根据我们需要的情况去调整我们的大于小于号
    if (child + 1 < size && a[child + 1] < a[child])
    {
      ++child;
    }
    // 2、孩子与父亲比较为真则交换,并继续往下调整
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
void AdjustUp(HPDataType* a, size_t child)
{
  size_t parent = (child - 1) / 2;//公式无论是左孩子还是右孩子都可以得到我们的parent
  while (child > 0)
  {
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}

我们得到调整后的函数后我们就可以将传来的数组调整成堆形式然后通过一个手段(会讲的啦)来得到我们想要的排序结果.


我们主要讲一下我们用向下调整和向上调整形成堆的时间复杂度.


堆的排序我们要使用向下调整的方式来实现.


首先我们先得到我们的堆可以通过向上调整和向下调整两种方式得到.


首先我们先将一下向上调整的方式的代码和时间复杂度讲一下.


for (int i = 1; i < n; i++)
  {
  AdjustUp(a, i);
  }


向上调整的函数直接将所有的元素都检查一下并将所有位置都进行调整,下面这种的调整也许大家更容易理解


for (int i = n; i >0; i--)
  {
  AdjustUp(a, i);
  }



我们来讲一下他的时间复杂度.


假设我们每次的AdjustUp都是最坏的情况及从尾一直走到头,及执行了他树高度的次数,我们知道他的N值通过公式的h = log2(N+1)(以2为底的log).


再因为for循环每次循环次数从n~1而每层节点数如下.

image.png



我们以满二叉树为例,因为我们的cpu的运算能力就算少哪几个节点或多几个节点对时间影响不大.


所以我们可以列出公式如下:


image.png


这就是很经典的等比数列乘以等差数列了,我们直接用错位相减的方式可得:


image.png


然后又有N= 2h -1


所以我们的时间复杂度就为O(N*logN).


向下调整的函数的时间复杂度我们也稍微讲一下.


先看看我们使用的方式吧.


for (int i = (n - 1 - 1) / 2; i >= 0; --i)
  {
  AdjustDown(a, n, i);
    //void AdjustDown(HPDataType* a, size_t size, size_t root)//root就是我们要调整的父亲节点.
  }


相信大家注意到了我们这个int i = (n-1-1)/2这个怪怪的表达式了.


我们这个是需要从最后一个非叶节点开始调整的,n的第一次-1是为了找到最后一个节点,再-1是因为我们要找父节点的公式所以可以解读长((n-1)-1)/2,其中的n-1就是为了得到最后一个节点的下标.


既然读懂了这个步骤的操作我们就来看看他的时间复杂度吧.


类似于我们的向上调整这个时间的复杂度我们也用满二叉树来讲解.


image.png


我们的向下调整建堆就比向上调整建堆减少了一层.


所以根据最坏次数得到的时间复杂度如下公式


假设有n个元素h层


image.png


依旧是错位相减即可(高中知识啊).


最后得

image.png



所以时间复杂度为O(N)


堆排序

OK.现在我们所有的方式的建堆都得到了.


现在我们想用这些堆来实现堆排序我们需要做什么呢.


这时候我们要通过向下调整来得到我们的排序结果.


使用向下调整的原因也很简单.因为向上调整函数会破坏我们已经创建好的堆结构.而向下调整不会.


不过我们不能普普通通的使用AdjustDown函数我们需要更改结尾位置


int end = n - 1;
  while (end > 0)
  {
  Swap(&a[0], &a[end]);
  AdjustDown(a, end, 0);
  end--;
  }



为什么我们要end–呢?


其实很简单我们要通过更改end的位置将根节点(a[0])传输到从数值的倒数第一的位置再到倒数第二的位置…这样我们的排序就完成了.🤪


TOPK问题

TOPK问题分为两种,


第一种总体N和K的数值差距不大 如: 从50个人里找到前10.


第二种总体N和K的数值相差巨大 如:从10亿个人里找到前十


第一种我们直接通过排序或者建堆然后用HeapPop函数来得到我们的值


不过如果是第二种情况呢?


总不能建立一个10亿的堆吧.我们这10亿个数据都存储在文件中的时候我们如何得到前K的数据呢?


其实我们可以建一个只有K个数值的小堆,然后我们遍历那个10亿的数据发现比小堆根节点根节点大的就将其进行调换并重新将堆格式进行调整.


这样我们就得到了K个最大的值,如果还想进行排序就简单多了.


结尾

下一篇我的计划是将分治思想.也不知道会有多大的篇幅.🤪


相关文章
|
机器学习/深度学习 存储 编解码
自编码器模型详解与实现(采用TensorFlow2实现)
自编码器的基本构建块是编码器和解码器。编码器负责将高维输入减少为一些低维潜(隐)变量。解码器是将隐变量转换回高维空间的模块。本文对自编码器的原理进行详解,同时使用tensorflow2实现自编码器。
1993 0
自编码器模型详解与实现(采用TensorFlow2实现)
|
6月前
|
NoSQL 应用服务中间件 Redis
Docker 常用命令整理
Docker 常用命令整理
156 1
|
8月前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
194 22
解析静态代理IP改善游戏体验的原理
|
11月前
|
设计模式 前端开发 JavaScript
揭秘!前端大牛们如何巧妙利用JavaScript,打造智能交互体验!
【10月更文挑战第30天】前端开发领域充满了无限可能与创意,JavaScript作为核心语言,凭借强大的功能和灵活性,成为打造智能交互体验的重要工具。本文介绍前端大牛如何利用JavaScript实现平滑滚动、复杂动画、实时数据更新和智能表单验证等效果,展示了JavaScript的多样性和强大能力。
254 4
|
数据可视化 算法 Java
了解go语言运行时工具的作用
【5月更文挑战第16天】本文简介`runtime`库提供系统调用包装、执行跟踪、内存分配统计、运行时指标和剖析支持。`internal/syscall`封装系统调用,保证uintptr参数有效。`trace`用于执行跟踪,捕获各种事件,如goroutine活动、系统调用和GC事件。`ReadMemStats`提供内存分配器统计。`metrics`接口访问运行时定义的度量,包括CPU使用、GC和内存信息。`coverage`支持代码覆盖率分析,`cgo`处理C语言交互,`pprof`提供性能剖析工具集成。这些功能帮助优化和理解Go程序的运行行为。
170 6
|
应用服务中间件 Linux nginx
Docker简介与云平台结合
Docker是一种开源的容器化平台,它通过将应用程序和其依赖项打包到一个独立的容器中,提供了一种轻量级、可移植和可扩展的软件交付解决方案。下面将介绍Docker的功能和优点:
435 1
|
网络协议 关系型数据库 MySQL
利用mysqldump 将一个表按条件导出数据
mysqldump -uroot -pdsideal -t dsideal_db t_resource_info --where="res_type=1 and group_id=1 and ts>2015122115005600474 ORDER BY TS DESC LIMIT 1" --triggers=false --replace > /usr/local/info.
1526 0
Vue3 watch监听reactive中的属性变化
Vue3 watch监听reactive中的属性变化
403 0
|
负载均衡 监控 微服务
手把手教你搭建SpringCloud项目(九)集成OpenFeign服务接口调用
手把手教你搭建SpringCloud项目(九)集成OpenFeign服务接口调用
407 0