上一节介绍了PriorityQueue的原理,先来简单的回顾一下 PriorityQueue 的原理

以最大堆为例来介绍

  1. PriorityQueue是用一棵完全二叉树实现的。
  2. 不但是棵完全二叉树,而且树中的每个根节点都比它的左右两个孩子节点元素大
  3. PriorityQueue底层是用数组来保存这棵完全二叉树的。

如下图,是一棵最大堆。

最大堆的删除操作

删除指的是删除根元素,也就是图中的100元素

删除元素也就是 shiftDown 操作,向下翻

删除一个根元素有以下步骤:

  1. 将100元素删除,将最后一个元素12放到100的位置上,12成为根节点
  2. 找出 12 这个节点的左右两个孩子节点中的最大的,也就是图中的28节点
  3. 12 出 28节点进行比较,如果12比28小,则交换位置
  4. 12节点继续重复2,3步骤,直到12比它的左右孩子节点都大则停止

最大堆插入一个节点

插入一个节点,也叫shiftUp操作,向上翻

以插入一个节点23为例,步骤如下:

  1. 将23放到二叉树的最后位置,也就是成为了28这个节点的左孩子
  2. 23与它的父节点进行比较,如果比它的父节点大,就交换位置
  3. 23这个节点继续重要第2步骤,直到比它的父节点小方停止比较

代码实现

首先我们先上两张图



我们从左往右,按层序遍历,分别存放到数组的相应索引对应的位置上。

数组的第0个索引位置我们不用,从索引为1的位置开始存放。

最终这个最大堆存放到数组中,如下图

首先实现一个最简单的只存 int 类型的优先级队列 QPriorityQueueInt

完整代码如下:

//最大堆,只存放int,并且没有扩容机制
public class QPriorityQueueInt {
//默认底层数据大小为10
private static int DEFAULT_INIT_CAPACITY = 10; //底层数组
private int[] queue; //节点的个数
private int size; public QPriorityQueueInt() {
//因为数组是从索引 1 的位置开始存放,索引为 0 的位置不用
//所以开辟空间的时候需要加 1
queue = new int[DEFAULT_INIT_CAPACITY + 1]; //当前数组中节点的个数为0
size = 0;
} //返回节点的个数
public int size() {
return size;
} //最大堆是否为空
public boolean isEmpty() {
return size == 0;
} //添加一个节点
public void add(int e) {
//将元素存放到数组当前最后一个位置上
queue[size + 1] = e; //个数需要加1
size++; //需要向上翻
shiftUp(size);
} //向上翻,最大堆中的最后一个节点,不停的与父节点比较
//最大堆中父节点的索引是 k / 2
private void shiftUp(int k) {
// k > 1 ,说明从第2个节点开始,因为如果只有一个节点的话,不需要比较了
// queue[k] > queue[k / 2] ,当前节点大于父节点
while (k > 1 && queue[k] > queue[k / 2]) {
//交换位置
swap(k, k / 2); //把父节点的索引赋值给 k,然后继续重复上面步骤
k = k / 2;
}
} //删除最大堆中的节点
public int poll() { //把第1个位置的节点保存起来
int result = queue[1]; //把最后一个节点放到第1个节点上面,成为整棵树的根节点
queue[1] = queue[size]; //别忘了size 要减1
size--; //最后一个节点成为根节点后,就需要向下翻了
//向下翻的目的就是把大的节点翻上来
shiftDown(1); //返回第1个节点,也就是队头节点
return result;
} //向下翻
private void shiftDown(int k) {
//2 * k <= size ,2*k 是左孩子
//2 * k <= size ,是当前节点有左孩子
//至少有个左孩子才可以交换,因为是完全二叉树,左孩子没有,右孩子肯定没有
while (2 * k <= size) { //比较左右两个孩子节点,将大的节点的索引赋值给 j //左孩子索引
int j = 2 * k;
//如果有右孩子,且 右孩子大于左孩子,将右孩子索引赋值给j
if (j + 1 <= size && queue[j + 1] > queue[j]) {
j = j + 1;
} //现在 j 保存的是左右孩子中较大的节点的索引
//比较当前节点和左右孩子中较大的节点
//如果比左右孩子中较大的节点还大,则不用向下翻了
if (queue[k] > queue[j]) {
break;
} //否则交换当前节点和左右孩子中较大的节点
swap(k, j); //把左右孩子中较大的节点的索引赋值给k,继续向下翻
k = j;
}
} //交换两个位置
private void swap(int i, int j) {
int t = queue[i];
queue[i] = queue[j];
queue[j] = t;
} }

下面是测试代码:

public static void main(String[] args) {
QPriorityQueueInt queue = new QPriorityQueueInt(); //随便弄5个数入队,数越大优先级越大
//由于我们的QPriorityQueueInt默认只支持10个元素
//所以插入的节点个数不要多于10个
queue.add(3);
queue.add(5);
queue.add(1);
queue.add(8);
queue.add(7); //打印
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}

输出如下:

8
7
5
3
1

从输出可以看出来,虽然7是最后入队的,但是优先级比较高,第二次就打印出来了。

优先级队列,同样是用数组实现。但是入队的效率比单纯的用数组排序要高多了。

至于扩容机制,读者可以自己查阅相关资源,自己实现。

4.2 手写Java PriorityQueue 核心源码 - 实现篇的更多相关文章

  1. 4.1 手写Java PriorityQueue 核心源码 - 原理篇

    本章先讲解优先级队列和二叉堆的结构.下一篇代码实现 从一个需求开始 假设有这样一个需求:在一个子线程中,不停的从一个队列中取出一个任务,执行这个任务,直到这个任务处理完毕,再取出下一个任务,再执行. ...

  2. 6 手写Java LinkedHashMap 核心源码

    概述 LinkedHashMap是Java中常用的数据结构之一,安卓中的LruCache缓存,底层使用的就是LinkedHashMap,LRU(Least Recently Used)算法,即最近最少 ...

  3. 3 手写Java HashMap核心源码

    手写Java HashMap核心源码 上一章手写LinkedList核心源码,本章我们来手写Java HashMap的核心源码. 我们来先了解一下HashMap的原理.HashMap 字面意思 has ...

  4. 1 手写Java ArrayList核心源码

    手写ArrayList核心源码 ArrayList是Java中常用的数据结构,不光有ArrayList,还有LinkedList,HashMap,LinkedHashMap,HashSet,Queue ...

  5. 2 手写Java LinkedList核心源码

    上一章我们手写了ArrayList的核心源码,ArrayList底层是用了一个数组来保存数据,数组保存数据的优点就是查找效率高,但是删除效率特别低,最坏的情况下需要移动所有的元素.在查找需求比较重要的 ...

  6. 5 手写Java Stack 核心源码

    Stack是Java中常用的数据结构之一,Stack具有"后进先出(LIFO)"的性质. 只能在一端进行插入或者删除,即压栈与出栈 栈的实现比较简单,性质也简单.可以用一个数组来实 ...

  7. Java HashMap 核心源码解读

    本篇对HashMap实现的源码进行简单的分析. 所使用的HashMap源码的版本信息如下: /* * @(#)HashMap.java 1.73 07/03/13 * * Copyright 2006 ...

  8. 手撕spring核心源码,彻底搞懂spring流程

    引子 十几年前,刚工作不久的程序员还能过着很轻松的日子.记得那时候公司里有些开发和测试的女孩子,经常有问题解决不了的,不管什么领域的问题找到我,我都能帮她们解决.但是那时候我没有主动学习技术的意识,只 ...

  9. 手写JAVA虚拟机(二)——实现java命令行

    查看手写JAVA虚拟机系列可以进我的博客园主页查看. 我们知道,我们编译.java并运行.class文件时,需要一些java命令,如最简单的helloworld程序. 这里的程序最好不要加包名,因为加 ...

随机推荐

  1. Html5的placeholder属性(IE兼容)

    HTML5对Web Form做了很多增强,比方input新增的type类型.Form Validation等. Placeholder是HTML5新增的还有一个属性,当input或者textarea设 ...

  2. 程序基石系列之C++多态的前提条件

    准备知识 C++中多态(polymorphism)有下面三个前提条件: 必须存在一个继承体系结构. 继承体系结构中的一些类必须具有同名的virtual成员函数(virtualkeyword) 至少有一 ...

  3. EasyDarwin开源团队招募开发组成员

    EasyDarwin开源流媒体服务器项目招募开发组成员,共同更新和维护EasyDarwin流媒体服务器,决策EasyDarwin后续开发方向: 加入要求: 1.对开源流媒体项目有浓厚兴趣: 2.有一定 ...

  4. 2-3-4树的java实现

    一.什么是2-3-4树 2-3-4树和红黑树一样,也是平衡树.只不过不是二叉树,它的子节点数目可以达到4个. 每个节点存储的数据项可以达到3个.名字中的2,3,4是指节点可能包含的子节点数目.具体而言 ...

  5. Android笔记之文本随滑块移动的SeekBar

    效果图 FloatingTextSeekBar.java package com.bu_ish.blog; import android.content.Context; import android ...

  6. Dockder的CS模式:

    Docker的守护进程一直运行, yw1989@ubuntu:~$ ps -ef | grep docker : 就是docxker的守护进程 root : ? :: /usr/bin/dockerd ...

  7. [RK3288][Android6.0] 调试笔记 --- 移除uboot和kernel开机logo【转】

    本文转载自:http://blog.csdn.net/kris_fei/article/details/71600690 Platform: RockchipOS: Android 6.0Kernel ...

  8. Gym - 100187A A - Potion of Immortality —— 贪心

    题目链接:http://codeforces.com/gym/100187/problem/A 题解: 光题意就想了很久:在最坏情况下的最小兔子数.其实就是至少用几只兔子就一定能找出仙药(答案存在的话 ...

  9. html5--4-5 embed元素及其他

    html5--4-5 embed元素及其他 学习要点 掌握embed元素的使用 了解object元素的使用 温馨提示:关于video和audio的事件方法等涉及都JavaScript知识的内容,暂时不 ...

  10. Batch Normalization层

    Batch Normalization的加速作用体现在两个方面:一是归一化了每层和每维度的scale,所以可以整体使用一个较高的学习率,而不必像以前那样迁就小scale的维度:二是归一化后使得更多的权 ...