一种数据结构,特殊场景使用,同样是使用堆结构,利用堆的特性
概要
- code语言:java、c
- 测试环境:win、java8
- 参考书籍:《数据结构与算法分析java语言描述》 原书第三版、《数据结构:使用C语言》、《算法导论》原书第三版
- 参考链接:漫画:什么是优先队列?
概述
- 需要优先有堆的知识才能更好理解堆支持的优先队列:数据结构与算法:堆排序
- 一般队列:FIFO,先进先出(顺便考下,栈的特点是? – 后进先出哈哈哈)
- 优先级队列:就是说出队列是需要按照一定规则的优先级咯。利用堆结构的话就能出两个类型的优先级队列:
- 最大优先队列,无论入队顺序,当前最大的元素优先出队
- 最小优先队列,无论入队顺序,当前最小的元素优先出队
- 为什么用堆?
- 线性数据结构时间复杂度高,最坏时间是O(N)
- 最大堆的堆顶是整个堆中的最大元素
- 最小堆的堆顶是整个堆中的最小元素
图解优先队列
这里以最大优先队列为例
构造初始堆
最大堆,构造方式这里不赘述,参看:数据结构与算法:堆排序
操作图如下:
假设原始数组情况:
3 | 6 | 4 | 5 | 7 |
---|
最后该初始堆对应的数组情况是:
7 | 6 | 3 | 5 | 4 |
---|
进行入队列演示
假设这里入队一个8。
逻辑:把元素插入到最后,之后会导致堆结构被破坏,然后从下往上开始调整为最大堆结构,这个过程也叫做,上滤
最后该堆对应的数组情况是:
8 | 6 | 7 | 5 | 4 | 3 |
---|
注:入队列时,若数组长度不够,可能需要扩展数组长度
进行出队列演示
逻辑:总是出堆顶元素,这里也就是8出队。并用把最后一个元素放到空出来的堆顶上。之后会导致堆结构被破坏,需要自上而下重新调整为一个最大堆,这个过程叫下沉
最后该堆对应的数组情况是:
7 | 6 | 3 | 5 | 4 | 空 |
---|
代码实现
由于此前一篇介绍堆排序的文章实现的是最大堆,这里我就实现一个最小堆。
此数据结构稍微改动,就可以成为一个通用的数据结构了(加泛型等等)
package com.mym.practice.lock;
/**
* 优先队列
*/
public class PriorityQueue {
/**
* 默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 存数据的数组
*/
private int[] array;
/**
* 当前大小(实际大小)
*/
private int currentSize;
public PriorityQueue(){
array = new int[DEFAULT_CAPACITY];
}
public PriorityQueue(int capacity){
array = new int[capacity];
}
public PriorityQueue(int[] arr){
currentSize = arr.length;
this.array = new int[currentSize * 2];
// 舍弃0位,方便二叉堆计算
for(int i = 1; i < currentSize + 1; i++){
array[i] = arr[i - 1];
}
// 构建初始堆
buildHeap();
}
/**
* 入队列
* @param x 入队列数据
*/
public void offer(int x){
if(currentSize == array.length - 1){
// 需要扩容
enlargeArray(array.length * 2 + 1);
}
// 当前空位
int hole = ++currentSize;
// 重新调整为堆结构
for(array[0] = x; x < array[hole / 2]; hole /= 2){
array[hole] = array[hole / 2];
}
// 入队列,并删除临时使用的
array[hole] = x;
array[0] = 0;
}
/**
* 出队列
* @return
*/
public int poll(){
int result = array[1];
// 更新
array[1] = array[currentSize];
array[currentSize] = 0;
currentSize--;
// 重新调整
buildHeap();
return result;
}
/**
* 下滤,下沉
* @param hole 当前位置
*/
private void percolateDown(int hole){
int child;
int temp = array[hole];
while(hole << 1 <= currentSize){
child = hole << 1; // 左孩。右孩节点 = 左孩节点 + 1
if(child != currentSize && array[child + 1] < array[child]){
child++;
}
if(array[child] < temp){
array[hole] = array[child];
}else{
break;
}
hole = child;
}
array[hole] = temp;
}
/**
* 构建堆
*/
private void buildHeap(){
for(int i = currentSize / 2; i > 0; i--){
percolateDown(i);
}
}
/**
* 扩展数组
* @param newSize
*/
private void enlargeArray(int newSize){
if(newSize <= array.length){
return;
}
int[] ints = new int[newSize];
for(int i = 0; i < array.length; i++){
ints[i] = array[i];
}
array = ints;
}
/**
* 删除最小的
* @return 删除的数值
*/
public int deleteMin(){
return poll();
}
/**
* 打印数组结构
*/
public void print(){
for(int i = 0; i < array.length; i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {3,6,4,5,7};
PriorityQueue priorityQueue = new PriorityQueue(arr);
priorityQueue.print();
int poll = priorityQueue.poll();
System.out.println("出队列的是:" + poll);
System.out.println("现堆数组情况:");
priorityQueue.print();
int min = priorityQueue.deleteMin();
System.out.println("删除当前最小的元素是:" + min);
System.out.println("现堆数组情况:");
priorityQueue.print();
priorityQueue.offer(3);
System.out.println("入队列数后,现堆数组情况:");
priorityQueue.print();
}
}
执行结果
0 3 5 4 6 7 0 0 0 0
出队列的是:3
现堆数组情况:
0 4 5 7 6 0 0 0 0 0
删除当前最小的元素是:4
现堆数组情况:
0 5 6 7 0 0 0 0 0 0
入队列数后,现堆数组情况:
0 3 5 7 6 0 0 0 0 0
堆 优先队列特点
- 入队列时间复杂度为logn(上滤为logn)
- 出队列时间复杂度也为logn(下沉为logn)
优先队列一般应用场景
- 哈哈,jdk1.5之后已经实现了并作为类库可以直接使用
- 可以以某种权重作为排序字段来设计队列优先级的使用场景