golang 排序_堆 堆排序 优先队列 图文详解(Golang实现)

引入

在实际应用中,我们经常需要从一组对象中查找 最大值最小值 。当然我们可以每次都先排序,然后再进行查找,但是这种做法效率很低。哪么有没有一种特殊的数据结构,可以高效率的实现我们的需求呢,答案就是 堆(heap)

堆分为最小堆和最大堆,它们的性质相似,我们以最小堆为例子。

最小堆

举例

6dccd6b8ef8e979ee773a1000fda7674.png

如上图所示,就为一个最小堆。

特性

  • 是一棵完全二叉树

如果一颗二叉树的任何结点,或者是树叶,或者左右子树均非空,则这棵二叉树称做满二叉树(full binary tree)

如果一颗二叉树最多只有最下面的两层结点度数可以小于2,并且最下面一层的结点都集中在该层最左边的连续位置上,则此二叉树称做完全二叉树(complete binary tree)

2d610f0ff2bec79febda73dd79671429.png
  • 局部有序

最小堆对应的完全二叉树中所有结点的值均不大于其左右子结点的值,且一个结点与其兄弟之间没有必然的联系

二叉搜索树中,左子 < 父 < 右子

存储结构

由于堆是一棵完全二叉树,所以我们可以用顺序结构来存储它,只需要计算简单的代数表达式,就能够非常方便的查找某个结点的父结点和子节点,既避免了使用指针来保持结构,又能高效的执行相应操作。

结点i的左子结点为2xi+1,右子结点为2xi+2结点i的父节点为(i-1)/2
c9a90e25995b83bc0141c931dd7da0f4.png

数据结构

// 本例为最小堆// 最大堆只需要修改less函数即可type Heap []intfunc (h Heap) swap(i, j int) { h[i], h[j] = h[j], h[i]}func (h Heap) less(i, j int) bool { return h[i] < h[j]}

如上所示,我们使用slice来存储我们的数据,为了后续方便我们在此定义了 swap 和 less 函数,分别用来交换两个结点和比较大小。

插入-Push

c5547970cc77cde001bc263ded256874.png

如上图所示,首先,新添加的元素加入末尾。为了保持最小堆的性质,需要沿着其祖先的路径, 自下而上 依次比较和交换该结点与父结点的位置,直到重新满足堆的性质为止。

这样会出现两种情况,要么新结点升到最小堆的顶端,要么到某一位置时发现父结点比新插入的结点关键值小。

上面的流程代码如下:

func (h Heap) up(i int) { for { f := (i - 1) / 2 // 父亲结点 if i == f || h.less(f, i) { break } h.swap(f, i) i = f }}

实现了最核心的 up 操作后,我们的插入操作 push 便很简单,代码如下:

// 注意go中所有参数转递都是值传递// 所以要让h的变化在函数外也起作用,此处得传指针func (h *Heap) Push(x int) { *h = append(*h, x) h.up(len(*h) - 1)}

删除-Remove

4306cefbb5ee8d5e25acc4ceacbe6112.png

如上图所示,首先把最末端的结点填入要删除节点的位置,然后删除末端元素,同理,这样做也可能导致破坏最小堆的堆序特性。

为了保持堆的特性,末端元素需要与被删除位置的父结点做比较,如果小于父结点,就要up(详细代码看插入)如果大于父结点,就要再和被删除位置的子结点做比较,即down,直到该结点下降到小于最小子结点为止。

上面down的流程代码如下:

func (h Heap) down(i int) { for { l := 2*i + 1 // 左孩子 if l >= len(h) { break // i已经是叶子结点了 } j := l if r := l + 1; r < len(h) && h.less(r, l) { j = r // 右孩子 } if h.less(i, j) { break // 如果父结点比孩子结点小,则不交换 } h.swap(i, j) // 交换父结点和子结点 i = j //继续向下比较 }}

实现了核心的 down 操作后,我们的 Remove 便很简单,代码如下:

// 删除堆中位置为i的元素// 返回被删元素的值func (h *Heap) Remove(i int) (int, bool) { if i < 0 || i > len(*h)-1 { return 0, false } n := len(*h) - 1 h.swap(i, n) // 用最后的元素值替换被删除元素 // 删除最后的元素 x := (*h)[n] *h = (*h)[0:n] // 如果当前元素大于父结点,向下筛选 if (*h)[i] > (*h)[(i-1)/2] { h.down(i) } else { // 当前元素小于父结点,向上筛选 h.up(i) } return x, true}

弹出-Pop

当i=0时, Remove 就是 Pop

// 弹出堆顶的元素,并返回其值func (h *Heap) Pop() int { n := len(*h) - 1 h.swap(0, n) x := (*h)[n] *h = (*h)[0:n] h.down(0) return x}

初始化-Init

在我们讲完了堆的核心操作 up 和 down 后,我们来讲如何根据一个数组构造一个最小堆。

其实我们可以写个循环,然后将各个元素依次 push 进去,但是这次我们利用数学规律,直接由一个数组构造最小堆。

首先,将所有关键码放到一维数组中,此时形成的完全二叉树并不具备最小堆的特征,但是仅包含叶子结点的子树已经是堆。

即在有n个结点的完全二叉树中,当 i>n/2-1 时,以i结点为根的子树已经是堆。

func (h Heap) Init() { n := len(h) // i > n/2-1 的结点为叶子结点本身已经是堆了 for i := n/2 - 1; i >= 0; i-- { h.down(i) }}

测试

func main() { var h = heap.Heap{20, 7, 3, 10, 15, 25, 30, 17, 19} h.Init() fmt.Println(h) // [3 7 20 10 15 25 30 17 19] h.Push(6) fmt.Println(h) // [3 6 20 10 7 25 30 17 19 15] x, ok := h.Remove(5) fmt.Println(x, ok, h) // 25 true [3 6 15 10 7 20 30 17 19] y, ok := h.Remove(1) fmt.Println(y, ok, h) // 6 true [3 7 15 10 19 20 30 17] z := h.Pop() fmt.Println(z, h) // 3 [7 10 15 17 19 20 30]}

堆排序

在讲完堆的基础知识后,我们再来看堆排序就变得非常简单。利用最小堆的特性,我们每次都从堆顶弹出一个元素(这个元素就是当前堆中的最小值),即可实现升序排序。代码如下:

// 堆排序var res []intfor len(h) != 0 {  res = append(res, h.Pop())}fmt.Println(res)

优先队列

优先队列是0个或者多个元素的集合,每个元素都有一个关键码,执行的操作有查找,插入和删除等。

优先队列的主要特点是支持从一个集合中快速地查找并移出具有最大值或最小值的元素。

堆是一种很好的优先队列的实现方法。

参考资料

  • 《数据结构与算法》张铭 王腾蛟 赵海燕 编著
  • GO SDK 1.13.1 /src/container/heap

最后

本文是自己的学习笔记,在刷了几道LeetCode中关于堆的题目后,感觉应该系统的学习和总结一下这一重要的数据结构了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513660.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

看懂 IPv6+,这篇就够了

来源&#xff1a;鲜枣课堂作者&#xff1a;小枣君5G网络的不断建设和普及&#xff0c;加速了我们迈入万物互联时代的步伐。我们的整个互联网络&#xff0c;正在发生翻天覆地的变化。急剧增加的网络连接数和流量&#xff0c;对网络的承载和传送能力&#xff0c;提出了前所未有的…

高德打车通用可编排订单状态机引擎设计

简介&#xff1a; 订单状态流转是交易系统的最为核心的工作&#xff0c;订单系统往往都会存在状态多、链路长、逻辑复杂的特点&#xff0c;还存在多场景、多类型、多业务维度等业务特性。在保证订单状态流转稳定性的前提下、可扩展性和可维护性是我们需要重点关注和解决的问题。…

边开飞机边换引擎?我们造了个新功能保障业务流量无损迁移

简介&#xff1a; 容器化部署应用可以降低企业成本&#xff0c;提升研发效率&#xff0c;解放运维人员。据 Gartner 预计&#xff0c;到 2022 年&#xff0c;将有 75&#xff05; 的企业将在生产中运行容器化应用程序。Kubernetes 是企业部署容器化应用的首选框架。由于 Kubern…

专访百度集团副总裁袁佛玉:科技创新对普惠金融正在充分发挥“乘数效应”

图为百度集团副总裁袁佛玉在发表演讲 “随着我国数字经济的腾飞&#xff0c;科技创新正在充分发挥对于普惠金融的“乘数效应”&#xff0c;加速普惠金融拓展的深度和广度。”百度集团副总裁袁佛玉在10月22日举办的2021金融街论坛“‘一带一路’金融减贫论坛”上表示。 袁佛玉…

Spring Cloud Stream 体系及原理介绍

简介&#xff1a; Spring Cloud Stream在 Spring Cloud 体系内用于构建高度可扩展的基于事件驱动的微服务&#xff0c;其目的是为了简化消息在 Spring Cloud 应用程序中的开发。 作者 | 洛夜 来源 | 阿里巴巴云原生公众号 Spring Cloud Stream在 Spring Cloud 体系内用于构建高…

工商银行分布式服务C10K场景的解决方案

简介&#xff1a; 未来&#xff0c;中国工商银行将持续致力于 Dubbo 的金融级规模化应用。 作者&#xff1a;颜高飞&#xff0c;微服务领域架构师&#xff0c;主要从事服务发现、高性能网络通信等研发工作&#xff0c;擅长 ZooKeeper、Dubbo、RPC 协议等技术方向。 Dubbo是一款…

使用html() undefined_SweetAlert2使用教程

SweetAlert2是一款功能强大的纯Js模态消息对话框插件。SweetAlert2用于替代浏览器默认的弹出对话框&#xff0c;它提供各种参数和方法&#xff0c;支持嵌入图片&#xff0c;背景&#xff0c;HTML标签等&#xff0c;并提供5种内置的情景类&#xff0c;功能非常强大。SweetAlert2…

埃森哲携手阿里云,采用K8s容器云服务为客户提供无限弹性

简介&#xff1a; 埃森哲作为全球领先的专业服务公司&#xff0c;在数字化、云计算等领域拥有全球领先的能力&#xff0c;我们在多年的实际客户项目中&#xff0c;找到并沉淀出了适合企业数字化转型的方法论&#xff0c;积累了丰富的落地经验。 作者&#xff1a;姚迪、周警伟 …

4阶范德蒙德行列式例题_线性代数入门——“爪型行列式”的计算及其应用

系列简介&#xff1a;这个系列文章讲解线性代数的基础内容&#xff0c;注重学习方法的培养。线性代数课程的一个重要特点(也是难点)是概念众多&#xff0c;而且各概念间有着千丝万缕的联系&#xff0c;对于初学者不易理解的问题我们会不惜笔墨加以解释。在内容上&#xff0c;以…

如何使用Arthas提高日常开发效率?

简介&#xff1a; 1. Arthas有什么功能&#xff0c;怎么用&#xff0c;请看&#xff1a;Arthas使用手册 2. Arthas命令比较复杂&#xff0c;一个帮助生成命令的IDEA插件&#xff1a;arthas idea plugin 使用文档 3. 基于Arthas实现的简单好用的热部署插件&#xff1a;ArthasHot…

stringutils 用哪个包 apache spring_spring整合mq、jsonp跨越、httpclient工具的使用

训练大纲(第087天)大家如果想快速有效的学习&#xff0c;思想核心是“以建立知识体系为核心”&#xff0c;具体方法是“守破离”。确保老师课堂上做的操作&#xff0c;反复练习直到熟练。第173次(ActiveMQ)学习主题&#xff1a;ActiveMQ学习目标&#xff1a;1 掌握什么是spring…

几种Java常用序列化框架的选型与对比

简介&#xff1a; 序列化与反序列化是我们日常数据持久化和网络传输中经常使用的技术&#xff0c;但是目前各种序列化框架让人眼花缭乱&#xff0c;不清楚什么场景到底采用哪种序列化框架。本文会将业界开源的序列化框架进行对比测试&#xff0c;分别从通用性、易用性、可扩展性…

12v小型电机型号大全_电动机型号参数大全,再也不怕看不懂电机型号了

电动机型号是便于使用、设计、制造等部门进行业务联系和简化技术文件中产品名称、规格、型式等叙述而引用的一种代号。下面为大家介绍电动机型号含义等信息。1电动机型号组成及含义由电机类型代号、电机特点代号、设计序号和励磁方式代号等四个小节顺序组成。1、类型代号是表征…

基于DataWorks搭建新零售数据中台

文章作者&#xff1a;许日&#xff08;欢伯&#xff09;&#xff0c;在2016年盒马早期的时候&#xff0c;转到盒马事业部作为在线数据平台的研发负责人&#xff0c;现任阿里云计算平台DataWorks建模引擎团队负责人。 文章简介&#xff1a;本篇文章向大家分享新零售企业如何基于…

身份云平台 Authing 完成 2300 万美元 A 轮融资

10 月 24 日&#xff0c;身份云平台 Authing 宣布完成 2300 万美元 A 轮融资。本轮融资由老虎环球基金领投&#xff0c;鼎晖VGC&#xff08;创新与成长基金&#xff09;、声网 Agora、老股东 GGV纪源资本和奇绩创坛跟投&#xff0c;跃为资本担任独家财务顾问。Authing 表示&…

大数据计算存储资源池_管家实践:轻松玩转大数据计算服务

以下是直播内容精华整理&#xff0c;主要包括以下四个方面&#xff1a;1.背景速览&#xff1b;2.功能介绍&#xff1b;3.案例讲解&#xff1b;4.新功能预告。一、背景速览MaxCompute(原ODPS)是一项大数据计算服务&#xff0c;它能提供快速、完全托管的PB级数据仓库解决方案&…

客如云数据中台建设

简介&#xff1a; 本次分享介绍客如云如何利用阿里云大数据产品来建设数据中台。 客如云是2012年成立的一家公司&#xff0c;覆盖餐饮、零售、美业&#xff0c;还有其他的业态以及服务的一家综合性的SaaS公司。到2020年为止&#xff0c;客如云已经服务了60万商家&#xff0c;帮…

微博机器学习平台云上最佳实践

简介&#xff1a; 本文讲述了微博机器学习平台和深度学习平台的业务功能和云上实践&#xff0c;剖析了阿里云大数据在微博这两大学习平台的架构建设上所起到的作用。 作者&#xff1a;新浪微博数据计算平台系统架构师 曹富强 本文讲述了微博机器学习平台和深度学习平台的业务功…

搞懂异地多活,看这篇就够了

来源&#xff1a;水滴与银弹作者&#xff1a;Kaito阅读本文大约需要 20 分钟。你好&#xff0c;我是 Kaito。在软件开发领域&#xff0c;「异地多活」是分布式系统架构设计的一座高峰&#xff0c;很多人经常听过它&#xff0c;但很少人理解其中的原理。异地多活到底是什么&…

搭建一个微服务商城到底可以有多快?

简介&#xff1a; 极速部署一个微服务电商商城&#xff0c;体验 Serverless 带给您的应用全托管体验。 作者&#xff1a;云原生技术运营 - 望宸 技术实践的门槛不仅在于应用上线后各类问题的排查难度&#xff0c;也在于搭建一个 Demo 应用时的复杂度。 今天我们尝试 3 种方法来…