前段时间折腾RabbitMQ踩了个坑,
- 队列中对每个消息设置TTL时,如果队头有消息阻塞会导致TTL已过期的消息不能准时送达,优先队列也同样适用;
- 由于存储方式的原因,RabbitMQ中PriorityQueue一但定义了就不能更改队列的类型;
其中关于PriorityQueue
的机制不太清楚,于是有了这篇关于二叉堆的文章,下一步研究下JDK中PriorityQueue的源码如何实现;
关键点:二叉堆基于一维数组
构建完全二叉树结构,,heapify
复杂度为O(n)
,其add
和pop
复杂度为O(log(n))
,peek
的复杂度为O(1)
,适用于优先队列场景;
数据结构中的堆栈
Stack栈
栈就像装数据的桶或箱子
- 栈它是一种具有后进先出(LIFO)性质的数据结构,也就是说后存放的先取,先存放的后取。
- 如同我们要取出放在箱子里面底下的东西(放入的比较早的物体),我们首先要移开压在它上面的物体(放入的比较晚的物体)。
Heap堆
堆像一棵倒过来的树
- 堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。
- 堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。
- 由于堆的这个特性,常用来实现优先队列;
- 堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。
内存分配中的堆栈
- 内存中的
Stack区
中分配局部变量空间,
- 内存中的
Heap区
是用于分配程序员申请的内存空间。
- 另外还有
静态区
是分配静态变量/全局变量空间的;只读区
是分配常量和程序代码空间的;以及其他一些分区。
- 数据结构中的堆栈,是一种
逻辑结构
,是一种抽象
的概念,如线性表;
- 内存分配中的堆栈,是一种
存储结构
,是一种具体/物理
的概念,是一块具体的内存空间;
下文中的堆是指数据结构中的Heap
堆(Heap)
堆是计算机科学中的一种特别的树状数据结构。
若是满足以下特性,即可称为堆:
- 给定堆中任意节点 P和C,
- 若P是C的母节点,那么P的值会小于等于(或大于等于)C的值。
- 若母节点的值恒小于等于子节点的值,此堆积称为最小堆(min heap);
- 反之,若母节点的值恒大于等于子节点的值,此堆积称为最大堆(max heap)。
- 在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。
堆始于JWJ Williams
在1964年发表的堆排序
(heap sort
),当时他提出了二元堆积树作为此演算法的资料结构。堆在Dijkstra
算法中亦为重要的关键。
堆的应用
在队列
中,调度程序反复提取队列中第一个作业并运行
,实际会有这样的情况
- 某些
耗时较短
的任务等待很长时间
才被处理,
- 某些
耗时不短
,但重要性
较高的任务,应当具有优先权
:优先队列
堆即为解决此类问题设计的一种数据结构。
二叉堆(binary heap)
二叉堆是一种特殊的堆,
- 二叉堆具有
堆
的性质:父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值;
- 二叉堆又具有
二叉树
的性质:二叉堆是完全二叉树
或者是近似完全二叉树);
- 当父节点的键值大于或等于它的每一个子节点的键值时我们称它为最大堆;
- 当父节点的键值小于或等于它的每一个子节点的键值时我们称它为最小堆;
二叉堆的性能
- pop和insert:
O(log(n))
- peek:
O(1)
二叉堆的节点类型
设堆大小为N,则以下节点的数组Index为:
- 父节点:P
- 左孩子:L=2P+1
- 右孩子:R=2P+2
- 最后一个父节点:(N/2)-1
二叉堆的操作类型
- pop:提取堆顶元素
- insert:插入节点
- peek:查询堆顶
构建二叉堆(heapify)
- 将一个未排序的数组构建成二叉堆(升序/降序);
- 将数组假定为一个二叉堆,然后
从最后一个父节点
开始向上遍历
,对每个父节点执行下沉
操作,直到根节点;
Heapify
后只能保证趋势顺序,HeapSort
保证绝对顺序
siftDown
插入节点(add)
- 在
堆尾
新增元素,数组大小不足时需要扩容;
- 从最后一个父节点开始执行
上浮
;
- 直到父节点的值小于或等于该节点,或到达根节点时,才停止上浮,即插入结束。
siftUp
提取最大/小节点(pop)
删除后需要要保持堆的完全二叉树特性;
- 将
堆尾
元素替换到堆顶
位置;
- 抽取
堆顶
元素;
- 以新的
堆顶
元素作为父节点,执行下沉
;
- 直到子节点小于或等于改节点,才停止下沉;
- 返回抽取的堆顶元素,即pop结束;
siftDown
堆排序
可以利于堆的特性(顶总是最值)来处理排序问题;重复从堆顶获取最值来完成数组排序;
- 对未排序的数组执行heapify,构建一个最大堆;
- 重复pop堆顶元素操作(下沉调整),直到堆元素为1;
- pop出的元素组成的数组即为排序后结果;
时间复杂度:O(n) + O(nlog(n)) = O(nlog(n))
二叉堆的Java实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
import com.alibaba.fastjson.JSON; import java.util.Arrays;
public class PriorityHeap {
private Object[] heap;
private int size;
public PriorityHeap(Object[] array) { heap = array; size = heap.length; }
public void heapify() { System.out.println("orgin heap."); printHeap(); int i = (size >> 1) - 1; while (i >= 0) { System.out.println(String.format("heapify,parent[%s] siftdown", i)); siftDown(i, (int) heap[i]); i--; printHeap(); } }
public void add(int node) { heap = Arrays.copyOf(heap, size + 1); size = size + 1; heap[size - 1] = node; siftUp(size - 1, (int) heap[size - 1]); }
public Object pop() { if (size == 0) { throw new IndexOutOfBoundsException("heap is empty"); } Object node = heap[0]; heap[0] = heap[heap.length - 1]; heap = Arrays.copyOf(heap, size - 1); size -= 1; if (size > 1) { System.out.println(String.format("parent[%s] siftdown", 0)); siftDown(0, (int) heap[0]); } return node; }
public Object peak() { if (size == 0) { throw new IndexOutOfBoundsException("heap is empty"); } return heap[0]; }
private void siftDown(int parent, int value) { while (parent < (size / 2)) { int left = (parent << 1) + 1; int right = left + 1; if (right < size && (int) heap[right] < (int) heap[left]) { left = right; } if (value < (int) heap[left]) { break; } heap[parent] = heap[left]; parent = left; } heap[parent] = value; }
private void siftUp(int child, int value) { while (child > 0) { System.out.println(String.format("node[%s] siftup", child)); int parent = (child - 1) / 2; if ((int) heap[child] < (int) heap[parent]) { heap[child] = heap[parent]; heap[parent] = value; child = parent; printHeap(); } else { break; } } }
public int[] heapSort() { heapify();
int[] sortArray = new int[heap.length]; for (int i = 0; i < sortArray.length; i++) { System.out.println(String.format("pop[%s]", i)); sortArray[i] = (int) pop(); } return sortArray; }
public void printHeap() { System.out.println(JSON.toJSONString(getHeap())); }
public Object[] getHeap() { return heap; }
}
|
参考