首页 > 其他分享 >堆的原理以及实现O(lgn)

堆的原理以及实现O(lgn)

时间:2023-09-27 17:46:55浏览次数:48  
标签:标号 index arr lgn 实现 len 原理 节点 left

大家好,我是蓝胖子,我一直相信编程是一门实践性的技术,其中算法也不例外,初学者可能往往对它可望而不可及,觉得很难,学了又忘,忘其实是由于没有真正搞懂算法的应用场景,所以我准备出一个系列,囊括我们在日常开发中常用的算法,并结合实际的应用场景,真正的感受算法的魅力。

今天我们就来看看堆这种数据结构。

源码已经上传到github

https://github.com/HobbyBear/codelearning/tree/master/heap

原理

在详细介绍堆之前,先来看一种场景,很多时候我们并不需要对所有元素进行排序,而只需要取其中前topN的元素,这样的情况如果按性能较好的排序算法,比如归并或者快排需要n*log( n)的时间复杂度,n为数据总量,排好序后取出前N条数据,而如果用堆这种数据结构则可以在n*log(N)的时间复杂度内找到这N条数据,N的数据量远远小于数据总量n。

接着我们来看看堆的定义和性质,堆是一种树状结构,且分为最小堆和最大堆,最大堆的性质有父节点大于左右子节点,最小堆的性质则是父节点小于左右子节点。如下图所示:

image.png

并且堆是一颗完全二叉树,完全二叉树的定义如下:

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

因为结点都集中在左侧,所以我们可以从上到下,从左到右对堆中节点进行标号,如下图所示:

image.png

从0开始对堆中节点进行标号后,可以得到以下规律:

父节点标号 = (子节点标号-1)/2
左节点标号 = 父节点标号 *2 + 1
右节点标号 = 父节点标号 *2 + 2

有了标号和父子节点的标号间的关系,我们可以用一个数组来保存堆这种数据结构,下面以构建一个最大堆为例,介绍两种构建堆的方式。

HeapInsert

heapInsert的方式是从零开始,逐个往堆中插入数组中的元素,并不断调整新的节点,让新节点的父节点满足最大堆父节点大于其子节点的性质,这个调整的过程被称作ShiftUp。当数组中元素全部插入完成时,就构建了一个最大堆。代码如下:

func HeapInsert(arr []int) *Heap {  
   h := &Heap{arr: make([]int, 0, len(arr))}  
   for _, num := range arr {  
      h.Insert(num)  
   }  
   return h  
}

Heapify

heapify的方式是假设数组已经是一个完全二叉树了,然后找到树中的最后一个非叶子节点,然后通过比较它与其子节点的大小关系,让其满足最大堆的父节点大于其子节点的性质,这样的操作被称作ShifDown,对每个非叶子节点都执行ShifDown操作,直至根节点,这样就达到了将一个普通数组变成一个堆的目的。

如果堆的长度是n,那么最后一个非叶子节点是 n/2 -1 ,所以可以写出如下逻辑,

func Heapify(arr []int) *Heap {  
   h := &Heap{arr: arr}  
   lastNotLeaf := len(arr)/ 2 -1  
   for i:= lastNotLeaf;i >= 0; i-- {  
      h.ShiftDown(i)  
   }  
   return h  
}

取出根节点

取出根节点的逻辑比较容易,将根节点结果保存,之后让它与堆中最后一个节点交换位置,然后从索引0开始进行ShiftDown操作,就又能让整个数组变成一个堆了。

func (h *Heap) Pop() int {  
   num := h.arr[0]  
   swap(h.arr, 0, len(h.arr)-1)  
   h.arr = h.arr[:len(h.arr)-1]  
   h.ShiftDown(0)  
   return num  
}

ShiftUp,ShiftDown实现

下面我将shiftUp和shiftDown的源码展示出来,它们都是一个递归操作,因为在每次shiftUp或者shiftDown成功后,其父节点或者子节点还要继续执行shifUp或shiftDown操作。

// 从标号为index的节点开始做shifUp操作  
func (h *Heap) ShiftUp(index int) {  
   if index == 0 {  
      return  
   }  
   parent := (index - 1) / 2  
   if h.arr[parent] < h.arr[index] {  
      swap(h.arr, parent, index)  
      h.ShiftUp(parent)  
   }  
}  
  
// 从标号为index的节点开始做shifDown操作  
func (h *Heap) ShiftDown(index int) {  
   left := index*2 + 1  
   right := index*2 + 2  
   if left < len(h.arr) && right < len(h.arr) {  
      if h.arr[left] >= h.arr[right] && h.arr[left] > h.arr[index] {  
         swap(h.arr, left, index)  
         h.ShiftDown(left)  
      }  
      if h.arr[right] > h.arr[left] && h.arr[right] > h.arr[index] {  
         swap(h.arr, right, index)  
         h.ShiftDown(right)  
      }  
   }  
   if left >= len(h.arr) {  
      return  
   }  
   if right >= len(h.arr) {  
      if h.arr[left] > h.arr[index] {  
         swap(h.arr, left, index)  
         h.ShiftDown(left)  
      }  
   }  
}

标签:标号,index,arr,lgn,实现,len,原理,节点,left
From: https://www.cnblogs.com/hobbybear/p/17733262.html

相关文章

  • 【Java】SpringBoot邮件发送实现
    Springboot3邮件发送哔哩哔哩萌狼蓝天微信公众号萌狼蓝天依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>配置这里我用的是网易免费企......
  • 记录一次使用MP的TableNameHandler实现简单的分表需求
    1.使用场景MybatisPlus3.4.0及以上版本有简单的分表需求,项目不允许嵌入其他分库分表框架2.具体使用2.1TableNameHandler介绍TableNameHandler是MP提供的一个动态表名处理接口,其原理是通过MP拓展的拦截器(DynamicTableNameInnerInterceptor)中动态修改解析完成后的sql中的......
  • java实现文件上传与下载
    一、对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦。 JSP代码,POST请求,表单必须设置为enctype="multipart/form-data"<spanstyle="font-size:14px;"><formaction="upload3"method="po......
  • Java实现四则运算生成器
    这个作业属于哪个课程计科二班这个作业要求在哪里结对项目这个作业的目标熟悉结对编程项目成员龙新超3121004921github链接:龙新超github艾孜海尔江3121004900github链接:海尔江githubPSP表格PSP2.1PersonalSoftwareProcessStages预估耗时(分钟)实......
  • Seata架构实现分布式事务
    Seata架构官网地址:http://seata.io/zh-cn/Seata架构实现模型 TC(TransactionCoordinator):事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。监控和通知各个事务,包括分支事务和全局事务。TM(TransactionManager):事务管理器:定义全局事务的范围、开始全局事务、......
  • unet原理学习与记录
    UNET:     左边编码下采样,右边编码上采样。   改进版本认为原始版本融合特征跨度太远,改为就近融合下面有4个损失函数,如果前面三个效果就很好,第四个可以丢掉(剪枝) 数据增强包:albumentations 链接:https://github.com/albumentations-team/albumentations#i-......
  • SSD与vgg目标检测网络原理
    目录:一、SSD二、基于SSD的极速人脸检测三、VGG 一、SSDSSD主干网络结构(SSD是一个多级分类网络)图1 ssd主干网络结构图ssd中的vgg-19网络:SSD采用的主干网络是VGG网络,关于VGG的介绍大家可以看我的另外一篇博客https://blog.csdn.net/weixin_44791964/article/detai......
  • Springboot自动装配原理
    BFPP:BeanFactoryPostProcessorBPP:BeanPostProcessorBDRPP:BeanDefinitionRegistryPostProcessor自动装配实现的原理:当启动springboot应用程序的时候,会先创建SpringApplication的对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及......
  • vue3项目结合ant design vue的upLoad组件实现上传和下载Excel文件
    1.上传文件  1.组件引入    import{ Button, Upload}from'ant-design-vue'  2.代码    <Upload      v-model:file-list="fileList"      name="file"      //限制文件格式      acce......
  • 【从0学习Solidity】 10. 控制流,用solidity实现插入排序
    【从0学习Solidity】10.控制流,用solidity实现插入排序博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和......