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

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

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

源码已经上传到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)  
      }  
   }  
}

http://www.niftyadmin.cn/n/5055670.html

相关文章

重大发布 | 雷特百元级DALI主控 200场景·万灯独控·有线无线全覆盖

中秋国庆放假安排 喜迎国庆、欢度中秋。按照国家有关规定&#xff0c;智哪儿定于9.29-10.6期间放假&#xff0c;10.7-10.8正常上班。 假期期间&#xff0c;智哪儿全平台暂停更新。祝大家合理安排好假期生活&#xff0c;度过一个愉快的假期。

安卓recovery流程分析(编译、界面、图片)

目录 recovery 界面菜单 recovery 界面操作 recovery 启动流程 recovery 编译makefile recovery 图片大小 ramdisk、boot.img、recovery.img之间的关系 authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 recovery 界面菜单 recovery 界面显示 android recoveryuse …

Java异常处理SOP

文章目录 一、常见异常处理方式二.try catch1.catch类型3.1 插入数据的时候&#xff0c;需要更加精细的判断是为幂等冲突异常么5. catch的作用6.异常处理sop15、throw new Exception中添加信息 一、常见异常处理方式 Service层func2&#xff0c;调用网关层func1 直接吃掉 publ…

uni-app:循环数据点击事件获取每行指定数据(获取参数)

效果 页面样式 点击首行控制台输出信息 代码 :data-id"item.id"&#xff1a;定义id信息&#xff0c;在点击事件时e.currentTarget.dataset.id获取点击行的id :data-index"index"&#xff1a;定义index信息&#xff0c;在点击事件时e.currentTarget.datase…

EV代码签名证书的作用有哪些?如何获取呢?

我们都知道&#xff0c;黑客们往往会通过篡改软件代码来进行各种恶意行为&#xff0c;例如加入病毒、木马、恶意代码等&#xff0c;为了确保软件代码的完整性和可信任性&#xff0c;代码签名证书诞生了。代码签名证书又分为普通代码签名证书和EV代码签名证书&#xff0c;我们在…

xlsx安装报错1 high severity vulnerability

背景&#xff1a;vue3tsviteelementplus想要使用xlsx实现 el-table表格的导出&#xff0c;但是安装时报错1 high severity vulnerability。 尝试&#xff1a;在网上搜索解决方案&#xff0c;有人提出是因为xlsx版本是0.18.5&#xff0c;需要node版本是14&#xff0c;而当前版本…

c++基础知识点总结

C基础 C语言和C的区别与联系 c是面向过程c是面向对象 C语言&#xff08;C&#xff09;和C语言&#xff08;C&#xff09;都是流行的编程语言&#xff0c;它们有许多相似之处&#xff0c;但也有一些重要的区别。下面是它们的主要区别和联系&#xff1a; 编程范式&#xff1a; C语…

第5讲:v-if与v-show的使用方法及区别

v-if条件判断 v-if是条件渲染指令&#xff0c;它根据表达式的真假来删除和插入元素&#xff0c;它的基本语法如下&#xff1a; v-if “expression” expression是一个返回bool值的表达式&#xff0c;表达式可以是一个bool属性&#xff0c;也可以是一个返回bool的运算式 &#…