堆排序算法(图解详细流程)

堆排序算法(图解详细流程)

堆排序的时间复杂度O(N*logN),额外空间复杂度O(1),是一个不稳定性的排序

目录

一 准备知识

1.1  大根堆和小根堆

二 堆排序基本步骤

2.1 构造堆

2.2 固定最大值再构造堆

三 总结

四 代码

 


一 准备知识

堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆

1.1  大根堆和小根堆

性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图

 我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子

还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么

1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)

2.左孩子索引:2*i+1

3.右孩子索引:2*i+2

所以上面两个数组可以脑补成堆结构,因为他们满足堆的定义性质:

大根堆:arr(i)>arr(2*i+1) && arr(i)>arr(2*i+2)

小根堆:arr(i)<arr(2*i+1) && arr(i)<arr(2*i+2)

二 堆排序基本步骤

基本思想:

1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2.1 构造堆

将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)

假设存在以下数组

主要思路:第一次保证0~0位置大根堆结构(废话),第二次保证0~1位置大根堆结构,第三次保证0~2位置大根堆结构...直到保证0~n-1位置大根堆结构(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端)

插入6的时候,6大于他的父结点3,即arr(1)>arr(0),则交换;此时,保证了0~1位置是大根堆结构,如下图:

                                     (友情提示:待交换的数为蓝色,交换后的数为绿色)

 插入8的时候,8大于其父结点6,即arr(2)>arr(0),则交换;此时,保证了0~2位置是大根堆结构,如下图

插入5的时候,5大于其父结点3,则交换,交换之后,5又发现比8小,所以不交换;此时,保证了0~3位置大根堆结构,如下图 

插入7的时候,7大于其父结点5,则交换,交换之后,7又发现比8小,所以不交换;此时整个数组已经是大根堆结构 

 

2.2 固定最大值再构造堆

此时,我们已经得到一个大根堆,下面将顶端的数与最后一位数交换,然后将剩余的数再构造成一个大根堆

                                    (友情提示:黑色的为固定好的数字,不再参与排序) 

 此时最大数8已经来到末尾,则固定不动,后面只需要对顶端的数据进行操作即可,拿顶端的数与其左右孩子较大的数进行比较,如果顶端的数大于其左右孩子较大的数,则停止,如果顶端的数小于其左右孩子较大的数,则交换,然后继续与下面的孩子进行比较

下图中,5的左右孩子中,左孩子7比右孩子6大,则5与7进行比较,发现5<7,则交换;交换后,发现5已经大于他的左孩子,说明剩余的数已经构成大根堆,后面就是重复固定最大值,然后构造大根堆

如下图:顶端数7与末尾数3进行交换,固定好7,

剩余的数开始构造大根堆 ,然后顶端数与末尾数交换,固定最大值再构造大根堆,重复执行上面的操作,最终会得到有序数组

 

三 总结

到这里,大家应该对堆排序都有了自己的见解,我们对上面的流程总结下:

1、首先将无需数组构造成一个大根堆(新插入的数据与其父结点比较)

2、固定一个最大值,将剩余的数重新构造成一个大根堆,重复这样的过程

四 代码

代码中主要两个方法:

1、将待排序数组构造成一个大根堆(元素上升)

2、固定一个最大值,将剩余的数再构造成一个大根堆(元素下降)

 
  1. //堆排序

  2. public static void heapSort(int[] arr) {

  3. //构造大根堆

  4. heapInsert(arr);

  5. int size = arr.length;

  6. while (size > 1) {

  7. //固定最大值

  8. swap(arr, 0, size - 1);

  9. size--;

  10. //构造大根堆

  11. heapify(arr, 0, size);

  12.  
  13. }

  14.  
  15. }

  16.  
  17. //构造大根堆(通过新插入的数上升)

  18. public static void heapInsert(int[] arr) {

  19. for (int i = 0; i < arr.length; i++) {

  20. //当前插入的索引

  21. int currentIndex = i;

  22. //父结点索引

  23. int fatherIndex = (currentIndex - 1) / 2;

  24. //如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点

  25. //然后继续和上面的父结点值比较,直到不大于父结点,则退出循环

  26. while (arr[currentIndex] > arr[fatherIndex]) {

  27. //交换当前结点与父结点的值

  28. swap(arr, currentIndex, fatherIndex);

  29. //将当前索引指向父索引

  30. currentIndex = fatherIndex;

  31. //重新计算当前索引的父索引

  32. fatherIndex = (currentIndex - 1) / 2;

  33. }

  34. }

  35. }

  36. //将剩余的数构造成大根堆(通过顶端的数下降)

  37. public static void heapify(int[] arr, int index, int size) {

  38. int left = 2 * index + 1;

  39. int right = 2 * index + 2;

  40. while (left < size) {

  41. int largestIndex;

  42. //判断孩子中较大的值的索引(要确保右孩子在size范围之内)

  43. if (arr[left] < arr[right] && right < size) {

  44. largestIndex = right;

  45. } else {

  46. largestIndex = left;

  47. }

  48. //比较父结点的值与孩子中较大的值,并确定最大值的索引

  49. if (arr[index] > arr[largestIndex]) {

  50. largestIndex = index;

  51. }

  52. //如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环

  53. if (index == largestIndex) {

  54. break;

  55. }

  56. //父结点不是最大值,与孩子中较大的值交换

  57. swap(arr, largestIndex, index);

  58. //将索引指向孩子中较大的值的索引

  59. index = largestIndex;

  60. //重新计算交换之后的孩子的索引

  61. left = 2 * index + 1;

  62. right = 2 * index + 2;

  63. }

  64.  
  65. }

  66. //交换数组中两个元素的值

  67. public static void swap(int[] arr, int i, int j) {

  68. int temp = arr[i];

  69. arr[i] = arr[j];

  70. arr[j] = temp;

  71. }

                                                              友情提示:手机观看,可以左右滑动 

转载:https://blog.csdn.net/u010452388/article/details/81283998?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.channel_param

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值