【Java数据结构】集合PriorityQueue及其背后的数据结构堆(优先级队列)(一)

简介: 【Java数据结构】集合PriorityQueue及其背后的数据结构堆(优先级队列)

优先级队列(PriorityQueue)

优先级队列的概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。

堆(Heap)

堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。  

堆的性质

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储

注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低

堆的创建:

调整方式:

向下调整 是让调整的结点与其孩子节点进行比较

向上调整 是让调整的结点与其父亲结点进行比较

已知双亲的下标,则左孩子的下标为:left=2parent+1;

则右孩子的下标为:left=2parent+2;

已知孩子结点(不区分左右)的下标,则双亲的下标为:(child-1)/2

大根堆实现代码:

1. public class TestHeap {
2. 
3. public int[]elem;
4. public int usedSize;
5. 
6. public static final int DEFAULT_SIZE=10;
7. 
8. public TestHeap() {
9.         elem=new int[DEFAULT_SIZE];
10.     }
11. 
12. public void initElem(int[]arr){
13. 
14. for(int i=0;i<arr.length;i++){
15.             elem[i]=arr[i];
16.             usedSize++;
17.         }
18. 
19.     }
20. 
21. public void creatHeap(){
22. //对所有子树的进行判断
23. for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
24. //进行调整
25.             shiftDown(parent,usedSize);
26.         }
27. 
28.     }
29. 
30. /**
31.      *
32.      * @param root 每颗子树的根
33.      * @param len   每颗子树结束的位置
34.      */
35. 
36. public void shiftDown(int root,int len){
37. int child=2*root+1;
38. //保证有左孩子
39. while (child<len){
40. if(child+1<len&&elem[child]<elem[child+1]){
41. //特别注意这个判断的条件(保证有右孩子才能执行下面的操作)
42. 
43.                 child++;//右移
44.             }
45. //现在child下标一定是左右孩子的最大值的下标
46. 
47. //判断root下标的值和孩子的最大值
48. if(elem[child]>elem[root]){
49. int temp=elem[child];
50.                 elem[child]=elem[root];
51.                 elem[root]=temp;
52.                 root=child;
53.                 child=2*root+1;
54.             }else{
55. break;
56.             }
57. 
58.         }
59. 
60. 
61.     }
62. }

小根堆实现代码:

1. public class TestHeap {
2. 
3. public int[]elem;
4. public int usedSize;
5. 
6. public static final int DEFAULT_SIZE=10;
7. 
8. public TestHeap() {
9.         elem=new int[DEFAULT_SIZE];
10.     }
11. 
12. public void initElem(int[]arr){
13. 
14. for(int i=0;i<arr.length;i++){
15.             elem[i]=arr[i];
16.             usedSize++;
17.         }
18. 
19.     }
20. 
21. public void creatHeap(){
22. //对所有子树的进行判断
23. for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
24. //进行调整
25.             shiftDown(parent,usedSize);
26.         }
27. 
28.     }
29. 
30. /**
31.      *
32.      * @param root 每颗子树的根
33.      * @param len   每颗子树结束的位置
34.      */
35. 
36. public void shiftDown(int root,int len){
37. int child=2*root+1;
38. //保证有左孩子
39. while (child<len){
40. //            if(child+1<len&&elem[child]<elem[child+1]){
41. //                //特别注意这个判断的条件(保证有右孩子才能执行下面的操作)
42. //
43. //                child++;//右移
44. //            }
45. //现在child下标一定是左右孩子的最大值的下标
46. 
47. if(child+1<len&&elem[child]>elem[child+1]){
48. //特别注意这个判断的条件(保证有右孩子才能执行下面的操作)
49. 
50.                 child++;//右移
51.             }
52. //现在child下标一定是左右孩子的最小值的下标
53. 
54. //判断root下标的值和孩子的最大值
55. //            if(elem[child]>elem[root]){
56. //                int temp=elem[child];
57. //                elem[child]=elem[root];
58. //                elem[root]=temp;
59. //                root=child;
60. //                child=2*root+1;
61. //            }else{
62. //                break;
63. //            }
64. 
65. //判断root下标的值和孩子的最小值
66. if(elem[child]<elem[root]){
67. int temp=elem[child];
68.                 elem[child]=elem[root];
69.                 elem[root]=temp;
70.                 root=child;
71.                 child=2*root+1;
72.             }else{
73. break;
74.             }
75. 
76.         }
77. 
78. 
79.     }
80. }

建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

因此:建堆的时间复杂度为O(N)

堆的插入与删除

堆的插入

堆的插入总共需要两个步骤:

1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)

2. 将最后新插入的节点向上调整,直到满足堆的性质

1. /**
2.      * 入队:仍然要保持是大根堆
3.      * @param val
4.      */
5. public void push(int val) {
6. if(isFull()){
7.             elem= Arrays.copyOf(this.elem,2*this.elem.length);
8.         }
9.         elem[usedSize]=val;
10.         usedSize++;
11.         shiftUp(usedSize-1);
12. 
13.     }
14. 
15. private void shiftUp(int child) {
16. 
17. int parent=(child-1)/2;
18. 
19. while(child>0)
20. if(elem[child]>elem[parent]){
21. 
22. int temp=elem[child];
23.             elem[child]=elem[parent];
24.             elem[parent]=temp;
25.             child=parent;
26.             parent=(child-1)/2;
27.         }else{
28. break;
29.         }
30.     }


相关文章
|
9天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
30 5
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
49 6
|
1月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
存储 算法 Java
Java 中常用的数据结构
【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
33 6
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
178 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1
|
23天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
下一篇
DataWorks