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


目录
打赏
0
0
0
1
2
分享
相关文章
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
61 3
|
2月前
|
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
159 77
|
9天前
|
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
24 11
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
2月前
|
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
53 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
2月前
|
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
52 9
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
48 7
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
67 5
|
4月前
|
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
387 9
|
4月前
|
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
65 1

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等