前言:

大家好,我是春风。

今天继续学习简单排序之堆排序,其实相对于堆排序,堆结构可能更加重要~

一、堆结构

堆其实就是一颗完全二叉树,只是堆的左右没有大小关系,我们一般将一个数组可以转化为堆结构。

  8/ \
4   5  heapSize = 3  8的index-0  4的index-1  5的index-2设父节点的index为i, 则左右子节点的index分别为: 2*i+1 和 2*i+2
  • 大根(顶)堆:每个节点左右两个子节点的值都小于等于它自己
  • 小根(顶)堆:每个节点左右两个子节点的值都大于等于它自己

heapSize:数组转化为堆的大小,其实也是堆的一个边界

大根堆的插入

大根堆每次插入都插到叶子节点,从左往后插,插进来后,比较新增节点是否大于父节点(根据上面的公式可以很快得到父节点的index),如果比父节点大,则交换新增节点与父节点的值,然后通过新的index继续和它的父节点做比较,如此循环。直到不比父节点大,或者到顶了,就停止,最后heapSize+1。

大根堆插入代码

public static void heapInsert(int[] arr, int index) {// 当不再比父节点大,或者到顶了,就停止while (arr[index] > arr[(index-1)>>>1]) {swap(arr, index, (index-1)>>>1);// 比较变换后的节点和父节点继续比较index = (index-1)>>>1;}}public static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}

大根堆的删除

删除index为0(堆顶)或者为i(把i作为堆顶)的元素,将该数和index最后的位置元素交换,然后pageSize-1,然后从i位置向下调整堆,根据上面的公式找到左右子节点的值,在子节点中找一个最大的,然后和i位置的元素比大小,如果i位置的比子节点最大的还小,则和这个最大子节点交换。然后替换到子节点后,继续和新的子节点做比较,如此循环,直达没有子节点,或者不再比子节点小为止。

没有子节点(边界判断):左孩子的index > heapSize

大根堆删除代码

public static void heapOut(int[] arr, int index, int heapSize) {int left = (index << 1) +1;// 当没有左孩子截止while (left < heapSize) {// 两个孩子中找到最大的值int max = left + 1 < heapSize && arr[left+1] > arr[left] ? left+1 : left;// 最大孩子和父节点比较,比最大孩子小,则交换max = arr[max] > arr[index] ? max : index;if (max == index) {break;}swap(arr, max, index);index = max;left = (index << 1) +1; }
}

修改堆中某个位置的值: 其实修改就可以看做是删除后,替换了的结果,紧跟着再做上述调整就好了。

替换后,我们先看是变大了,还是变小了,和父节点比,和子节点中最大的比,只会满足一种情况,然后照着上述方法调整。

时间复杂度: 插入和删除的时间复杂度,其实就看调整花费的时间,每次调整只会顺着一条路径移动,所以最复杂的情况就是跑完全高度的路径,因此插入删除的时间复杂度==堆的高度==logN

堆排序:

理解完堆结构后,我们来看堆排序的过程其实就很简单了,但其实理解我们的堆结构要比堆排序重要的多!

堆排序:就是在上面我们构建完大根堆后,我们的堆顶就是排好序的那个数了,然后将堆顶移除(参考删除过程,移除堆顶,将index最后的位置放到堆顶,然后做调整,heapSize-1)

堆排序代码:

public static void heapSort(int[] arr) {if (arr == null || arr.length < 2) {return;}// 构建大根堆for (int i=0; i<arr.length; i++) {heapInsert(arr, i);}int heapSize = arr.length;//移除堆顶元素swap(arr, 0, --heapSize);while (heapSize > 0) {heapOut(arr, 0, heapSize);swap(arr, 0, --heapSize);}
}

构建大根堆除了我们上面说的一个一个从叶子节点加,还有另外一种方式,把数组看做一个没有调整过的完全二叉树,然后找到倒数第二层的节点,不断的调用移除调整的代码,从下往上调整,这样少了最后一层叶子节点的调整,时间复杂度会更低

堆排序的时间复杂度: 堆排序的时间复杂度主要就是两部分:堆构建,和排序调整

堆构建我们上面说了,是logN,而排序调整,我们每个节点移除的时间复杂度为logN,有n个节点,所以整体的时间复杂度==logN + N x logN == N x logN

空间复杂度:O(1) ,我们没有申请额外的空间,也没有递归需要消耗

优先级队列:其实就是堆结构

例题(堆扩展问题):

一个几乎有序的数组,几乎有序即某一个数排序移动的距离最多k次,对该数组排序?

题解:

我们知道最终排完序的结果,第0个位置一定是最小或者最大值,那么假如排序前,这个最小值或者最大值在第k+1个位置或者之后,则该元素需要移动的距离就超过了k,所以这个最小或者最大值一定只能在第0到第k个数之间。

在这种情况下, 我们就可以使用小根堆或者大根堆,创建一个大小为k的堆,每次找到里面的最小值,然后弹出去,再添加下一个k+1元素。

Java自带的堆结构

这里我们使用Java自带的堆结构:优先级队列- PriorityQueue

public static void sort(int[] arr, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>();// 第一次添加前K个元素到堆里int index=0;for (; index<Math.min(k, arr.length); index++) {heap.add(arr[index]);}// 剩下的元素每次出一个最小,进一个int i=0;for (; index<arr.length; i++, index++) {heap.add(arr[index]);arr[i] = heap.poll();}// 堆里还剩下的数直接添加到数组while (!heap.isEmpty()) {arr[i++] = heap.poll();}
}

PriorityQueue排序的时间复杂度分析:

堆排序的时间复杂度已知:N x logN

而自带的优先级队列做排序时,除了本身的堆排序之外,还有一部分消耗来自于扩容,因为优先级队列每次扩容都是旧容量的2倍,2、4、、8、16,所以当容量为N时,其实是经过了logN次扩容的。每次扩容消耗时间O(1)的话,N个数就是O(N),这样平均下来每个数就是logN,所以自带的优先级队列做排序的时间复杂度==N x logN + logN --> N x logN

PriorityQueue默认是小根堆的实现,如果我们需要大根堆,可以重写比较器

PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>() {@Override// 返回负数-o1在前面// 返回正数-o2在前面public int compare(Integer o1, Integer o2) {return o2-o1;}
});

结语:

截止目前,时间复杂度为NlogN的排序有:

  • 归并
  • 快排
  • 堆排序

我们发现之所以这些排序算法的时间复杂度带logN,其实跟他们共同的特点有关:就是都涉及到二分,一旦二分,就有logN。

接下来还有一个桶排序,简单排序就完结了~


最后的最后,求点赞!非常感谢!

我是春风,春风和煦,不负归期! 公H:程序员春风

【数据结构与算法】三、从堆到堆排序,又是一个logN相关推荐

  1. 数据结构与算法(三) 排序算法(代码示例)

    数据结构与算法三 排序算法 1. 选择排序 2. 插入排序 3. 冒泡排序 4. 归并排序 5. 快速排序 6. 希尔排序 7. 堆排序 总结 1. 选择排序 选择排序的基本原理: 对于未排序的一组记 ...

  2. 白话经典算法系列之七 堆与堆排序

     堆排序与高速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先解说下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是全然二叉树或者是近似全然二叉树. 二叉堆满 ...

  3. 数据结构与算法——二叉树、堆、优先队列

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 七 ...

  4. [算法系列]优先队列,堆与堆排序

    优先队列,堆与堆排序 1 优先队列 有时我们在处理有序元素时,并不一定要求他们全部有序. 很多情况下我们会收集一些元素, 处理当前最大的元素, 然后再收集更多元素, 再处理当前最大元素 - 这种情况下 ...

  5. 数据结构与算法--二叉堆(最大堆,最小堆)实现及原理

    二叉堆(最大堆,最小堆)实现及原理 二叉堆与二叉查找树一样,堆也有两个性质,即结构性质和堆性质.和AVL树一样,对堆的一次操作必须到堆的所有性质都被满足才能终止,也就是我们每次对堆的操作都必须对堆中的 ...

  6. 数据结构和算法三十六

    剑指 Offer 66. 构建乘积数组 题目:给定一个数组 A[0,1,-,n-1],请构建一个数组 B[0,1,-,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B ...

  7. python堆排序算法_Python算法学习之堆和堆排序

    什么是堆? 堆是一种完全二叉树(请你回顾下上一章的概念),有最大堆和最小堆两种.最大堆: 对于每个非叶子节点 V,V 的值都比它的两个孩子大,称为 最大堆特性(heap order property) ...

  8. python range倒序_Python算法学习之堆和堆排序

    什么是堆? 堆是一种完全二叉树(请你回顾下上一章的概念),有最大堆和最小堆两种. 最大堆: 对于每个非叶子节点 V,V 的值都比它的两个孩子大,称为 最大堆特性(heap order property ...

  9. 算法与数据结构(python):堆与堆排序

    提示:专栏解锁后,可以查看该专栏所有文章. 文章目录 什么是堆 堆的表示 图解堆排序 什么是堆 堆是一种完全二叉树,有最大堆和最小堆两种. 最大堆:对于每个非叶子节点V, V的值都比它的两个孩子大,称 ...

最新文章

  1. Nginx为什么快到根本停不下来?
  2. FrostSullivan:2012年中国数据库安全审计与防护产品市场分析
  3. Abp vNext异常处理的缺陷/改造方案
  4. 蛮力法在求解“最近对”问题中的应用(JAVA)
  5. 并查集一般高级应用的理解
  6. 关于表、栈、队列的几种操作
  7. 20145239杜文超 《Java程序设计》第3周学习总结
  8. flash物理引擎应用:FisixObject类(1)
  9. PS非常火焰的火焰字效果
  10. nginx学习笔记七(nginx HTTP框架的执行流程)
  11. Hadoop数据迁移工具DistCp
  12. 送你一波运维背锅专用图~
  13. 电脑WIFI突然消失解决方法
  14. Oracle安装提示无效条目,oracle NET 无效条目,要求有效的“服务名”
  15. 15-VulnHub-Raven 1
  16. 天池 入门赛-新闻文本分类-单个bert模型分数0.961
  17. php时间戳 中文,PHP时间戳-WEB资讯专栏-DMOZ中文网站分类目录-免费收录各类优秀网站的中文网站目录....
  18. 来吧,嘤!,c++高级编程介绍
  19. 我爱Flask之URL和Flask视图介绍
  20. java 后端开发技能_Java 后端开发,应该重点学习哪些知识/技能?

热门文章

  1. JavaEE入门级别最全教程2--初学者必看
  2. 空间滤波-均值滤波器
  3. Java中,生产者和消费者的问题
  4. 【养生】20210828网上信息摘录
  5. 鲍曼技术大学的计算机专业好不好,除了莫斯科鲍曼技术大学理工强 俄罗斯还有加里宁工程学院...
  6. 十荟团被顶格罚款,社区团购没有无辜者
  7. [你可能忘记的JavaScript] 正则表达式—replace的运用
  8. 服务行业中什么生意最赚钱
  9. Mac安装python3并配置pip
  10. python contourf色阶_matplotlib:plt.contourf(画等高线)