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


相关文章
|
5天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
64 9
|
8天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
10天前
|
存储 Java 调度
Java 中的优先队列 PriorityQueue 详解
【10月更文挑战第22天】优先队列 PriorityQueue 是一种非常实用的数据结构,在许多场景中都能发挥重要作用。通过深入了解其特点和用法,我们可以更好地利用它来解决实际问题。
|
8天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
27天前
初步认识栈和队列
初步认识栈和队列
57 10
|
27天前
|
存储 算法 Java
🏗️Java零基础:深入了解Java 堆
【10月更文挑战第2天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
20 3
|
27天前
|
存储 算法 定位技术
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
这篇文章主要介绍了稀疏数组和队列的概念、应用实例以及如何使用数组模拟队列和环形队列的实现方法。
20 0
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
|
22天前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
13 0
|
27天前
探索数据结构:队列的的实现与应用
探索数据结构:队列的的实现与应用
|
27天前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
15 0