2013-09-13 16:36 16408人阅读 评论(1) 收藏 举报

本文章已收录于:

版权声明:本文为博主原创文章,未经博主允许不得转载。

转自:码农下的天桥

目录(?)[+]

  1. 堆排序解释第一篇描述不太清楚
  2. 最大堆的插入删除调整排序操作图解程序JAVA

此坑待埋。

点击打开漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析链接

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

二叉排序树与二叉堆

堆排序(注:这篇文章说明了如何从一个数组构建一个最大堆,推荐看)

最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)

下面来说一说具体算法。

堆排序解释第一篇(描述不太清楚)

1.堆

堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:

Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]

即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。

堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

2.堆排序的思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

其基本思想为(大顶堆):

1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;

2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];

3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

操作过程如下:

1)初始化堆:将R[1..n]构造为堆;

2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

下面举例说明:

给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。

首先根据该数组元素构建一个完全二叉树,得到

 然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:

20和16交换后导致16不满足堆的性质,因此需重新调整

这样就得到了初始堆。

即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。

此时3位于堆顶不满堆的性质,则需调整继续调整

这样整个区间便已经有序了。
从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

【ok,从一个原始数组调整成为一个堆,想必已经很清楚了,那么如何从堆里面获取数据变成已经排好序的数组呢?】
下面是一个例子:

0.待排序序列:

  A[6]={3,5,8,9,1,2},

1.建堆后(建堆过程参见4.4):

 A[6]={9,3,8,5,1,2}

2.9和2交换,然后把9从堆中去掉后:

   A[6]={2,3,8,5,1,9}

3.筛选法调整堆A[5]={2,3,8,5,1}后(调整过程参见4.3):

 A[6]={8,3,2,5,1,9}

4.堆顶记录与最后一个记录互换,重复第二步,但是堆顶记录和最后一个记录的值变了

【附上另一篇文章---最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)】

最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)

 堆有最大堆和最小堆之分,最大堆就是每个节点的值都>=其左右孩子(如果有的话)值的完全二叉树。最小堆便是每个节点的值都<=其左右孩子值的完全二叉树。

设有n个元素的序列{k1,k2,...,kn},当且仅当满足下列关系时,称之为堆。 

堆的三种基本操作(以下以最大堆为例): 
⑴最大堆的插入

由于需要维持完全二叉树的形态,需要先将要插入的结点x放在最底层的最右边,插入后满 足完全二叉树的特点; 
  然后把x依次向上调整到合适位置满足堆的性质,例如下图中插入80,先将80放在最后,然后两次上浮到合适位置. 
  时间:O(logn)。  “结点上浮” 

程序实现:

[java] view plain copy print?
  1. //向最大堆中插入元素, heap:存放堆元素的数组
  2. public static void insert(List<Integer> heap, int value) {
  3. //在数组的尾部添加
  4. if(heap.size()==0)
  5. heap.add(0);//数组下标为0的位置不放元素
  6. heap.add(value);
  7. //开始上升操作
  8. // heapUp2(heap, heap.size() - 1);
  9. heapUp(heap, heap.size() - 1);
  10. }
  11. //上升,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值相交换
  12. public static void heapUp(List<Integer> heap, int index) {
  13. //注意由于数值是从下标为1开始,当index = 1的时候,已经是根节点了
  14. if (index > 1) {
  15. //求出父亲的节点
  16. int parent = index / 2;
  17. //获取相应位置的数值
  18. int parentValue = (Integer) heap.get(parent);
  19. int indexValue = (Integer) heap.get(index);
  20. //如果父亲节点比index的数值小,就交换二者的数值
  21. if (parentValue < indexValue) {
  22. //交换数值
  23. swap(heap, parent, index);
  24. //递归调用
  25. heapUp(heap, parent);
  26. }
  27. }
  28. }
 //向最大堆中插入元素, heap:存放堆元素的数组public static void insert(List<Integer> heap, int value) { //在数组的尾部添加if(heap.size()==0)heap.add(0);//数组下标为0的位置不放元素heap.add(value); //开始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } //上升,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值相交换 public static void heapUp(List<Integer> heap, int index) { //注意由于数值是从下标为1开始,当index = 1的时候,已经是根节点了 if (index > 1) { //求出父亲的节点 int parent = index / 2; //获取相应位置的数值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); //如果父亲节点比index的数值小,就交换二者的数值 if (parentValue < indexValue) { //交换数值 swap(heap, parent, index); //递归调用 heapUp(heap, parent); } } }

⑵删除 
   操作原理是:当删除节点的数值时,原来的位置就会出现一个孔,填充这个孔的方法就是, 
把最后的叶子的值赋给该孔并下调到合适位置,最后把该叶子删除。 
  
如图中要删除72,先用堆中最后一个元素来35替换72,再将35下沉到合适位置,最后将叶子节点删除。 
   “结点下沉”

【勘误】
大家看到上面的删除过程是不是觉得很容易明白?
我也如此认为,直到我写程序时候出现了问题才重新审视删除算法的正确性。
譬如说:现在有一个最小堆,如下图:
现在我选中了93,并且要删除它,接下来会发生什么事?
接下来就是这个算法的结果了:
对,当节点没有空间下沉的时候它就会无所事事,结果导致不对了。
这种情况下面我们可以借用插入过程的上浮调整方式,从最下面开始向上调整。
[java] view plain copy print?
  1. 程序:
  2. /**
  3. * 删除堆中位置是index处的节点
  4. * 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔
  5. * 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除
  6. * @param heap
  7. */
  8. public static void delete(List<Integer> heap,int index) {
  9. //把最后的一个叶子的数值赋值给index位置
  10. heap.set(index, heap.get(heap.size() - 1));
  11. //下沉操作
  12. //heapDown2(heap, index);
  13. heapDown(heap, index);
  14. //把最后一个位置的数字删除
  15. heap.remove(heap.size() - 1);
  16. }
  17. /**
  18. * 递归实现
  19. * 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变
  20. * @param heap 保持堆元素的数组
  21. * @param index 被删除的那个节点的位置
  22. */
  23. public static void heapDown(List<Integer> heap, int index) {
  24. //因为第一个位置存储的是空值,不在考虑之内
  25. int n = heap.size() - 2;
  26. //记录最大的那个儿子节点的位置
  27. int child = -1;
  28. //2*index>n说明该节点没有左右儿子节点了,那么就返回
  29. if (2 * index > n) {
  30. return;
  31. } //如果左右儿子都存在
  32. else if (2 * index < n) {
  33. //定义左儿子节点
  34. child = 2 * index;
  35. //如果左儿子小于右儿子的数值,取右儿子的下标
  36. if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {
  37. child++;
  38. }
  39. }//如果只有一个儿子(左儿子节点)
  40. else if (2 * index == n) {
  41. child = 2 * index;
  42. }
  43. if ((Integer) heap.get(child) > (Integer) heap.get(index)) {
  44. //交换堆中的child,和index位置的值
  45. swap(heap, child, index);
  46. //完成交换后递归调用,继续下降
  47. heapDown(heap, child);
  48. }
  49. }
程序:/*** 删除堆中位置是index处的节点* 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔* 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除* @param heap */ public static void delete(List<Integer> heap,int index) { //把最后的一个叶子的数值赋值给index位置 heap.set(index, heap.get(heap.size() - 1)); //下沉操作 //heapDown2(heap, index); heapDown(heap, index); //把最后一个位置的数字删除 heap.remove(heap.size() - 1); } /*** 递归实现* 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变* @param heap 保持堆元素的数组* @param index 被删除的那个节点的位置*/ public static void heapDown(List<Integer> heap, int index) { //因为第一个位置存储的是空值,不在考虑之内 int n = heap.size() - 2; //记录最大的那个儿子节点的位置 int child = -1; //2*index>n说明该节点没有左右儿子节点了,那么就返回 if (2 * index > n) { return; } //如果左右儿子都存在 else if (2 * index < n) { //定义左儿子节点 child = 2 * index; //如果左儿子小于右儿子的数值,取右儿子的下标 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }//如果只有一个儿子(左儿子节点) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { //交换堆中的child,和index位置的值 swap(heap, child, index); //完成交换后递归调用,继续下降 heapDown(heap, child); } } 

⑶初始化 
方法1:插入法: 
  从空堆开始,依次插入每一个结点,直到所有的结点全部插入到堆为止。 
  时间:O(n*log(n)) 
  方法2:调整法: 
    序列对应一个完全二叉树;从最后一个分支结点(n div 2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。 
  时间:O(n) 
对如图的序列,要使其成为堆,我们从最后一个分支结点(10/2),其值为72开始,依次对每个分支节点53,18,36 45进行调整(下沉). 
 
 

【补充说明】
如何获取相应数组序列?
方法是依次将堆的根节点的小数记下,然后删除根节点,如此反复直到堆为空。上面提到了删除操作,每次删除之后都是要调整堆让堆的性质不变,即根节点必为最大值或最小值,明白了吗?
[java] view plain copy print?
  1. 程序:
  2. /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/
  3. public static void adjust(List<Integer> heap){
  4. for (int i = heap.size() / 2; i > 0; i--)
  5. adjust(heap,i, heap.size()-1);
  6. System.out.println("=================================================");
  7. System.out.println("调整后的初始堆:");
  8. print(heap);
  9. }
  10. /**
  11. * 调整堆,使其满足堆得定义
  12. * @param i
  13. * @param n
  14. */
  15. public static void adjust(List<Integer> heap,int i, int n) {
  16. int child;
  17. for (; i <= n / 2; ) {
  18. child = i * 2;
  19. if(child+1<=n&&heap.get(child)<heap.get(child+1))
  20. child+=1;/*使child指向值较大的孩子*/
  21. if(heap.get(i)< heap.get(child)){
  22. swap(heap,i, child);
  23. /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/
  24. i = child;
  25. }  else break;
  26. }
  27. }
程序:/*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/  public static void adjust(List<Integer> heap){for (int i = heap.size() / 2; i > 0; i--)  adjust(heap,i, heap.size()-1);  System.out.println("=================================================");System.out.println("调整后的初始堆:");print(heap);}/** * 调整堆,使其满足堆得定义 * @param i * @param n */  public static void adjust(List<Integer> heap,int i, int n) {  int child;  for (; i <= n / 2; ) {  child = i * 2;  if(child+1<=n&&heap.get(child)<heap.get(child+1))  child+=1;/*使child指向值较大的孩子*/  if(heap.get(i)< heap.get(child)){  swap(heap,i, child);  /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/  i = child;  }  else break;}  }

(4)最大堆排序

[java] view plain copy print?
  1. //对一个最大堆heap排序
  2. public static void heapSort(List<Integer> heap) {
  3. for (int i = heap.size()-1; i > 0; i--) {
  4. /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/
  5. swap(heap,1, i);
  6. adjust(heap,1, i - 1);
  7. }
  8. }
 //对一个最大堆heap排序public static void heapSort(List<Integer> heap) {  for (int i = heap.size()-1; i > 0; i--) {  /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/  swap(heap,1, i);  adjust(heap,1, i - 1);  }  }

(5)完整的代码

[java] view plain copy print?
  1. import java.util.*;
  2. /**
  3. *实现的最大堆的插入和删除操作
  4. * @author Arthur
  5. */
  6. public class Heap {
  7. /**
  8. * 删除堆中位置是index处的值
  9. * 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔
  10. * 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除
  11. * @param heap 一个最大堆
  12. */
  13. public static void delete(List<Integer> heap,int index) {
  14. //把最后的一个叶子的数值赋值给index位置
  15. heap.set(index, heap.get(heap.size() - 1));
  16. //下沉操作
  17. //heapDown2(heap, index);
  18. heapDown(heap, index); //节点下沉
  19. //把最后一个位置的数字删除
  20. heap.remove(heap.size() - 1);
  21. }
  22. /**
  23. * 节点下沉递归实现
  24. * 删除一个堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变
  25. * @param heap 保持最大堆元素的数组
  26. * @param index 被删除的那个节点的位置
  27. */
  28. public static void heapDown(List<Integer> heap, int index) {
  29. //因为第一个位置存储的是空值,不在考虑之内
  30. int n = heap.size() - 2;
  31. //记录最大的那个儿子节点的位置
  32. int child = -1;
  33. //2*index>n说明该节点没有左右儿子节点了,那么就返回
  34. if (2 * index > n) {
  35. return;
  36. } //如果左右儿子都存在
  37. else if (2 * index < n) {
  38. //定义左儿子节点
  39. child = 2 * index;
  40. //如果左儿子小于右儿子的数值,取右儿子的下标
  41. if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {
  42. child++;
  43. }
  44. }//如果只有一个儿子(左儿子节点)
  45. else if (2 * index == n) {
  46. child = 2 * index;
  47. }
  48. if ((Integer) heap.get(child) > (Integer) heap.get(index)) {
  49. //交换堆中的child,和index位置的值
  50. swap(heap, child, index);
  51. //完成交换后递归调用,继续下降
  52. heapDown(heap, child);
  53. }
  54. }
  55. //非递归实现
  56. public static void heapDown2(List<Integer> heap, int index) {
  57. int child = 0;//存储左儿子的位置
  58. int temp = (Integer) heap.get(index);
  59. int n = heap.size() - 2;
  60. //如果有儿子的话
  61. for (; 2 * index <= n; index = child) {
  62. //获取左儿子的位置
  63. child = 2 * index;
  64. //如果只有左儿子
  65. if (child == n) {
  66. child = 2 * index;
  67. } //如果右儿子比左儿子的数值大
  68. else if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {
  69. child++;
  70. }
  71. //如果数值最大的儿子比temp的值大
  72. if ((Integer) heap.get(child) >temp) {
  73. //交换堆中的child,和index位置的值
  74. swap(heap, child, index);
  75. } else {
  76. break;
  77. }
  78. }
  79. }
  80. //打印链表
  81. public static void print(List<Integer> list) {
  82. for (int i = 1; i < list.size(); i++) {
  83. System.out.print(list.get(i) + " ");
  84. }
  85. System.out.println();
  86. }
  87. //把堆中的a,b位置的值互换
  88. public static void swap(List<Integer> heap, int a, int b) {
  89. //临时存储child位置的值
  90. int temp = (Integer) heap.get(a);
  91. //把index的值赋给child的位置
  92. heap.set(a, heap.get(b));
  93. //把原来的child位置的数值赋值给index位置
  94. heap.set(b, temp);
  95. }
  96. //向最大堆中插入元素
  97. public static void insert(List<Integer> heap, int value) {
  98. //在数组的尾部添加要插入的元素
  99. if(heap.size()==0)
  100. heap.add(0);//数组下标为0的位置不放元素
  101. heap.add(value);
  102. //开始上升操作
  103. // heapUp2(heap, heap.size() - 1);
  104. heapUp(heap, heap.size() - 1);
  105. }
  106. //节点上浮,让插入的数和父节点的数值比较,当大于父节点的时候就和节点的值相交换
  107. public static void heapUp(List<Integer> heap, int index) {
  108. //注意由于数值是从小标为一开始,当index = 1的时候,已经是根节点了
  109. if (index > 1) {
  110. //保存父亲的节点
  111. int parent = index / 2;
  112. //获取相应位置的数值
  113. int parentValue = (Integer) heap.get(parent);
  114. int indexValue = (Integer) heap.get(index);
  115. //如果父亲节点比index的数值小,就交换二者的数值
  116. if (parentValue < indexValue) {
  117. //交换数值
  118. swap(heap, parent, index);
  119. //递归调用
  120. heapUp(heap, parent);
  121. }
  122. }
  123. }
  124. //非递归实现
  125. public static void heapUp2(List<Integer> heap, int index) {
  126. int parent = 0;
  127. for (; index > 1; index /= 2) {
  128. //获取index的父节点的下标
  129. parent = index / 2;
  130. //获得父节点的值
  131. int parentValue = (Integer) heap.get(parent);
  132. //获得index位置的值
  133. int indexValue = (Integer) heap.get(index);
  134. //如果小于就交换
  135. if (parentValue < indexValue) {
  136. swap(heap, parent, index);
  137. }
  138. }
  139. }
  140. /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/
  141. public static void adjust(List<Integer> heap){
  142. for (int i = heap.size() / 2; i > 0; i--)
  143. adjust(heap,i, heap.size()-1);
  144. System.out.println("=================================================");
  145. System.out.println("调整后的初始堆:");
  146. print(heap);
  147. }
  148. /**
  149. * 调整堆,使其满足堆得定义
  150. * @param i
  151. * @param n
  152. */
  153. public static void adjust(List<Integer> heap,int i, int n) {
  154. int child;
  155. for (; i <= n / 2; ) {
  156. child = i * 2;
  157. if(child+1<=n&&heap.get(child)<heap.get(child+1))
  158. child+=1;/*使child指向值较大的孩子*/
  159. if(heap.get(i)< heap.get(child)){
  160. swap(heap,i, child);
  161. /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/
  162. i = child;
  163. }  else break;
  164. }
  165. }
  166. //对一个最大堆heap排序
  167. public static void heapSort(List<Integer> heap) {
  168. for (int i = heap.size()-1; i > 0; i--) {
  169. /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/
  170. swap(heap,1, i);
  171. adjust(heap,1, i - 1);
  172. }
  173. }
  174. public static void main(String args[]) {
  175. List<Integer> array = new ArrayList<Integer>(Arrays.asList(null,
  176. 1, 2, 5, 10, 3, 7, 11, 15, 17, 20, 9, 15, 8, 16));
  177. adjust(array);//调整使array成为最大堆
  178. delete(array,8);//堆中删除下标是8的元素
  179. System.out.println("删除后");
  180. print(array);
  181. insert(array, 99);//堆中插入
  182. print(array);
  183. heapSort(array);//排序
  184. System.out.println("将堆排序后:");
  185. print(array);
  186. System.out.println("-------------------------");
  187. List<Integer> array1=new ArrayList<Integer>();
  188. insert(array1,0);
  189. insert(array1, 1);insert(array1, 2);insert(array1, 5);
  190. insert(array1, 10);insert(array1, 3);insert(array1, 7);
  191. insert(array1, 11);insert(array1, 15); insert(array1, 17);
  192. insert(array1, 20);insert(array1, 9);
  193. insert(array1, 15);insert(array1, 8);insert(array1, 16);
  194. print(array1);
  195. System.out.println("==============================");
  196. array=new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));
  197. adjust(array);
  198. insert(array, 80);//堆中插入
  199. print(array);
  200. delete(array,2);//堆中删除80的元素
  201. print(array);
  202. delete(array,2);//堆中删除72的元素
  203. print(array);
  204. }
  205. }
import java.util.*; /***实现的最大堆的插入和删除操作* @author Arthur*/
public class Heap { /*** 删除堆中位置是index处的值* 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔* 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除* @param heap 一个最大堆*/ public static void delete(List<Integer> heap,int index) { //把最后的一个叶子的数值赋值给index位置 heap.set(index, heap.get(heap.size() - 1)); //下沉操作 //heapDown2(heap, index); heapDown(heap, index); //节点下沉//把最后一个位置的数字删除 heap.remove(heap.size() - 1); } /** * 节点下沉递归实现* 删除一个堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变* @param heap 保持最大堆元素的数组* @param index 被删除的那个节点的位置*/ public static void heapDown(List<Integer> heap, int index) { //因为第一个位置存储的是空值,不在考虑之内 int n = heap.size() - 2; //记录最大的那个儿子节点的位置 int child = -1; //2*index>n说明该节点没有左右儿子节点了,那么就返回 if (2 * index > n) { return; } //如果左右儿子都存在 else if (2 * index < n) { //定义左儿子节点 child = 2 * index; //如果左儿子小于右儿子的数值,取右儿子的下标 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }//如果只有一个儿子(左儿子节点) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { //交换堆中的child,和index位置的值 swap(heap, child, index); //完成交换后递归调用,继续下降 heapDown(heap, child); } } //非递归实现 public static void heapDown2(List<Integer> heap, int index) { int child = 0;//存储左儿子的位置 int temp = (Integer) heap.get(index); int n = heap.size() - 2; //如果有儿子的话 for (; 2 * index <= n; index = child) { //获取左儿子的位置 child = 2 * index; //如果只有左儿子 if (child == n) { child = 2 * index; } //如果右儿子比左儿子的数值大 else if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } //如果数值最大的儿子比temp的值大 if ((Integer) heap.get(child) >temp) { //交换堆中的child,和index位置的值 swap(heap, child, index); } else { break; } } } //打印链表 public static void print(List<Integer> list) { for (int i = 1; i < list.size(); i++) { System.out.print(list.get(i) + " "); } System.out.println();} //把堆中的a,b位置的值互换 public static void swap(List<Integer> heap, int a, int b) { //临时存储child位置的值 int temp = (Integer) heap.get(a); //把index的值赋给child的位置 heap.set(a, heap.get(b)); //把原来的child位置的数值赋值给index位置 heap.set(b, temp); } //向最大堆中插入元素 public static void insert(List<Integer> heap, int value) { //在数组的尾部添加要插入的元素if(heap.size()==0)heap.add(0);//数组下标为0的位置不放元素heap.add(value); //开始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } //节点上浮,让插入的数和父节点的数值比较,当大于父节点的时候就和节点的值相交换 public static void heapUp(List<Integer> heap, int index) { //注意由于数值是从小标为一开始,当index = 1的时候,已经是根节点了 if (index > 1) { //保存父亲的节点 int parent = index / 2; //获取相应位置的数值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); //如果父亲节点比index的数值小,就交换二者的数值 if (parentValue < indexValue) { //交换数值 swap(heap, parent, index); //递归调用 heapUp(heap, parent); } } } //非递归实现 public static void heapUp2(List<Integer> heap, int index) { int parent = 0; for (; index > 1; index /= 2) { //获取index的父节点的下标 parent = index / 2; //获得父节点的值 int parentValue = (Integer) heap.get(parent); //获得index位置的值 int indexValue = (Integer) heap.get(index); //如果小于就交换 if (parentValue < indexValue) { swap(heap, parent, index); } } } /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/  public static void adjust(List<Integer> heap){for (int i = heap.size() / 2; i > 0; i--)  adjust(heap,i, heap.size()-1);  System.out.println("=================================================");System.out.println("调整后的初始堆:");print(heap);}/** * 调整堆,使其满足堆得定义 * @param i * @param n */  public static void adjust(List<Integer> heap,int i, int n) {  int child;  for (; i <= n / 2; ) {  child = i * 2;  if(child+1<=n&&heap.get(child)<heap.get(child+1))  child+=1;/*使child指向值较大的孩子*/  if(heap.get(i)< heap.get(child)){  swap(heap,i, child);  /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/  i = child;  }  else break;}  }  //对一个最大堆heap排序public static void heapSort(List<Integer> heap) {  for (int i = heap.size()-1; i > 0; i--) {  /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/  swap(heap,1, i);  adjust(heap,1, i - 1);  }  }  public static void main(String args[]) { List<Integer> array = new ArrayList<Integer>(Arrays.asList(null,
1, 2, 5, 10, 3, 7, 11, 15, 17, 20, 9, 15, 8, 16));adjust(array);//调整使array成为最大堆delete(array,8);//堆中删除下标是8的元素System.out.println("删除后");print(array);insert(array, 99);//堆中插入print(array); heapSort(array);//排序System.out.println("将堆排序后:");print(array);System.out.println("-------------------------");List<Integer> array1=new ArrayList<Integer>();insert(array1,0);insert(array1, 1);insert(array1, 2);insert(array1, 5);insert(array1, 10);insert(array1, 3);insert(array1, 7);insert(array1, 11);insert(array1, 15); insert(array1, 17);insert(array1, 20);insert(array1, 9);insert(array1, 15);insert(array1, 8);insert(array1, 16);print(array1);System.out.println("==============================");array=new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));adjust(array);insert(array, 80);//堆中插入print(array);delete(array,2);//堆中删除80的元素print(array);delete(array,2);//堆中删除72的元素print(array);}
}

程序运行: 
D:\java>java   Heap 
================================================= 
调整后的初始堆: 
20 17 16 15 9 15 11 1 10 3 2 7 8 5 
删除后 
20 17 16 15 9 15 11 5 10 3 2 7 8 
99 17 20 15 9 15 16 5 10 3 2 7 8 11 
将堆排序后: 
2 3 5 7 8 9 10 11 15 15 16 17 20 99 
------------------------- 
20 17 16 10 15 9 15 0 5 2 11 1 7 3 8 
============================== 
================================================= 
调整后的初始堆: 
93 72 48 53 45 30 18 36 15 35 
93 80 48 53 72 30 18 36 15 35 45 
93 72 48 53 45 30 18 36 15 35 
93 53 48 36 45 30 18 35 15

好了,想必大家都明白了,下一篇文章将放出相关算法及结果

最小堆最大堆的详细解读相关推荐

  1. 堆 最小堆 最大堆 堆排序(小到大,大到小)

    导航 1.了解什么是堆 2.如何创建最小堆,最大堆 3.新增值在堆中如何进行 4.完整的堆排序,升序和降序(两种方式) ---------------------------------------- ...

  2. 数据结构 - 最小堆最大堆

    可以在O(nlogn)的时间复杂度内完成排序 典型的用法是,寻找 第k个/前k个 最大/最小元素,k个有序序列合并 1.合并K个升序链表(最小堆实现) 或许可以改进成每次堆只存放K个元素? # Def ...

  3. Leetcode295 数据流中的中位数-最小堆和最大堆

    题目 中位数是有序列表中间的数.如果列表长度是偶数,中位数则是中间两个数的平均值. 例如,[2,3,4] 的中位数是 3:[2,3] 的中位数是 (2 + 3) / 2 = 2.5 设计一个支持以下两 ...

  4. C++实现最大堆最小堆

    目录 堆和栈的区别 最大堆与最小堆 最大堆的操作 最大堆的插入操作 最大堆的弹出操作 最大堆的C++代码实现 最小堆概念 最小堆的插入操作 最小堆的弹出操作 最小堆的C++代码实现 最大堆最小堆的应用 ...

  5. C++之最小堆、最大堆

    #include<queue> #include<vector> std::priority_queue<int> big_heap; // 构造一个默认最大堆 s ...

  6. 数据结构最小堆C++实现

    1.最小堆的结构: 左右子女的元素均小于等于父节点的元素,叫最小堆:左右子女的元素均大于等于父节点的元素,叫最大堆. 2.最小堆的建立过程如下图所示: 所有节点从上往下,从左往右一次标号0,1,2,. ...

  7. WebServer代码解读(3)【最小堆定时器与队列】

    文章目录 1 - 处理事件 1-1 接收新连接 1-2 最小堆定时器 1-4 将request加入线程池 1-5 处理request 1 - 处理事件 因为epoll_wait函数已经返回了需要处理的 ...

  8. python实现最大堆,最小堆和堆排序

    目录 0.什么是堆 1.最大堆的实现 2.最小堆的实现 3.堆排序 0.什么是堆 小堆和大堆分为如下图: 堆需要满足的条件: 1. 必须是二叉树,且必须是完全二叉树 2. 各个父节点必须大于或小于左右 ...

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

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

  10. 最详细的最小堆构建、插入、删除的过程图解

    转载:http://blog.csdn.net/hrn1216/article/details/51465270 1.简介 最小堆是一棵完全二叉树,非叶子结点的值不大于左孩子和右孩子的值.本文以图解的 ...

最新文章

  1. ssha java接口_java – 从Spring在LDAP中设置SSHA密码
  2. Visual Studio调试之符号文件
  3. python如何编程-如何入门Python编程
  4. python四十一:hashlib模块
  5. python一年365天、初始水平值_2020年11月7日,20201107
  6. 应用多级缓存模式支撑海量读服务
  7. 【算法竞赛学习】二手车交易价格预测-Task2数据分析
  8. apache配置文件详解与优化
  9. git与github学习笔记
  10. 四川专科学校排名四川计算机,四川省现代计算机职业学院2020年排名
  11. Python+Selenium+Firefox配置,及可以启动浏览器,不能自动输入地址
  12. 树莓派Linux内核编译选项如何开启TPM 2.0
  13. JS中的call()和apply()方法
  14. 数据结构课程设计(二)---算术表达式求值
  15. 封装、继承、多态 详解
  16. 迟来太久的2020年总结与2021展望
  17. yaml数组解析_Java使用snakeyaml解析yaml
  18. JavaWeb - 小米商城网 - 项目启动
  19. 怎么查看linux自动校验时间,linux校验时间
  20. Phoenix重磅 | Phoenix核心功能原理及应用场景介绍

热门文章

  1. linux内存不足,swap交换分区创建
  2. 3.1 栈—栈的存储实现和运算实现
  3. zoj 2678 Bishops on a Toral Board 同余方程组,完系(3-I)
  4. ASP中Application与Sesstion对象的使用实例
  5. margin和padding的学习
  6. struts2 中 constant标签
  7. windows安装jdk与配置环境变量详解
  8. 简单整理 - 常用设计模式
  9. Servlet--ListenerFilter
  10. linux 自动清理var log,Linux 系统 /var/log/journal/ 垃圾日志清理-Fun言