
【JDK源码分析系列】PriorityQueue源码分析
发布日期:2021-05-07 20:52:21
浏览次数:31
分类:原创文章
本文共 7355 字,大约阅读时间需要 24 分钟。
【JDK源码分析系列】PriorityQueue源码分析
【1】PriorityQueue 继承体系图示
【2】堆排序算法简介
【2.1】堆的概念
堆是一棵顺序存储的完全二叉树,其中每个结点的关键字都不大于其孩子结点的关键字,即为小根堆;其中每个结点的关键字都不小于其孩子结点的关键字,即为大根堆;
对于 n 个元素的序列 {R0, R1, ... , Rn} 当且仅当满足下列关系之一时,称之为堆,(1) Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)(2) Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)其中 i=1,2,…,n/2 向下取整;
堆与数组的对应关系
设当前元素在数组中以R[i]表示,(1) 其左孩子结点是 R[2*i+1];(2) 其右孩子结点是 R[2*i+2];(3) 其父结点是 R[(i-1)/2];
【2.2】构造初始堆的步骤图示
【2.3】堆排序的步骤图示
【2.4】插入元素
【2.5】删除堆顶元素
【3】PriorityQueue 源码分析
【3.1】PriorityQueue 基本属性
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { // 默认容量 private static final int DEFAULT_INITIAL_CAPACITY = 11; // 元素个数 private int size = 0; // 比较器 private final Comparator<? super E> comparator; // 元素存储的数组 transient Object[] queue; // 修改次数 // 该属性表明PriorityQueue是fast-fail // 非私有以简化嵌套类访问 transient int modCount = 0;}
【3.2】PriorityQueue 构造方法
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { //(1) PriorityQueue是一个小顶堆 //(2) PriorityQueue是非线程安全的 //(3) PriorityQueue不是有序的,只有堆顶存储着最小的元素 //(4) 入队就是堆的插入元素的实现 //(5) 出队就是堆的删除元素的实现 public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { // 检查参数有效性 if (initialCapacity < 1) throw new IllegalArgumentException(); // 初始化数组 this.queue = new Object[initialCapacity]; // 初始化比较器 this.comparator = comparator; }}
【3.3】PriorityQueue 元素入队
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { // 向优先级队列中添加元素 public boolean add(E e) { // 实际调用offer方法 return offer(e); } // 新增元素 public boolean offer(E e) { // 如果是空元素的话,抛异常 if (e == null) throw new NullPointerException(); modCount++; // 取大小 int i = size; // 队列实际大小大于容量时,进行扩容 // 扩容策略是 如果老容量小于 64,2 倍扩容,如果大于 64,50 % 扩容 if (i >= queue.length) grow(i + 1); size = i + 1; // 如果队列为空,当前元素正好处于队头 if (i == 0) queue[0] = e; else // 插入元素到数组size的位置,即最后一个元素的下一位 // 注意size是元素个数 // 需要根据优先级进行排序 siftUp(i, e); return true; } private void siftUp(int k, E x) { // 判断是否自定义了比较器 if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } // 按照从小到大的顺序排列 private void siftUpComparable(int k, E x) { // 获取待发送数据并转换为Comparable类型 Comparable<? super E> key = (Comparable<? super E>) x; // k 是当前队列实际大小的位置 while (k > 0) { // 找到父节点的位置 // 因为元素是从0开始的,所以减1之后再除以2 int parent = (k - 1) >>> 1; // 父节点的值 Object e = queue[parent]; // 比较插入的元素与父节点的值 // 如果比父节点大,则跳出循环 // 否则交换位置 if (key.compareTo((E) e) >= 0) break; // 与父节点交换位置 queue[k] = e; // 现在插入的元素位置移到了父节点的位置 k = parent; } // 最后找到应该插入的位置,放入元素 queue[k] = key; } @SuppressWarnings("unchecked") private void siftUpUsingComparator(int k, E x) { while (k > 0) { // 确定父节点 int parent = (k - 1) >>> 1; // 去除父节点处的元素 Object e = queue[parent]; // 调用比较器比较待插入节点与父节点元素 if (comparator.compare(x, (E) e) >= 0) // 当前节点大于父节点则退出循环 break; // 交换当前节点与父节点 queue[k] = e; // 更新待插入的位置 k = parent; } // 查找到了对应的位置则插入该节点 queue[k] = x; }}
【3.4】PriorityQueue 元素出队
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { @SuppressWarnings("unchecked") public E poll() { // 如果size为0,说明没有元素 if (size == 0) return null; // 弹出元素,元素个数减1 int s = --size; modCount++; // 队列首元素 E result = (E) queue[0]; // 队列末元素 E x = (E) queue[s]; // 将队列末元素删除 queue[s] = null; // 如果弹出元素后还有元素 if (s != 0) // 将队列末元素移到队列首 // 再做自上而下的堆化 siftDown(0, x); // 返回弹出的元素 return result; } private void siftDown(int k, E x) { // 根据是否有比较器,选择不同的方法 if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } @SuppressWarnings("unchecked") private void siftDownUsingComparator(int k, E x) { // 减半 // 叶子节点占了一半的元素 int half = size >>> 1; while (k < half) { // 寻找子节点的位置,这里加1是因为元素从0号位置开始 int child = (k << 1) + 1; // 左子节点的值 Object c = queue[child]; // 右子节点的位置 int right = child + 1; // 左右两边的值进行比较 if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) // 左右节点取其小者 c = queue[child = right]; // 如果比子节点都小,则结束 if (comparator.compare(x, (E) c) <= 0) break; // 如果比最小的子节点大,则交换位置 queue[k] = c; // 指针移到最小子节点的位置继续往下比较 k = child; } // 找到正确的位置,放入元素 queue[k] = x; } @SuppressWarnings("unchecked") private void siftDownComparable(int k, E x) { // 将x转换为Comparable类型 Comparable<? super E> key = (Comparable<? super E>)x; // 只需要比较一半就行了,因为叶子节点占了一半的元素 int half = size >>> 1; // loop while a non-leaf while (k < half) { // 寻找子节点的位置,这里加1是因为元素从0号位置开始 int child = (k << 1) + 1; // assume left child is least // 左子节点的值 Object c = queue[child]; // 右子节点的位置 int right = child + 1; if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) // 左右节点取其小者 c = queue[child = right]; // 如果比子节点都小,则结束 if (key.compareTo((E) c) <= 0) break; // 如果比最小的子节点大,则交换位置 queue[k] = c; // 指针移到最小子节点的位置继续往下比较 k = child; } // 找到正确的位置,放入元素 queue[k] = key; } }
【3.5】PriorityQueue 扩容
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { private void grow(int minCapacity) { // 旧容量 int oldCapacity = queue.length; // Double size if small; else grow by 50% // 旧容量小于64时,容量翻倍 // 旧容量大于等于64,容量只增加旧容量的一半 int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // overflow-conscious code // 检查是否溢出 if (newCapacity - MAX_ARRAY_SIZE > 0) // 溢出的情况下计算最大的容量 newCapacity = hugeCapacity(minCapacity); // 创建出一个新容量大小的新数组并把旧数组元素拷贝过去 queue = Arrays.copyOf(queue, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }}
致谢
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。
【1】
【2】
【3】
发表评论
最新留言
网站不错 人气很旺了 加油
[***.192.178.218]2025年03月28日 15时39分46秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Codeforces 1400E Clear the Multiset(贪心 + 分治)
2021-05-08