前言:

大家好,我是春风。

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

一、堆结构

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

  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. 子div超出父div_菜鸟学 react props 子到父
  2. iOS js oc相互调用(JavaScriptCore)(二)
  3. 有这10个特征的项目领导者做的项目,失败率增加60%
  4. ubuntu安装ruby、安装sass
  5. centos6.8安装node
  6. ssl2331OJ1373-鱼塘钓鱼 之3【dp】
  7. caffe使用训练好的模型对自己的一张图片进行测试
  8. linux在安全模式下如何编辑,在安全模式下修改initrd文件
  9. 最强战队出炉,2020腾讯广告算法大赛圆满落幕
  10. 机器学习--支持向量机(四)SMO算法详解
  11. 电脑计算器_教训!19年中级败给了电脑计算器,CPA难道要步后尘?
  12. Java项目:医院门诊收费管理系统(java+html+jdbc+mysql)
  13. linux桌面鼠标变一只手,Linux_安装鼠标主题 让 Ubuntu 的鼠标变漂亮,对ubuntu默认的白色鼠标主题厌 - phpStudy...
  14. 念荆轲[原创诗一首]
  15. 网络和浏览器相关笔记
  16. Linux时间同步(Fri Nov 16 12:12:13 Local time zone must be set--see zic manual page 2018)解决办法
  17. java读取jpg点数_我的世界:基岩版beta1.16.0.61修复59个“特性”,同步Java版?
  18. 针对IE浏览器的兼容性ie7、ie8、ie9
  19. hive正则表达式regexp_extract
  20. monthly rollup和security only的区别

热门文章

  1. 出海新加坡,瑞幸咖啡驶向更广阔蓝海
  2. 香港科技大学硕士浅谈职业选择
  3. c 语言磁盘调度算法,磁盘调度算法
  4. Revit坐标系概念深入理解及应用:内部点、原点、项目基点、测量点、共享坐标系、地理坐标及之间关系和衍生概念操作(详细)
  5. mysql怎么给数据加序号_Mysql 查询数据并按顺序添加序号
  6. 在【运行】时输入IP地址访问局域网保存了凭据后如何取消?
  7. 组网技术 | 远程telnet管理路由器
  8. Navicat下载安装:
  9. 前端标注切图利器-像素大厨(PxCook)
  10. 配套深信服 SASE 一粒云发布文件内部安全管理方案