原创文章出自公众号:「码农富哥」,欢迎转载和关注,如转载请注明出处!

堆基本概念

堆排序是一个很重要的排序算法,它是高效率的排序算法,复杂度是O(nlogn),堆排序不仅是面试进场考的重点,而且在很多实践中的算法会用到它,比如经典的TopK算法、小顶堆用于实现优先级队列。

堆排序是利用堆这种数据结构所设计的一种排序算法。堆实际上是一个完全二叉树结构。

问:那么什么是完全二叉树呢?

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

我们知道堆是一个完全二叉树了,那么堆又分两种堆:大顶堆小顶堆

它们符合一个重要的性质:

  • 小顶堆满足: Key[i] <= key[2i+1] && Key[i] <= key[2i+2]
  • 大顶堆满足: Key[i] >= Key[2i+1] && key >= key[2i+2]

怎么理解呢,其实很简单,顾名思义,大顶堆最大的元素在跟节点,堆的性质决定了大顶堆中节点一定大于等于其子节点,反之,小顶堆的最小元素在根节点。我们来看看大顶堆和小顶堆的示意图:

堆排序基本思想及步骤

堆排序有以下几个核心的步骤:

  1. 将待排序的数组初始化为大顶堆,该过程即建堆。
  2. 将堆顶元素与最后一个元素进行交换,除去最后一个元素外可以组建为一个新的大顶堆。
  3. 由于第二部堆顶元素跟最后一个元素交换后,新建立的堆不是大顶堆,需要重新建立大顶堆。重复上面的处理流程,直到堆中仅剩下一个元素。

假设我们有一个待排序的数组 arr = [4, 6, 7, 2, 9, 8, 3, 5], 我们把这个数组构造成为一个二叉树,如下图:

问:此时我们需要把这个完全二叉树构造成一个大顶堆,怎么构造呢?

答:一个很好的方法是遍历二叉树的非叶子节点自下往上的构造大顶堆,针对每个非叶子节点,都跟它的左右子节点比较,把最大的值换到这个子树的父节点。

问:为什么要从非叶子节点开始,而不是从最后一个节点开始?

答:因为叶子节点下面没有子节点了,就没必要操作了。

问:为什么要从下往上而不是从上往下遍历非叶子节点?

答:我们从下面开始遍历调整每个节点成为它左右节点的最大值,那么一直往上的话,最后根节点一定是最大的值;但是如果我们从上往下,上面满足了大顶堆,下面不满足,调整后,上面可能又不满足了,所以从下往上是最好的方案。

那么我们构造的大顶堆的代码就很明显了:

# 构造大顶堆,从非叶子节点开始倒序遍历,因此是l//2 -1 就是最后一个非叶子节点
l = len(arr)
for i in range(l//2-1, -1, -1):
build_heap()
# 遍历针对每个非叶子节点构造大顶堆

看我们的例子,非叶子节点有2, 8, 6, 4, 我们从最后一个非叶子节点,也就是5开始遍历构造大顶堆,2 跟 5 比较,5比较大,所以把 arr[3]和arr[7]从数组中交换一下位置,那么就完成第一个非叶子节点的置换。下面的节点继续交换







此时9跟4交换后,4这个节点下面的树就不是不符合大顶堆了,所以要针对4这个节点跟它的左右节点再次比较,置换成较大的值,4跟左右子节点比较后,应该跟6交换位置。



那么至此,整个二叉树就是一个完完整整的大顶堆了,每个节点都不小于左右子节点。

此时我们把堆的跟节点,即数组最大值9跟数组最后一个元素2交换位置,那么9就是排好序的放在了数组最后一个位置

2到了跟节点后,新的堆不满足大顶堆,我们需要重复上面的步骤,重新构造大顶堆,然后把大顶堆根节点放到二叉树后面作为排好序的数组放好。就这样利用大顶堆一个一个的数字排好序。

值得注意的一个地方是,上面我们把9和2交换位置后,2处于二叉树根节点,2需要跟右子树8交换位置,交换完位置后,右子树需要重新递归调整大顶堆,但是左子树6这边,已经是满足大顶堆属性,因为不需要再操作。

我们再看看堆排序的一个直观的动图吧:

代码实现:

class Solution(object):
def heap_sort(self, nums):
i, l = 0, len(nums)
self.nums = nums
# 构造大顶堆,从非叶子节点开始倒序遍历,因此是l//2 -1 就是最后一个非叶子节点
for i in range(l//2-1, -1, -1):
self.build_heap(i, l-1)
# 上面的循环完成了大顶堆的构造,那么就开始把根节点跟末尾节点交换,然后重新调整大顶堆
for j in range(l-1, -1, -1):
nums[0], nums[j] = nums[j], nums[0]
self.build_heap(0, j-1) return nums def build_heap(self, i, l):
"""构建大顶堆"""
nums = self.nums
left, right = 2*i+1, 2*i+2 ## 左右子节点的下标
large_index = i
if left <= l and nums[i] < nums[left]:
large_index = left if right <= l and nums[left] < nums[right]:
large_index = right # 通过上面跟左右节点比较后,得出三个元素之间较大的下标,如果较大下表不是父节点的下标,说明交换后需要重新调整大顶堆
if large_index != i:
nums[i], nums[large_index] = nums[large_index], nums[i]
self.build_heap(large_index, l)

堆排序复杂度

时间复杂度, 包括两个方面:

  1. 初始化建堆过程时间:O(n)
  2. 更改堆元素后重建堆时间:O(nlogn),循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn ,所以复杂度是 O(nlogn)

时间复杂度:O(nlogn)

空间复杂度: 因为堆排序是就地排序,空间复杂度为常数:O(1)

堆排序的应用:TopK算法

面试中经常考的一个面试题就是,如果在海量数据中找出最大的100个数字,看到这个问题,可能大家首先会想到的是使用高效排序算法,比如快排,对这些数据排序,时间复杂度是O(nlogn),然后取出最大的100个数字。但是如果数据量很大,一个机器的内存不足以一次过读取这么多数据,就不能使用这个方法了。

不使用分布式机器计算,使用一个机器也能找出TopK的经典算法就是使用堆排序了,具体方法是:

维护一个大小为 K 的小顶堆,依次将数据放入堆中,当堆的大小满了的时候,只需要将堆顶元素与下一个数比较:

  • 如果小于堆顶元素,则直接忽略,比较下一个元素;
  • 如果大于堆顶元素,则将当前的堆顶元素抛弃,并将该元素插入堆中。遍历完全部数据,Top K 的元素也自然都在堆里面了。



整个操作中,遍历数组需要O(n)的时间复杂度,每次调整小顶堆的时间复杂度是O(logK),加起来就是 O(nlogK) 的复杂度,如果 K 远小于 n 的话, O(nlogK) 其实就接近于 O(n) 了,甚至会更快,因此也是十分高效的。

总结

堆排序有以下几个核心的步骤:

  1. 将待排序的数组初始化为大顶堆,该过程即建堆。
  2. 将堆顶元素与最后一个元素进行交换,除去最后一个元素外可以组建为一个新的大顶堆。
  3. 由于第二部堆顶元素跟最后一个元素交换后,新建立的堆不是大顶堆,需要重新建立大顶堆。重复上面的处理流程,直到堆中仅剩下一个元素。

最后

文章如果对你有收获,可以收藏转发,这也是对我写作的肯定!另外可以关注我公众号「码农富哥」 ,我会持续输出Python,服务端架构,计算机基础(MySQL, Linux,TCP/IP)的 原创 文章

Python 实现转堆排序算法原理及时间复杂度(多图解释)的更多相关文章

  1. Canny边缘检測算法原理及其VC实现具体解释(一)

    图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般能够看作是一个阶跃,既从一个灰度值在非常小的缓冲区域内急剧变化到还有一个灰度相差较大的灰度值.图象的边缘部分集中了图象的大部分信息,图 ...

  2. 第一篇:K-近邻分类算法原理分析与代码实现

    前言 本文介绍机器学习分类算法中的K-近邻算法并给出伪代码与Python代码实现. 算法原理 首先获取训练集中与目标对象距离最近的k个对象,然后再获取这k个对象的分类标签,求出其中出现频数最大的标签. ...

  3. 梯度迭代树(GBDT)算法原理及Spark MLlib调用实例(Scala/Java/python)

    梯度迭代树(GBDT)算法原理及Spark MLlib调用实例(Scala/Java/python) http://blog.csdn.net/liulingyuan6/article/details ...

  4. Python实现的选择排序算法原理与用法实例分析

    Python实现的选择排序算法原理与用法实例分析 这篇文章主要介绍了Python实现的选择排序算法,简单描述了选择排序的原理,并结合实例形式分析了Python实现与应用选择排序的具体操作技巧,需要的朋 ...

  5. 【机器学习】Apriori算法——原理及代码实现(Python版)

    Apriopri算法 Apriori算法在数据挖掘中应用较为广泛,常用来挖掘属性与结果之间的相关程度.对于这种寻找数据内部关联关系的做法,我们称之为:关联分析或者关联规则学习.而Apriori算法就是 ...

  6. PageRank算法原理与Python实现

    一.什么是pagerank PageRank的Page可是认为是网页,表示网页排名,也可以认为是Larry Page(google 产品经理),因为他是这个算法的发明者之一,还是google CEO( ...

  7. Python实现各种排序算法的代码示例总结

    Python实现各种排序算法的代码示例总结 作者:Donald Knuth 字体:[增加 减小] 类型:转载 时间:2015-12-11我要评论 这篇文章主要介绍了Python实现各种排序算法的代码示 ...

  8. GBDT算法原理深入解析

    GBDT算法原理深入解析 标签: 机器学习 集成学习 GBM GBDT XGBoost 梯度提升(Gradient boosting)是一种用于回归.分类和排序任务的机器学习技术,属于Boosting ...

  9. Javascript中的冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序 算法性能分析

    阿里面试中有一道题是这样的: 请用JavaScript语言实现 sort 排序函数,要求:sort([5, 100, 6, 3, -12]) // 返回 [-12, 3, 5, 6, 100],如果你 ...

随机推荐

  1. poj 2253 最短路 or 最小生成树

    Freddy Frog is sitting on a stone in the middle of a lake. Suddenly he notices Fiona Frog who is sit ...

  2. spring boot 集成apollo 快速指南

    目前市面上流行的三大配置中心框架:Spring CLoud Config .Alibaba Nacos 以及携程apollo, 我们相应架构组号召,就使用Apollo吧. Work Flow 简单解释 ...

  3. .net core webapi搭建(2)跨域

    Core WebAPI中的跨域处理 在使用WebAPI项目的时候基本上都会用到跨域处理 Core WebAPI的项目中自带了跨域Cors的处理,不需要单独添加程序包 如图所示 修改 Configure ...

  4. Nito.AsyncEx 这个库

    有一个非常聪明的小伙子 (我高度赞扬) 叫 Stephen Cleary ,他写了一个很棒的 Extension 集,共同参与开发的还有 Stephen Toub (他显然是经验丰富的),所以我充分信 ...

  5. 优雅写Java之三(IO与文本解析)

    一.资源相关 二.路径与文件 读文件: String fileName = "str.txt"; Files.lines(Paths.get(fileName), Standard ...

  6. springboot mybatis 多数据源配置支持切换以及一些坑

    一 添加每个数据源的config配置,单个直接默认,多个需要显示写出来 @Configuration @MapperScan(basePackages ="com.zhuzher.*.map ...

  7. Stopping service [Tomcat] Disconnected from the target VM, address:XXXXXX解决方案

    原文出处:https://blog.csdn.net/u013294097/article/details/90677049 Stopping service [Tomcat] Disconnecte ...

  8. JQuery--JQuery面向对象编程快速入门-插件开发

    JQuery源码片段分析 (function(window){ /** * jQuery是一个函数 是一个对象 是一个构造器函数 */ var jQuery = function( selector, ...

  9. gulp实现自动化打包(二)

    引言 在这篇文章中我基于上一篇文章gulp的简单打包示例(一)的代码(重点,不然看的懵逼状态)来介绍gulp的自动化打包,主要是修改gulpfile.js配置文件.当我们执行gulp任务,gulp自动 ...

  10. Keras入门(五)搭建ResNet对CIFAR-10进行图像分类

      本文将会介绍如何利用Keras来搭建著名的ResNet神经网络模型,在CIFAR-10数据集进行图像分类. 数据集介绍   CIFAR-10数据集是已经标注好的图像数据集,由Alex Krizhe ...