堆是一个数组,可以被看成一个近似的完全二叉树,树上的每一个节点对应数组的每个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。

堆排序以及实现的优先队列 - 图1
堆又分为大根堆和小根堆
大根堆:父节点总是大于其子节点,
小根堆:父节点总是小于其子节点。

一、建堆

这里是将一个数组转换为一个大根堆(也可转成小根堆)的结构

  1. public class Heap {
  2. final int MAX = 999;// 定义最大容量999
  3. private int[] heap;
  4. private int size;
  5. /**
  6. * 初始化一个堆
  7. *
  8. * @param a
  9. */
  10. public Heap(int a[]) {
  11. this.heap = new int[MAX];
  12. for (int i = 0; i < a.length; i++)//将数组的值传给heap
  13. heap[i + 1] = a[i];// 堆的下标从1开始
  14. this.size = a.length;
  15. // 建堆
  16. for (int i = this.size / 2; i >= 1; i--) //注释在A,一个图就能明白
  17. max_Heapify(heap, i, size);
  18. }
  19. public int[] getHeap() {
  20. return heap;
  21. }
  22. public int getSize() {
  23. return size;
  24. }
  25. public void printHeap(int heap[], int size) {
  26. for (int i = 1; i <= size; i++)
  27. System.out.print(heap[i]);
  28. }
  29. public int getParent(int i) {// 获取节点i的父节点下标
  30. return i / 2;
  31. }
  32. public int getLeft(int i) {// 左孩子下标
  33. return 2 * i;
  34. }
  35. public int getRight(int i) {// 右孩子下标
  36. return 2 * i + 1;
  37. }
  38. /**
  39. * 调整堆结构,使得以i为根节点的子树满足堆结构
  40. *
  41. * @param a
  42. * @param i
  43. * @param size 堆的大小
  44. * @return
  45. */
  46. public int[] max_Heapify(int a[], int i, int size) {
  47. int left = getLeft(i);
  48. int right = getRight(i);
  49. int largest;
  50. // 找到i与其两个子节点中最大的,交换
  51. if (left <= size && a[i] < a[left]) {
  52. largest = left;
  53. } else
  54. largest = i;
  55. if (right <= size && a[right] > a[largest])
  56. largest = right;
  57. if (largest != i) {
  58. exchange(a, i, largest);
  59. max_Heapify(a, largest, size);// 递归地调整,直到满足堆结构
  60. }
  61. return a;
  62. }
  63. /**堆排序
  64. * 利用最大堆的第一个元素一定是最大的这个特性,每次将最后一个元素与第一个元素交换,然后调整就可以获得最大值,第二大值....
  65. * @param a
  66. * @param size
  67. * @return
  68. */
  69. public int[] heapSort(int a[],int size) {
  70. for(int i = size;i>=1;i--) {
  71. exchange(a,1,i);//交换最后一个元素与第一个元素
  72. size = size-1;
  73. max_Heapify(a,1,size);//每次都调整根节点
  74. }
  75. return a;
  76. }
  77. /**
  78. * 交换两个下标的元素值
  79. *
  80. * @param a
  81. * @param x
  82. * @param y
  83. */
  84. public void exchange(int a[], int x, int y) {
  85. int tmp = a[x];
  86. a[x] = a[y];
  87. a[y] = tmp;
  88. }
  89. }

注释A
image.png

二、堆排序

思想:利用堆结构的特点,堆顶的元素值最大,所以我们每次都将堆顶的元素与堆底的元素交换,使size-1,然后重新调整堆结构,这时候的调整就不会影响到堆底的那个最大的元素了,直到堆的size=1时,就完成了排序。排序的顺序是从大到小确定元素的位置。

  1. public int[] heapSort(int a[],int size) {
  2. for(int i = size;i>=1;i--) {
  3. exchange(a,1,i);//交换最后一个元素与第一个元素
  4. size = size-1;
  5. max_Heapify(a,1,size);//每次都调整根节点
  6. }
  7. return a;
  8. }

三、用堆结构实现优先队列

队列
堆排序是个优秀的算法,但是实际应用中,快速排序的性能一般优于堆排序,尽管如此,堆这一数据结构仍有许多应用——作为高效的优先队列。和堆一样,优先队列也有两种形式,最大优先队列和最小优先队列。这里我关注于如何基于最大堆实现最大优先队列。
实现
Maximum(S):返回具有最大关键字的元素

  1. public int Maximum(int a[], int size) {
  2. return a[1];
  3. }

push:压入元素(上浮的过程)
1.入队操作
优先队列本质上就是用二叉堆来实现的,每次插入一个数据都是插入到数据数组的最后一个位置,然后再做上浮操作,如果插入的数是数组中最大数,自然会上浮到堆顶。如“图1 入队操作”所示:

堆排序以及实现的优先队列 - 图3

  1. public void push(int heap[],int key) {
  2. int size = this.getSize();
  3. heap[size+1]=key;
  4. this.setSize(size+1);
  5. int i = this.getSize();
  6. while(i>1&&heap[getParent(i)]<heap[i]) {//如果更改后的key值比其父节点要大,就交换两个节点值
  7. exchange(heap,i,getParent(i));
  8. i = getParent(i);
  9. }
  10. }

pop:去掉并返回最大关键字的元素
2.出队操作
出队操作就是返回堆顶最小堆的数据之后用数组最后的数插入到堆顶,之后再做下沉操作。如“图2 出队操作”所示:

堆排序以及实现的优先队列 - 图4

  1. public int pop(int heap[], int size) {
  2. if (size < 1) {
  3. System.out.print("heap underflow");
  4. return -1;
  5. }
  6. int max = heap[1];// 获得最大值
  7. exchange(heap, 1, size);
  8. this.setSize(this.getSize() - 1);// 堆size-1
  9. max_Heapify(heap, 1, this.getSize());// 调整堆结构
  10. return max;
  11. }