1.优先级队列介绍

1.1 优先级队列

  有时在调度任务时,我们会想要先处理优先级更高的任务。例如,对于同一个柜台,在决定队列中下一个服务的用户时,总是倾向于优先服务VIP用户,而让普通用户等待,即使普通的用户是先加入队列的。

  优先级队列和普通的先进先出FIFO的队列类似,最大的不同在于,优先级队列中优先级最高的元素总是最先出队的,而不是遵循先进先出的顺序。

1.2 堆

  优先级队列的接口要求很简单。从逻辑上来说,向量链表或者平衡二叉搜索树等数据结构都可用于实现优先级队列。但考虑到时间和空间的效率,就必须仔细斟酌和考量了。而一种被称为的数据结构非常适合实现优先级队列。’

  堆和二叉搜索树类似,存储的元素在逻辑上是按照层次排放的,在全局任意地方其上层元素优先级大于下层元素,这一顺序性也被称为堆序性,而其中优先级最大的元素被放在最高的层级(大顶堆)。和二叉搜索树的排序方式不同的是,堆中元素的顺序并不是完全的排序,而只是维护了一种偏序关系,被称为堆序性。在这种偏序关系下,元素之间的顺序性比较疏散,维护堆序性的代价比较低,因而在实现优先级队列时,堆的效率要高于平衡二叉搜索树。

1.3 完全二叉堆

  完全二叉堆是堆的一种,其元素在逻辑上是以完全二叉树的形式存放的,但实际却是存储在向量(数组)中的。在这里,我们使用完全二叉堆来实现优先级队列。

  

2.优先级队列ADT接口

  1. /**
  2. * 优先级队列 ADT接口
  3. */
  4. public interface PriorityQueue <E>{
  5.  
  6. /**
  7. * 插入新数据
  8. * @param newData 新数据
  9. * */
  10. void insert(E newData);
  11.  
  12. /**
  13. * 获得优先级最大值(窥视) 不删数据
  14. * @return 当前优先级最大的数据
  15. * */
  16. E peekMax();
  17.  
  18. /**
  19. * 获得并且删除当前优先级最大值
  20. * @return 被删除的 当前优先级最大的数据
  21. */
  22. E popMax();
  23.  
  24. /**
  25. * 获得当前优先级队列 元素个数
  26. * @return 当前优先级队列 元素个数
  27. * */
  28. int size();
  29.  
  30. /**
  31. * 是否为空
  32. * @return true 队列为空
  33. * false 队列不为空
  34. * */
  35. boolean isEmpty();
  36. }

3.完全二叉堆实现细节

3.1 基础属性

  完全二叉堆内部使用之前封装好的向量作为基础。和二叉搜索树类似,用户同样可以通过传入Comparator比较器来指定堆中优先级大小比较的逻辑。

  1. public class CompleteBinaryHeap<E> implements PriorityQueue<E>{
  2. /**
  3. * 内部向量
  4. * */
  5. private ArrayList<E> innerArrayList;
  6.  
  7. /**
  8. * 比较逻辑
  9. * */
  10. private final Comparator<E> comparator;
  11.  
  12. /**
  13. * 当前堆的逻辑大小
  14. * */
  15. private int size;

构造方法:

  1. /**
  2. * 无参构造函数
  3. * */
  4. public CompleteBinaryHeap() {
  5. this.innerArrayList = new ArrayList<>();
  6. this.comparator = null;
  7. }
  8.  
  9. /**
  10. * 指定初始容量的构造函数
  11. * @param defaultCapacity 指定的初始容量
  12. * */
  13. public CompleteBinaryHeap(int defaultCapacity){
  14. this.innerArrayList = new ArrayList<>(defaultCapacity);
  15. this.comparator = null;
  16. }
  17.  
  18. /**
  19. * 指定初始容量的构造函数
  20. * @param comparator 指定的比较器逻辑
  21. * */
  22. public CompleteBinaryHeap(Comparator<E> comparator){
  23. this.innerArrayList = new ArrayList<>();
  24. this.comparator = comparator;
  25. }
  26.  
  27. /**
  28. * 指定初始容量和比较器的构造函数
  29. * @param defaultCapacity 指定的初始容量
  30. * @param comparator 指定的比较器逻辑
  31. * */
  32. public CompleteBinaryHeap(int defaultCapacity, Comparator<E> comparator) {
  33. this.innerArrayList = new ArrayList<>(defaultCapacity);
  34. this.comparator = comparator;
  35. }
  36.  
  37. /**
  38. * 将指定数组 转换为一个完全二叉堆
  39. * @param array 指定的数组
  40. * */
  41. public CompleteBinaryHeap(E[] array){
  42. this.innerArrayList = new ArrayList<>(array);
  43. this.comparator = null;
  44.  
  45. this.size = array.length;
  46.  
  47. // 批量建堆
  48. heapify();
  49. }
  50.  
  51. /**
  52. * 将指定数组 转换为一个完全二叉堆
  53. * @param array 指定的数组
  54. * @param comparator 指定的比较器逻辑
  55. * */
  56. public CompleteBinaryHeap(E[] array, Comparator<E> comparator){
  57. this.innerArrayList = new ArrayList<>(array);
  58. this.comparator = comparator;
  59.  
  60. this.size = array.length;
  61.  
  62. // 批量建堆
  63. heapify();
  64. }

3.2 辅助方法

  由于完全二叉堆在逻辑上等价于一颗完全二叉树,但实际上却采用了一维的向量数据结构来存储元素。因而我们需要实现诸如getParentIndex、getLeftChildIndex、getRightChildIndex等方法来进行完全二叉树和向量表示方法的转换。

  这里,定义了一些私有方法来封装常用的逻辑,用以简化代码。

  1. /**
  2. * 获得逻辑上 双亲节点下标
  3. * @param currentIndex 当前下标
  4. * */
  5. private int getParentIndex(int currentIndex){
  6. return (currentIndex - 1)/2;
  7. }
  8.  
  9. /**
  10. * 获得逻辑上 左孩子节点下标
  11. * @param currentIndex 当前下标
  12. * */
  13. private int getLeftChildIndex(int currentIndex){
  14. return (currentIndex * 2) + 1;
  15. }
  16.  
  17. /**
  18. * 获得逻辑上 右孩子节点下标
  19. * @param currentIndex 当前下标
  20. * */
  21. private int getRightChildIndex(int currentIndex){
  22. return (currentIndex + 1) * 2;
  23. }
  24.  
  25. /**
  26. * 获得末尾下标
  27. * */
  28. private int getLastIndex(){
  29. return this.size - 1;
  30. }
  31.  
  32. /**
  33. * 获得最后一个非叶子节点下标
  34. * */
  35. private int getLastInternal(){
  36. return (this.size()/2) - 1;
  37. }
  38.  
  39. /**
  40. * 交换向量中两个元素位置
  41. * @param a 某一个元素的下标
  42. * @param b 另一个元素的下标
  43. * */
  44. private void swap(int a, int b){
  45. // 现暂存a、b下标元素的值
  46. E aData = this.innerArrayList.get(a);
  47. E bData = this.innerArrayList.get(b);
  48.  
  49. // 交换位置
  50. this.innerArrayList.set(a,bData);
  51. this.innerArrayList.set(b,aData);
  52. }
  53.  
  54. /**
  55. * 进行比较
  56. * */
  57. @SuppressWarnings("unchecked")
  58. private int compare(E t1, E t2){
  59. // 迭代器不存在
  60. if(this.comparator == null){
  61. // 依赖对象本身的 Comparable,可能会转型失败
  62. return ((Comparable) t1).compareTo(t2);
  63. }else{
  64. // 通过迭代器逻辑进行比较
  65. return this.comparator.compare(t1,t2);
  66. }
  67. }

3.3 插入和上滤

  当新元素插入完全二叉堆时,我们直接将其插入向量末尾(堆底最右侧),此时新元素的优先级可能会大于其双亲元素甚至祖先元素,破坏了堆序性,因此我们需要对插入的新元素进行一次上滤操作,使完全二叉堆恢复堆序性。由于堆序性只和双亲和孩子节点相关,因此堆中新插入元素的非祖先元素的堆序性不会受到影响,上滤只是一个局部性的行为

上滤操作

  上滤的元素不断的和自己的双亲节点进行优先级的比较:

  1. 如果上滤元素的优先级较大,则与双亲节点交换位置,继续向上比较。

  2. 如果上滤元素的优先级较小(等于),堆序性恢复,终止比较,结束上滤操作。

  3. 特别的,当上滤的元素被交换到树根节点时(向量下标第0位),此时由于上滤的元素是堆中的最大元素,终止上滤操作。

上滤操作的时间复杂度:

  上滤操作时,上滤元素进行比较的次数正比于上滤元素的深度。因此,上滤操作的时间复杂度为O(logN)

  1. @Override
  2. public void insert(E newData) {
  3. // 先插入新数据到 向量末尾
  4. this.innerArrayList.add(newData);
  5.  
  6. // 获得向量末尾元素下标
  7. int lastIndex = getLastIndex();
  8. // 对向量末尾元素进行上滤,以恢复堆序性
  9. siftUp(lastIndex);
  10. }
  11.  
  12. /**
  13. * 上滤操作
  14. * @param index 需要上滤的元素下标
  15. * */
  16. private void siftUp(int index){
  17. while(index >= 0){
  18. // 获得当前节点
  19. int parentIndex = getParentIndex(index);
  20.  
  21. E currentData = this.innerArrayList.get(index);
  22. E parentData = this.innerArrayList.get(parentIndex);
  23.  
  24. // 如果当前元素 大于 双亲元素
  25. if(compare(currentData,parentData) > 0){
  26. // 交换当前元素和双亲元素的位置
  27. swap(index,parentIndex);
  28.  
  29. // 继续向上迭代
  30. index = parentIndex;
  31. }else{
  32. // 当前元素没有违反堆序性,直接返回
  33. return;
  34. }
  35. }
  36. }

3.4 删除和下滤

  当优先级队列中极值元素出队时,需要在满足堆序性的前提下,选出新的极值元素。

  我们简单的将当前向量末尾的元素放在堆顶,堆序性很有可能被破坏了。此时,我们需要对当前的堆顶元素进行一次下滤操作,使得整个完全二叉堆恢复堆序性。

下滤操作:

  下滤的元素不断的和自己的左、右孩子节点进行优先级的比较:

  1. 双亲节点最大,堆序性恢复,终止下滤。

  2. 左孩子节点最大,当前下滤节点和自己的左孩子节点交换,继续下滤。

  3. 右孩子节点最大,当前下滤节点和自己的右孩子节点交换,继续下滤。

  4. 特别的,当下滤的元素抵达堆底时(成为叶子节点),堆序性已经恢复,终止下滤。

下滤操作时间复杂度:

  下滤操作时,下滤元素进行比较的次数正比于下滤元素的高度。因此,下滤操作的时间复杂度为O(logN)

  1. @Override
  2. public E popMax() {
  3. if(this.innerArrayList.isEmpty()){
  4. throw new CollectionEmptyException("当前完全二叉堆为空");
  5. }
  6.  
  7. // 将当前向量末尾的元素和堆顶元素交换位置
  8. int lastIndex = getLastIndex();
  9. swap(0,lastIndex);
  10.  
  11. // 暂存被删除的最大元素(之前的堆顶最大元素被放到了向量末尾)
  12. E max = this.innerArrayList.get(lastIndex);
  13. this.size--;
  14.  
  15. // 对当前堆顶元素进行下滤,以恢复堆序性
  16. siftDown(0);
  17.  
  18. return max;
  19. }
  20.  
  21. /**
  22. * 下滤操作
  23. * @param index 需要下滤的元素下标
  24. * */
  25. private void siftDown(int index){
  26. int size = this.size();
  27. // 叶子节点不需要下滤
  28. int half = size >>> 1;
  29.  
  30. while(index < half){
  31. int leftIndex = getLeftChildIndex(index);
  32. int rightIndex = getRightChildIndex(index);
  33.  
  34. if(rightIndex < size){
  35. // 右孩子存在 (下标没有越界)
  36.  
  37. E leftData = this.innerArrayList.get(leftIndex);
  38. E rightData = this.innerArrayList.get(rightIndex);
  39. E currentData = this.innerArrayList.get(index);
  40.  
  41. // 比较左右孩子大小
  42. if(compare(leftData,rightData) >= 0){
  43. // 左孩子更大,比较双亲和左孩子
  44. if(compare(currentData,leftData) >= 0){
  45. // 双亲最大,终止下滤
  46. return;
  47. }else{
  48. // 三者中,左孩子更大,交换双亲和左孩子的位置
  49. swap(index,leftIndex);
  50. // 继续下滤操作
  51. index = leftIndex;
  52. }
  53. }else{
  54. // 右孩子更大,比较双亲和右孩子
  55. if(compare(currentData,rightData) >= 0){
  56. // 双亲最大,终止下滤
  57. return;
  58. }else{
  59. // 三者中,右孩子更大,交换双亲和右孩子的位置
  60. swap(index,rightIndex);
  61. // 继续下滤操作
  62. index = rightIndex;
  63. }
  64. }
  65. }else{
  66. // 右孩子不存在 (下标越界)
  67.  
  68. E leftData = this.innerArrayList.get(leftIndex);
  69. E currentData = this.innerArrayList.get(index);
  70.  
  71. // 当前节点 大于 左孩子
  72. if(compare(currentData,leftData) >= 0){
  73. // 终止下滤
  74. return;
  75. }else{
  76. // 交换 左孩子和双亲的位置
  77. swap(index,leftIndex);
  78. // 继续下滤操作
  79. index = leftIndex;
  80. }
  81. }
  82. }
  83. }

3.5 批量元素建堆

  有时,我们需要将一个无序的元素集合数组转换成一个完全二叉堆,这一操作被称为批量建堆。

  一个朴素的想法是:将无序集合中的元素依次插入一个空的完全二叉堆,对每一个新插入的元素进行上滤操作。使用上滤操作实现的对N个元素进行批量建堆的算法,其时间复杂度为O(n.logn),比较直观。

  但还存在一种效率更加高效的批量建堆算法,是以下滤操作为基础实现的,被称为Floyd建堆算法。下滤操作可以看做是将两个较小的堆合并为一个更大堆的过程(单个元素可以被视为一个最小的堆),通过从底到高不断的下滤操作,原本无序的元素集合将通过不断的合并建立较小的堆,最终完成整个集合的建堆过程。

  Floyd建堆算法的时间复杂度的证明较为复杂,其时间复杂度比起以上滤为基础的朴素算法效率高一个数量级,为O(n)

  简单的一种解释是:在完全二叉树中,低层元素的数量要远远少于高层的数量。高层元素的高度较高而深度较低;底层元素的高度较低而深度较高。由于上滤操作的时间复杂度正比于高度,对于存在大量底层元素的完全二叉堆很不友好,使得基于上滤的批量建堆算法效率较低。

  

  1. /**
  2. * 批量建堆(将内部数组转换为完全二叉堆)
  3. * */
  4. private void heapify(){
  5. // 获取下标最大的 内部非叶子节点
  6. int lastInternalIndex = getLastInternal();
  7.  
  8. // Floyd建堆算法 时间复杂度"O(n)"
  9. // 从lastInternalIndex开始向前遍历,对每一个元素进行下滤操作,从小到大依次合并
  10. for(int i=lastInternalIndex; i>=0; i--){
  11. siftDown(i);
  12. }
  13. }

4.堆排序

堆排序主要分为两步进行:

  1. 堆排序首先将传入的数组转化为一个堆(floyd建堆算法,时间复杂度O(n))。

  2. 和选择排序类似,堆排序每次都从未排序的区间中选择出一个极值元素置入已排序区域,在堆中极值元素就是堆顶元素,可以通过popMax方法(时间复杂度O(logN))获得。从数组末尾向前遍历,循环往复直至排序完成,总的时间复杂度为O(N logN)。

  综上所述,堆排序的渐进时间复杂度为O(N logN)。同时由于堆排序能够在待排序数组中就地的进行排序,因此空间效率很高,空间复杂度为(O(1))。

  1. public static <T> void heapSort(T[] array){
  2. CompleteBinaryHeap<T> completeBinaryHeap = new CompleteBinaryHeap<>(array);
  3.  
  4. for(int i=array.length-1; i>=0; i--){
  5. array[i] = completeBinaryHeap.popMax();
  6. }
  7. }

5.完整代码

优先级队列ADT接口:

  1. /**
  2. * 优先级队列 ADT接口
  3. */
  4. public interface PriorityQueue <E>{
  5.  
  6. /**
  7. * 插入新数据
  8. * @param newData 新数据
  9. * */
  10. void insert(E newData);
  11.  
  12. /**
  13. * 获得优先级最大值(窥视)
  14. * @return 当前优先级最大的数据
  15. * */
  16. E peekMax();
  17.  
  18. /**
  19. * 获得并且删除当前优先级最大值
  20. * @return 被删除的 当前优先级最大的数据
  21. */
  22. E popMax();
  23.  
  24. /**
  25. * 获得当前优先级队列 元素个数
  26. * @return 当前优先级队列 元素个数
  27. * */
  28. int size();
  29.  
  30. /**
  31. * 是否为空
  32. * @return true 队列为空
  33. * false 队列不为空
  34. * */
  35. boolean isEmpty();
  36. }

完全二叉堆实现:

  1. /**
  2. * 完全二叉堆 实现优先级队列
  3. */
  4. public class CompleteBinaryHeap<E> implements PriorityQueue<E>{
  5.  
  6. // =========================================成员属性===========================================
  7. /**
  8. * 内部向量
  9. * */
  10. private ArrayList<E> innerArrayList;
  11.  
  12. /**
  13. * 比较逻辑
  14. * */
  15. private final Comparator<E> comparator;
  16.  
  17. /**
  18. * 当前堆的逻辑大小
  19. * */
  20. private int size;
  21.  
  22. // ===========================================构造函数========================================
  23. /**
  24. * 无参构造函数
  25. * */
  26. public CompleteBinaryHeap() {
  27. this.innerArrayList = new ArrayList<>();
  28. this.comparator = null;
  29. }
  30.  
  31. /**
  32. * 指定初始容量的构造函数
  33. * @param defaultCapacity 指定的初始容量
  34. * */
  35. public CompleteBinaryHeap(int defaultCapacity){
  36. this.innerArrayList = new ArrayList<>(defaultCapacity);
  37. this.comparator = null;
  38. }
  39.  
  40. /**
  41. * 指定初始容量的构造函数
  42. * @param comparator 指定的比较器逻辑
  43. * */
  44. public CompleteBinaryHeap(Comparator<E> comparator){
  45. this.innerArrayList = new ArrayList<>();
  46. this.comparator = comparator;
  47. }
  48.  
  49. /**
  50. * 指定初始容量和比较器的构造函数
  51. * @param defaultCapacity 指定的初始容量
  52. * @param comparator 指定的比较器逻辑
  53. * */
  54. public CompleteBinaryHeap(int defaultCapacity, Comparator<E> comparator) {
  55. this.innerArrayList = new ArrayList<>(defaultCapacity);
  56. this.comparator = comparator;
  57. }
  58.  
  59. /**
  60. * 将指定数组 转换为一个完全二叉堆
  61. * @param array 指定的数组
  62. * */
  63. public CompleteBinaryHeap(E[] array){
  64. this.innerArrayList = new ArrayList<>(array);
  65. this.comparator = null;
  66.  
  67. this.size = array.length;
  68.  
  69. // 批量建堆
  70. heapify();
  71. }
  72.  
  73. /**
  74. * 将指定数组 转换为一个完全二叉堆
  75. * @param array 指定的数组
  76. * @param comparator 指定的比较器逻辑
  77. * */
  78. public CompleteBinaryHeap(E[] array, Comparator<E> comparator){
  79. this.innerArrayList = new ArrayList<>(array);
  80. this.comparator = comparator;
  81.  
  82. this.size = array.length;
  83.  
  84. // 批量建堆
  85. heapify();
  86. }
  87.  
  88. // ==========================================外部方法===========================================
  89.  
  90. @Override
  91. public void insert(E newData) {
  92. // 先插入新数据到 向量末尾
  93. this.innerArrayList.add(newData);
  94.  
  95. // 获得向量末尾元素下标
  96. int lastIndex = getLastIndex();
  97. // 对向量末尾元素进行上滤,以恢复堆序性
  98. siftUp(lastIndex);
  99. }
  100.  
  101. @Override
  102. public E peekMax() {
  103. // 内部数组第0位 即为堆顶max
  104. return this.innerArrayList.get(0);
  105. }
  106.  
  107. @Override
  108. public E popMax() {
  109. if(this.innerArrayList.isEmpty()){
  110. throw new CollectionEmptyException("当前完全二叉堆为空");
  111. }
  112.  
  113. // 将当前向量末尾的元素和堆顶元素交换位置
  114. int lastIndex = getLastIndex();
  115. swap(0,lastIndex);
  116.  
  117. // 暂存被删除的最大元素(之前的堆顶最大元素被放到了向量末尾)
  118. E max = this.innerArrayList.get(lastIndex);
  119. this.size--;
  120.  
  121. // 对当前堆顶元素进行下滤,以恢复堆序性
  122. siftDown(0);
  123.  
  124. return max;
  125. }
  126.  
  127. @Override
  128. public int size() {
  129. return this.size;
  130. }
  131.  
  132. @Override
  133. public boolean isEmpty() {
  134. return this.size() == 0;
  135. }
  136.  
  137. @Override
  138. public String toString() {
  139. //:::空列表
  140. if(this.isEmpty()){
  141. return "[]";
  142. }
  143.  
  144. //:::列表起始使用"["
  145. StringBuilder s = new StringBuilder("[");
  146.  
  147. //:::从第一个到倒数第二个元素之间
  148. for(int i=0; i<size-1; i++){
  149. //:::使用", "进行分割
  150. s.append(this.innerArrayList.get(i)).append(",").append(" ");
  151. }
  152.  
  153. //:::最后一个元素使用"]"结尾
  154. s.append(this.innerArrayList.get(size-1)).append("]");
  155. return s.toString();
  156. }
  157.  
  158. public static <T> void heapSort(T[] array){
  159. CompleteBinaryHeap<T> completeBinaryHeap = new CompleteBinaryHeap<>(array);
  160.  
  161. for(int i=array.length-1; i>=0; i--){
  162. array[i] = completeBinaryHeap.popMax();
  163. }
  164. }
  165.  
  166. // =========================================内部辅助函数===========================================
  167. /**
  168. * 上滤操作
  169. * @param index 需要上滤的元素下标
  170. * */
  171. private void siftUp(int index){
  172. while(index >= 0){
  173. // 获得当前节点
  174. int parentIndex = getParentIndex(index);
  175.  
  176. E currentData = this.innerArrayList.get(index);
  177. E parentData = this.innerArrayList.get(parentIndex);
  178.  
  179. // 如果当前元素 大于 双亲元素
  180. if(compare(currentData,parentData) > 0){
  181. // 交换当前元素和双亲元素的位置
  182. swap(index,parentIndex);
  183.  
  184. // 继续向上迭代
  185. index = parentIndex;
  186. }else{
  187. // 当前元素没有违反堆序性,直接返回
  188. return;
  189. }
  190. }
  191. }
  192.  
  193. /**
  194. * 下滤操作
  195. * @param index 需要下滤的元素下标
  196. * */
  197. private void siftDown(int index){
  198. int size = this.size();
  199. // 叶子节点不需要下滤
  200. int half = size >>> 1;
  201.  
  202. while(index < half){
  203. int leftIndex = getLeftChildIndex(index);
  204. int rightIndex = getRightChildIndex(index);
  205.  
  206. if(rightIndex < size){
  207. // 右孩子存在 (下标没有越界)
  208.  
  209. E leftData = this.innerArrayList.get(leftIndex);
  210. E rightData = this.innerArrayList.get(rightIndex);
  211. E currentData = this.innerArrayList.get(index);
  212.  
  213. // 比较左右孩子大小
  214. if(compare(leftData,rightData) >= 0){
  215. // 左孩子更大,比较双亲和左孩子
  216. if(compare(currentData,leftData) >= 0){
  217. // 双亲最大,终止下滤
  218. return;
  219. }else{
  220. // 三者中,左孩子更大,交换双亲和左孩子的位置
  221. swap(index,leftIndex);
  222. // 继续下滤操作
  223. index = leftIndex;
  224. }
  225. }else{
  226. // 右孩子更大,比较双亲和右孩子
  227. if(compare(currentData,rightData) >= 0){
  228. // 双亲最大,终止下滤
  229. return;
  230. }else{
  231. // 三者中,右孩子更大,交换双亲和右孩子的位置
  232. swap(index,rightIndex);
  233. // 继续下滤操作
  234. index = rightIndex;
  235. }
  236. }
  237. }else{
  238. // 右孩子不存在 (下标越界)
  239.  
  240. E leftData = this.innerArrayList.get(leftIndex);
  241. E currentData = this.innerArrayList.get(index);
  242.  
  243. // 当前节点 大于 左孩子
  244. if(compare(currentData,leftData) >= 0){
  245. // 终止下滤
  246. return;
  247. }else{
  248. // 交换 左孩子和双亲的位置
  249. swap(index,leftIndex);
  250. // 继续下滤操作
  251. index = leftIndex;
  252. }
  253. }
  254. }
  255. }
  256.  
  257. /**
  258. * 批量建堆(将内部数组转换为完全二叉堆)
  259. * */
  260. private void heapify(){
  261. // 获取下标最大的 内部非叶子节点
  262. int lastInternalIndex = getLastInternal();
  263.  
  264. // Floyd建堆算法 时间复杂度"O(n)"
  265. // 从lastInternalIndex开始向前遍历,对每一个元素进行下滤操作,从小到大依次合并
  266. for(int i=lastInternalIndex; i>=0; i--){
  267. siftDown(i);
  268. }
  269. }
  270.  
  271. /**
  272. * 获得逻辑上 双亲节点下标
  273. * @param currentIndex 当前下标
  274. * */
  275. private int getParentIndex(int currentIndex){
  276. return (currentIndex - 1)/2;
  277. }
  278.  
  279. /**
  280. * 获得逻辑上 左孩子节点下标
  281. * @param currentIndex 当前下标
  282. * */
  283. private int getLeftChildIndex(int currentIndex){
  284. return (currentIndex * 2) + 1;
  285. }
  286.  
  287. /**
  288. * 获得逻辑上 右孩子节点下标
  289. * @param currentIndex 当前下标
  290. * */
  291. private int getRightChildIndex(int currentIndex){
  292. return (currentIndex + 1) * 2;
  293. }
  294.  
  295. /**
  296. * 获得当前向量末尾下标
  297. * */
  298. private int getLastIndex(){
  299. return this.size - 1;
  300. }
  301.  
  302. /**
  303. * 获得最后一个非叶子节点下标
  304. * */
  305. private int getLastInternal(){
  306. return (this.size()/2) - 1;
  307. }
  308.  
  309. /**
  310. * 交换向量中两个元素位置
  311. * @param a 某一个元素的下标
  312. * @param b 另一个元素的下标
  313. * */
  314. private void swap(int a, int b){
  315. // 现暂存a、b下标元素的值
  316. E aData = this.innerArrayList.get(a);
  317. E bData = this.innerArrayList.get(b);
  318.  
  319. // 交换位置
  320. this.innerArrayList.set(a,bData);
  321. this.innerArrayList.set(b,aData);
  322. }
  323.  
  324. /**
  325. * 进行比较
  326. * */
  327. @SuppressWarnings("unchecked")
  328. private int compare(E t1, E t2){
  329. // 迭代器不存在
  330. if(this.comparator == null){
  331. // 依赖对象本身的 Comparable,可能会转型失败
  332. return ((Comparable) t1).compareTo(t2);
  333. }else{
  334. // 通过迭代器逻辑进行比较
  335. return this.comparator.compare(t1,t2);
  336. }
  337. }
  338. }

  本系列博客的代码在我的 github上:https://github.com/1399852153/DataStructures,存在许多不足之处,请多多指教。

自己动手实现java数据结构(八) 优先级队列的更多相关文章

  1. java PriorityBlockingQueue 基于优先级队列,的读出操作可以阻止.

    java PriorityBlockingQueue 基于优先级队列.的读出操作可以阻止. package org.rui.thread.newc; import java.util.ArrayLis ...

  2. 自己动手实现java数据结构(一) 向量

    1.向量介绍 计算机程序主要运行在内存中,而内存在逻辑上可以被看做是连续的地址.为了充分利用这一特性,在主流的编程语言中都存在一种底层的被称为数组(Array)的数据结构与之对应.在使用数组时需要事先 ...

  3. java中PriorityQueue优先级队列使用方法

    优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最高优先权的元素. PriorityQueue是从JDK1.5开始提供的新的数据结构接口. 如果不提供Comparator的话,优先 ...

  4. 《转》JAVA中PriorityQueue优先级队列使用方法

    该文章转自:http://blog.csdn.net/hiphopmattshi/article/details/7334487 优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最 ...

  5. 【转】java中PriorityQueue优先级队列使用方法

    优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最高优先权的元素. PriorityQueue是从JDK1.5开始提供的新的数据结构接口. 如果不提供Comparator的话,优先 ...

  6. java 中PriorityQueue优先级队列使用方法

    1.前言 优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最高优先权的元素. PriorityQueue是从JDK1.5开始提供的新的数据结构接口. 如果想实现按照自己的意愿进行优 ...

  7. 用Python实现数据结构之优先级队列

    优先级队列 如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了.这样,我们就引入了优先级队列 这 ...

  8. 自己动手实现java数据结构(四)双端队列

    1.双端队列介绍 在介绍双端队列之前,我们需要先介绍队列的概念.和栈相对应,在许多算法设计中,需要一种"先进先出(First Input First Output)"的数据结构,因 ...

  9. 自己动手实现java数据结构(二) 链表

    1.链表介绍 前面我们已经介绍了向量,向量是基于数组进行数据存储的线性表.今天,要介绍的是线性表的另一种实现方式---链表. 链表和向量都是线性表,从使用者的角度上依然被视为一个线性的列表结构.但是, ...

随机推荐

  1. c++沉思录 学习笔记 第五章 代理类

    Vehicle 一个车辆的虚基类 class Vehicle {public: virtual double weight()const = 0; virtual void start() = 0; ...

  2. c++ 计算cpu占用率

    计算CPU占用率就是获取系统总的内核时间 用户时间及空闲时间 其中空闲时间就是内核空转 所以内核时间包含空闲时间 然后计算 运行时间 = 内核时间 加 用户时间 减去 空闲时间 间隔时间 =  内核时 ...

  3. AX_Currency

    Currency::curAmount(9.23,"HKD");  Currency::curAmount2CurAmount(9.23,"RMB"," ...

  4. canvasJS

  5. tensorflow o. 到 tensorflow 1. 部分改变

    一下内容为笔者实际应用中遇到的问题,可能(必须)不全,后面会持续更新. (1) tf.nn.run_cell   改为   tf.contrib.rnn (2) tf.reduce_mean   改为 ...

  6. Linux环境下java开发环境搭建一 JDK搭建

    第一步:下载jdk压缩文件 第二步:上传到家目录下的soft目录下,可以采用winscp,此处下载的是.tar.gz文件 第三步:解压压缩文件,并在/usr/local目录下创建一个jdk7的目录,并 ...

  7. Python从入门到精通之First!

    Python的种类 Cpython Python的官方版本,使用C语言实现,使用最为广泛,CPython实现会将源文件(py文件)转换成字节码文件(pyc文件),然后运行在Python虚拟机上. Jy ...

  8. Linux学习---条件预处理的应用

    预处理的使用: ⑴包含头文件 #include ⑵宏定义 #define    替换,不进行语法检查 ①常量宏定义:#define 宏名 (宏体) (加括号为防止不进行语法检查而出现的错误) eg:# ...

  9. qhfl-6 购物车

    购物车中心 用户点击价格策略加入购物车,个人中心可以查看自己所有购物车中数据 在购物车中可以删除课程,还可以更新购物车中课程的价格策略 所以接口应该有四种请求方式, get,post,patch,de ...

  10. Makefile入门

    相信大家对makefile都不陌生,在Linux下编写程序基本都离不开makefile的编写,我们都知道多个.c文件经过编译器编译后得到多个.o文件,这些文件是互相独立的,但最终我们要得到一个可正常运 ...