• 【数据结构】优先级队列(堆)


    作者主页:paper jie_博客

    本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

    本文录入于《JAVA数据结构》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将javaSE基础知识一网打尽,希望可以帮到读者们哦。

    其他专栏:《算法详解》《C语言》《javaSE》等

    内容分享:本期将会分享数据结构中的优先级队列

    优先级队列

    我们了解过的队列,是一种先进先出的数据结构。但是呢,在有些情况下,数据的出入是有优先级的,一般出队时,可能需要优先级高的元素先出队列,在这种场景下,使用队列就不合适了。优先级就比如:我们使用手机玩游戏的时候,有电话打过来的时候,手机就要先处理打过来的电话。

    在这种情况下,我们就应该提供两种基本的操作,返回最高优先级对象和新增对象。这种数据结构就是优先级队列。

    优先级队列的模拟实现

    JDK1.8中的priorityQueue底层使用了堆这种数据结构,而这个堆又是在完全二叉树的基础上进行的调整。

    什么是堆

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

    堆的性质:

    堆中某个节点的值总是不大于(小根堆) 或者不小于(大根堆) 其父节点的值 

    堆一定是一颗完全二叉树 

    堆的存储方式

    堆是一颗完全二叉树,可以用层序的规则采用顺序的方式来高效存储

    这里要注意:对于不是完全二叉树的,就不适合使用顺序存储了,因为要还原二叉树,就要将空节点也保存,这样子就会浪费空间,空间利用率低。

    我们可以根据以下二叉树的性质来还原二叉树:

    如果i为0,表示的节点为根结点,否则i节点的双亲节点为(i - 1)/ 2

    如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子

    如果2 * i + 2 小于节点个数, 则节点i的右孩子下标为2 * i + 1,否则没有右孩

    堆的创建

    这里创建堆的方式是使用向下调整的方式来建堆的。想法就是从堆的最后一个节点的父结点开始向下调整,调整完后再向前一个节点向下调整,依次不断到0下标的节点调整完后就算建堆成功了。

    画图分析:

    建堆的时间复杂度分析

    这里我们考虑最坏的情况即是一颗满叉树且每一个节点都要调整

    画图分析:

    通过上图得知向上调整的时间复杂度为O(n)

    向下调整的复杂度会比向上调整的复杂度高上很多,不建议使用它来建堆

    原因:

    我们知道向上调整是从最后一层的最后一个节点开始,而向下调整只需要从倒数第二层的最后一个节点开始,而往往最后一层的节点就基本等于前面全部节点加起来之和-1

    堆的插入和删除

    堆的插入

    堆的插入就只有4个步骤:

    检查空间大小

    将需插入节点放入最后一个位置

    再从最后一个位置开始向上调整

    最后有效位置加一

    画图分析:

    堆的删除

    注意这里的删除是堆顶的元素。具体步骤:

    将堆顶元素和堆尾元素交换

    有效位置减一

    从堆顶开始向下调整

    画图分析:

    具体代码

    1. import java.util.Arrays;
    2. public class MyHeap {
    3. private int[] elem;
    4. int usedsize;
    5. public MyHeap() {
    6. this.elem = new int[10];
    7. }
    8. //初始化elem数组
    9. public void initElem(int[] array) {
    10. for(int i = 0; i < array.length; i++) {
    11. elem[i] = array[i];
    12. usedsize++;
    13. }
    14. }
    15. //创建大根堆
    16. public void createHeap() {
    17. for(int parent = (usedsize-1-1)/2; parent>=0; parent--) {
    18. siftDown(parent, usedsize);
    19. }
    20. }
    21. private void siftDown(int parent, int len) {
    22. //左孩子
    23. int child = 2*parent+1;
    24. while(child < len) {
    25. if(child+1 < len && elem[child] < elem[child+1] ) {
    26. child += 1;
    27. }
    28. if(elem[child] > elem[parent]) {
    29. //交换
    30. int temp = elem[child];
    31. elem[child] = elem[parent];
    32. elem[parent] = temp;
    33. parent = child;
    34. child = 2*parent+1;
    35. }else {
    36. break;
    37. }
    38. }
    39. }
    40. //插入元素
    41. public void push(int val) {
    42. //满了
    43. if(elem.length == usedsize) {
    44. elem = Arrays.copyOf(elem, elem.length * 2);
    45. }
    46. elem[usedsize] = val;
    47. siftUp(usedsize);
    48. usedsize++;
    49. }
    50. private void siftUp(int child) {
    51. int parent = (child - 1) / 2;
    52. while(child > 0 ) {
    53. if(elem[child] > elem[parent]) {
    54. int temp = elem[child];
    55. elem[child] = elem[parent];
    56. elem[parent] = temp;
    57. child = parent;
    58. parent = (child - 1) / 2;
    59. }else {
    60. break;
    61. }
    62. }
    63. }
    64. /*
    65. *
    66. * 删除元素
    67. *
    68. */
    69. public void pop() {
    70. if(isEmpty()) {
    71. return ;
    72. }
    73. int temp = elem[0];
    74. elem[0] = elem[usedsize-1];
    75. elem[usedsize-1] = temp;
    76. usedsize--;
    77. int parent = 0;
    78. int child = 2 * parent + 1 ;
    79. while(child < usedsize) {
    80. if(child+11]) {
    81. child+=1;
    82. }
    83. if(elem[child] > elem[parent]) {
    84. int tmp = elem[child];
    85. elem[child] = elem[parent];
    86. elem[parent] = tmp;
    87. parent = child;
    88. child = 2*parent+1;
    89. }else {
    90. break;
    91. }
    92. }
    93. }
    94. public boolean isEmpty() {
    95. return usedsize == 0;
    96. }
    97. }

    priorityQueue

    java集合中提供了priorityQueue和priorityBlockingQueue两种类型的优先级队列,priorityQueue是线程不安全的,PriorityQueue是线程安全的。

     

    使用priorityQueue的注意事项:

    1 使用时必须导入PriorityQueue所在的包:

    import java.util.PriorityQueue;

    2 priorityQueue中放置元素必须要能够比较大小,不能插入无法比较大小的对象,不然会抛出ClassCastException异常

    3 不能插入null对象,不然会1抛出nullpointerException

    没有容量限制,任意插入多个元素,内部会自动扩容

    4 插入和删除元素的时间复杂度都是O(logn)

    5 priorityQueue的底层使用堆数据结构

    6 priorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素,想要建立大堆需要自己传比较器

    priorityQueue的使用

    优先级队列的构造方法

    1. static void TestPriorityQueue(){
    2. // 创建一个空的优先级队列,底层默认容量是11
    3. PriorityQueue q1 = new PriorityQueue<>();
    4. // 创建一个空的优先级队列,底层的容量为initialCapacity
    5. PriorityQueue q2 = new PriorityQueue<>(100);
    6. ArrayList list = new ArrayList<>();
    7. list.add(4);
    8. list.add(3);
    9. list.add(2);
    10. list.add(1);
    11. // 用ArrayList对象来构造一个优先级队列的对象
    12. // q3中已经包含了三个元素
    13. PriorityQueue q3 = new PriorityQueue<>(list);
    14. System.out.println(q3.size());
    15. System.out.println(q3.peek());
    16. }

    一般在默认的情况下,PriorityQueue队列是小堆,需要变成大堆需要提供比较器:

    1. class IntCmp implements Comparator{
    2. @Override
    3. public int compare(Integer o1, Integer o2) {
    4. return o2-o1;
    5. }
    6. }

    插入,删除,获取优先级最高的元素

    1. static void TestPriorityQueue2() {
    2. int[] arr = {4, 1, 9, 2, 8, 0, 7, 3, 6, 5};
    3. // 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
    4. // 否则在插入时需要不多的扩容
    5. // 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
    6. PriorityQueue q = new PriorityQueue<>(arr.length);
    7. for (int e : arr) {
    8. q.offer(e);
    9. }
    10. System.out.println(q.size()); // 打印优先级队列中有效元素个数
    11. System.out.println(q.peek()); // 获取优先级最高的元素
    12. // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
    13. q.poll();
    14. q.poll();
    15. System.out.println(q.size()); // 打印优先级队列中有效元素个数
    16. System.out.println(q.peek()); // 获取优先级最高的元素
    17. q.offer(0);
    18. System.out.println(q.peek()); // 获取优先级最高的元素
    19. // 将优先级队列中的有效元素删除掉,检测其是否为空
    20. q.clear();
    21. if (q.isEmpty()) {
    22. System.out.println("优先级队列已经为空!!!");
    23. }
    24. }

    这里要注意优先级队列扩容说明:

    如果容量小于64时,就是按oldCapacity的两倍大小来扩容

    如果容量大于等于64时,就是按oldCapacity的1.5倍大小扩容

    如果扩容后的容量大小大于整型的最大值,则按整型的最大值来扩容

    堆的应用

    用堆为数据结构来封装优先级队列

    Java中的priorityQueue底层就是用堆来实现的

    堆排序

    堆排序就是使用堆的思想来进行排序

    1. 建堆

    升序:建大堆

    降序:建小堆

    2. 使用堆元素删除思想来进行排序

    交换堆顶和堆尾元素,然后向下调整

  • 相关阅读:
    什么是指标体系,怎么搭建一套完整的指标体系?(附PDF素材)
    Java多态
    Winform 多语言化快速解析替换工具-1分钟一个界面
    字符串函数和内存函数(strlen,strcpy ,strcat ,strcmp,strstr,memcpy,memmove,memcmp,memset)
    微信小程序开发15 项目实战 基于云开发开发一个在线商城小程序
    amd羿龙CPU A320系列主板如何安装win7
    WordPress 常规设置页面调用媒体中心上传图片插入URL(新版可用)
    MySql5.5之后的默认存储引擎为InnoDB。
    2023国赛数学建模C题思路代码 - 蔬菜类商品的自动定价与补货决策
    GB/T 26538-2011 烧结保温砖和保温砌块检测
  • 原文地址:https://blog.csdn.net/paperjie/article/details/133747849