【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.     }


相关文章
|
16天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
22 5
【数据结构】优先级队列(堆)从实现到应用详解
|
10天前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
3天前
|
前端开发
07_用队列实现栈
07_用队列实现栈
|
3天前
|
测试技术
02_由两个栈组成的队列
02_由两个栈组成的队列
|
7天前
|
存储
|
24天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
147 3
|
25天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
26 0
【数据结构】栈和队列的深度探索,从实现到应用详解
|
1月前
|
存储 程序员 C语言
堆和栈之间有什么区别
【9月更文挑战第1天】堆和栈之间有什么区别
114 0
|
存储 Java 安全
死磕 java集合之PriorityQueue源码分析
死磕 java集合之PriorityQueue源码分析问题(1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介优先级队列,是0个或多个元素的集合,集合中的每个元素都有一个权重值,每次出队都弹出优先级最大或最小的元素。
1071 0
|
5天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
18 2
下一篇
无影云桌面