文章目录
前言
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。
一、优先级队列(堆)的概念
首先,在建堆之前我们需要了解到:
1.堆逻辑上是一颗完全二叉树;
2.堆物理上是保存在数组中的;
3.满足任意节点的值都大于其子树中节点的值,叫做大堆(大根堆或最大堆)
最大堆的堆顶是整个堆中的最大元素
4.满足任意节点的值都小于其子树中节点的值,叫做小堆(小根堆或最小堆)
最小堆的堆顶是整个堆中的最小元素
5.堆的基本左用:快速找到集合中的最值
二、优先级队列(堆)的实现
1.向下调整(左右子树必须是一个堆才能调整)
以建小堆为例
//优先级队列-->堆 向下调整 (建小堆为例)
public static void shiftDown(int[] array,int size,int index){
int left = 2*index+1;
while (left <size){ //因为是完全二叉树 直接判断左子树即可
int min = left;
int right = 2*index +2;
if(right<size){
if(array[right]<array[left]){ //让左右子树的最小值放入min
min = right;
}
}
if(array[index]<=array[min]){ // 再让要比较的父节点 与最小的比较 大的话交换,
//小的话就意味着这就是一个小堆 不用交换 ,直接break
break;
}
int t = array[index];
array[index] = array[min];
array[min] = t;
index = min; //最后这个根节点调整完 要把index 指向被换的数 再看这个数是否要调整
left = 2*index+1;
}
}
或者下面 一样 只是换成了孩子节点
public void adjustDown(int parent,int len) {
int child = 2*parent+1;
while (child < len) {
if(child+1 < len && this.elem[child] < this.elem[child+1]) {
child++;
}
//child 下标是孩子节点最大值的下标
if(this.elem[child] > this.elem[parent]) {
int tmp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = tmp;
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}
2.向上调整(以建大堆为例)
//向上调整 (建大堆为例)
public static void shiftUp(int[] array,int size,int index) { //此时index 应该给 长度-1 一次向上调整
while (index > 0) {
int parent = (index - 1) / 2; //找到要调整的父亲节点
if (array[parent] >= array[index]) {
break;
}
int t = array[parent];
array[parent] = array[index];
array[index] = t;
index = parent;
}
}
或者下面的child 就相当于上面的index
public void adjustUp(int child) {
int parent = (child-1)/2;
while (child > 0) {
if(this.elem[child] > this.elem[parent]) {
int tmp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = tmp;
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
3.建堆(以建小堆为例)
//建堆
public static void createHeap(int[] array, int size){
for (int i = (size-1-1)/2; i>=0; i++) { //i从最后一个节点(index-1)的父亲节点((index-1)-1)/2开始
shiftUp(array,size,i);//向上调整 (建小堆)
}
}
4.入队、出队、返回队首元素的操作
class MyPriorityQueue{
private int[] array = new int[10];
private int size = 0;
public void offer(int a){ //入队操作 并调整
array[size++] = a; //尾插法加入
shiftDown(array,size,size-1);
}
public int poll(){//出队 先把第一个和最后一个交换 再调整,然后同时size--
int tmp = array[0];
array[0] = array[--size];
shiftDown(array,size,0);
return tmp;
}
public int peek(){
return array[0];
}
}
相关练习
1.找到和最小的 k 对数字
/**
* 给定两个以升序排列的整形数组 nums1 和 nums2, 以及一个整数 k。
* k为返回的对数
* 定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2。
*
* 找到和最小的 k 对数字 (u1,v1), (u2,v2) ... (uk,vk)。
*/
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
PriorityQueue<List<Integer>> queue = new PriorityQueue<>(k, (o1, o2)->{
return (o2.get(0) + o2.get(1)) - (o1.get(0) + o1.get(1));
});
for(int i = 0; i < Math.min(nums1.length, k); i++){
for(int j = 0; j < Math.min(nums2.length, k); j++){
if(queue.size() == k && nums1[i]+nums2[j] > queue.peek().get(0) + queue.peek().get(1)){
break;
}
if(queue.size() == k){
queue.poll();
}
List<Integer> pair = new ArrayList<>();
pair.add(nums1[i]);
pair.add(nums2[j]);
queue.add(pair);
}
}
List<List<Integer>> res = new LinkedList<>();
for(int i =0; i < k && !queue.isEmpty(); i++){
res.add(queue.poll());
}
return res;
}
2.石头的重量
/**
* 有一堆石头,每块石头的重量都是正整数。
*
* 每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。
* 假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
* 如果 x == y,那么两块石头都会被完全粉碎;
* 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
* 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
*/
public int lastStoneWeight(int[] stones) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(stones.length, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i:stones) {
priorityQueue.add(i);
}
while (priorityQueue.size() >=2){
int x = priorityQueue.poll();
int y = priorityQueue.poll();
if(x > y){
priorityQueue.offer(x-y);
}
}
return priorityQueue.size()==1? priorityQueue.poll():0;
}
}
总结
根据前面栈、队列的学习,优先级队列的学习并不是很困难,操作都很类似,只是储存的方式和删除的方式有些不同,而且加上二叉树的知识,就更容易理解,它是以二叉树形式储存,而且是安全二叉树,只要掌握这些,就不难理解这部分知识。