堆的原理以及实现O(lgn)
大家好,我是蓝胖子,我一直相信编程是一门实践性的技术,其中算法也不例外,初学者可能往往对它可望而不可及,觉得很难,学了又忘,忘其实是由于没有真正搞懂算法的应用场景,所以我准备出一个系列,囊括我们在日常开发中常用的算法,并结合实际的应用场景,真正的感受算法的魅力。
今天我们就来看看堆这种数据结构。
源码已经上传到github
https://github.com/HobbyBear/codelearning/tree/master/heap
原理
在详细介绍堆之前,先来看一种场景,很多时候我们并不需要对所有元素进行排序,而只需要取其中前topN的元素,这样的情况如果按性能较好的排序算法,比如归并或者快排需要n*log( n)的时间复杂度,n为数据总量,排好序后取出前N条数据,而如果用堆这种数据结构则可以在n*log(N)的时间复杂度内找到这N条数据,N的数据量远远小于数据总量n。
接着我们来看看堆的定义和性质,堆是一种树状结构,且分为最小堆和最大堆,最大堆的性质有父节点大于左右子节点,最小堆的性质则是父节点小于左右子节点。如下图所示:

并且堆是一颗完全二叉树,完全二叉树的定义如下:
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
因为结点都集中在左侧,所以我们可以从上到下,从左到右对堆中节点进行标号,如下图所示:

从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)
}
}
}
堆的原理以及实现O(lgn)的更多相关文章
- fibonacci-Heap(斐波那契堆)原理及C++代码实现
斐波那契堆是一种高级的堆结构,建议与二项堆一起食用效果更佳. 斐波那契堆是一个摊还性质的数据结构,很多堆操作在斐波那契堆上的摊还时间都很低,达到了θ(1)的程度,取最小值和删除操作的时间复杂度是O(l ...
- Linux 堆溢出原理分析
堆溢出与堆的内存布局有关,要搞明白堆溢出,首先要清楚的是malloc()分配的堆内存布局是什么样子,free()操作后又变成什么样子. 解决第一个问题:通过malloc()分配的堆内存,如何布局? 上 ...
- 漫谈 C++ 的 内存堆 实现原理
如果我来设计 C++ 的 内存堆 , 我会这样设计 : 进程 首先会跟 操作系统 要 一块大内存区域 , 我称之为 Division , 简称 div . 然后 , 将这块 div 作为 堆 , 就可 ...
- poppo大根堆的原理与实现。
大根堆的定义:1 大根堆是一个大根树 2 大根堆是一个完全二叉树 所以大根堆用数组表示是连续的,不会出现空白字段. 对于大根堆的插入 对于大根堆的插入,可以在排序前确定大根堆的形状,可以确定元素5从位 ...
- binary-heap(二叉堆)原理及C++代码实现
二叉堆可以看做一个近似的完全二叉树,所以一般用数组来组织. 二叉堆可以分为两种形式:最大堆和最小堆.最大堆顾名思义,它的每个结点的值不能超过其父结点的值,因此堆中最大元素存放在根结点中.最小堆的组织方 ...
- 利用DWORD SHOOT实现堆溢出的利用(先知收录)
原文链接:https://xz.aliyun.com/t/4009 1.0 DWORD SHOOT是什么捏? DWORD SHOOT指能够向内存任意位置写入任意数据,1个WORD=4个bytes,即可 ...
- 浅析PriorityBlockingQueue优先级队列原理
介绍 当你看本文时,需要具备以下知识点 二叉树.完全二叉树.二叉堆.二叉树的表示方法 如果上述内容不懂也没关系可以先看概念. PriorityBlockingQueue是一个无界的基于数组的优先级阻塞 ...
- 【CTF】日志 2019.7.13 pwn 堆溢出基础知识
十六进制两位表示一个字节 堆溢出 先上堆图: 堆的数据结构 一般情况下,物理相邻的两个空闲 chunk 会被合并为一个 chunk struct malloc_chunk { INTERNAL_SIZ ...
- 八大排序算法Java实现
本文对常见的排序算法进行了总结. 常见排序算法如下: 直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序 它们都属于内部排序,也就是只考虑数据量较小仅需要使用内存的排 ...
- 八大排序算法总结与java实现(转)
八大排序算法总结与Java实现 原文链接: 八大排序算法总结与java实现 - iTimeTraveler 概述 直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序 ...
随机推荐
- 经纬度坐标为中心点生成米距离长度半径的圆形面,含java js源码+在线绘制,代码简单零依赖
目录 java版源码 js版源码 在线绘制预览效果 关于计算的精确度 前些时间在更新我的坐标边界查询工具的时候,需要用到经纬度坐标点的距离计算,和以坐标点为中心生成一个指定距离为半径的圆,搜了一下没有 ...
- Spark架构与运行流程
1. 阐述Hadoop生态系统中,HDFS, MapReduce, Yarn, Hbase及Spark的相互关系. 2. Spark已打造出结构一体化.功能多样化的大数据生态系统,请简述Spark生态 ...
- Docker和Kubernetes与容器自动化扩展:最佳实践
目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ...
- 🎉Avalonia 11.0.0 正式版发布
Avalonia 11.0.0 正式版发布! AvaloniaUI 发布11.0.0正式版 终于avalonia发布了正式版. 更新内容 A11y(辅助功能) 这个版本的Avalonia在使应用程序更 ...
- 使用Githud 实现分发IPA包遇到的坑
最近要用到测试包分发,首先想到了,蒲公英,但是把包扔上去,扫描下载的时候发现,现在需要用户登录才能下载,弃了. 又跑到fir ,发现还得实名才能用,还得上传各种证件照,而且好像每天只有10个下载量,. ...
- Codeforces Round #881 (Div. 3) A-F
比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; int a[57]; bool ...
- 刷了一个月AI歌唱的视频 做一个大胆预测
现在的AI热点转到ChatAI和AI唱歌去了 很好理解(现在每天在看Neuro的切片 感慨这才是看V的初心 可惜Neuro这个形象在创立的时候只是一个ChatAI 和游戏用的GameBOT并不是同一个 ...
- .NET周刊【7月第3期 2023-07-16】
国内文章 揭秘 .NET 中的 TimerQueue(上) https://www.cnblogs.com/eventhorizon/p/17557821.html TimerQueue 是.NET中 ...
- [Spring+SpringMVC+Mybatis]框架学习笔记(八):Mybatis概述
第8章 Mybatis概述 8.1 几个概念 ORM Object-Relationship Mapping 对象关系映射,它是一种思想,它的实质是将数据库中的数据用对象的形式表现出来. JPA Ja ...
- SEO相关配置 HTML meta标签总结与属性使用介绍
HTML meta标签总结与属性使用介绍 <!-- 声明文档使用的字符编码 --> <meta charset='utf-8'> <!-- 优先使用 IE 最新版本和 C ...