优先级队列(PriorityQueue),比较普通的队列可以提供了一个基本操作,返回最高优先级元素。PriorityQueue的底层使用了堆这种数据结构,而堆则是在完全二叉树的基础上进行调整。
堆的特点:
堆的父节点一定是大于(大堆)或小于(小堆)子节点。
堆的逻辑结构是二叉数,但存储结构是一维数组。
堆一定是完全二叉树。
普通二叉树不使用一维数组存储的原因是二叉树的节点可能为空,要还原二叉树就要将空节点null也存入数组,使空间利用率较低。
堆的创建
堆的创建采用的是向下调整,以小根堆为例,向下调整是先在头节点不满足小根堆,而左右子树都已经满足小根堆,设左子树为child,父节点为parent,通过先比较child和child+1,找出最大的数,与parent比较,如果小于则将两者交换,再让parent = child,child = chlid+1,直到child>size或child和parent已经满足小根堆了,结束循环。
如果知道子节点child,求父节点parent = (child-1)/2。
知道父节点parent,左chlid = 2*parent+1,右child=2*parent+2。
向下调整:
public void shiftdown1 (int index,int[] arr){
int parent = index;
int child = 2*parent+1;
while(child<arr.length){
while(child<arr.length&&arr[child]<arr[child+1]){
child+=1;
}
if(arr[parent]>arr[child]){
sweap(arr[parent],arr[child]);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
public void sweap(int a,int b){
int tem = a;
a = b;
b = tem;
}
如果给一组不规则的数组,建堆则要从最后一个非叶子节点开始,依次向下调整。
public void creatheap(int [] arr){
for (int i = (arr.length-1-1)/2; i >=0 ; i--) {
shiftdown1(i,arr);
}
}
public void shiftdown1 (int index,int[] arr){
int parent = index;
int child = 2*parent+1;
while(child<arr.length){
while(child<arr.length&&arr[child]<arr[child+1]){
child+=1;
}
if(arr[parent]>arr[child]){
sweap(arr[parent],arr[child]);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
public void sweap(int a,int b){
int tem = a;
a = b;
b = tem;
}
建堆的时间复杂度是O(N)
堆的删除和添加
堆的删除是先将堆顶元素和堆的最后一个叶子节点交换,然后对堆顶元素进行一次向下调整,将元素个数减一。
public void pollHeap(int arr[]) {
int tem = arr[0];
arr[0] = arr[usedSize-1];
usedSize--;
shiftDown(0,arr);
}
private void shiftDown(int index,int []arr) {
int parent = index;
int child = 2*parent +1;
while(child<usedSize){
if(child+1<usedSize&&arr[child]>=arr[child+1]){
child++;
}
if(arr[parent]>arr[child]){
sweap(arr[parent],arr[child]);
}
parent = child;
child = 2*parent+1;
}
}
public void sweap(int a,int b){
int tem = a;
a = b;
b = tem;
}
堆添加元素,要使用向上调整,新添的元素直接放到堆尾,找到它的父节点,比较大小,如果大于父就break;小于就交换值,并且让child = parent+1,parent = (child-1)/2。
public void push(int val,int [] arr) {
arr[usedSize]= val;
usedSize++;
shiftUp(usedSize-1,arr);
}
//在添加新元素的时候,向上调整
private void shiftUp(int index,int[]arr) {
int child = index;
int parent = (child-1)/2;
while(child>0){
if(arr[parent]>arr[child]){
sweap(arr[parent],arr[child]);
child= parent ;
parent = (child-1)/2;
}else{
break;
}
}
}
堆模拟实现优先级队列。
public class priorityqueue {
int[] arr = new int [100];
int size = 0;
public void offer(int k){
arr[size] = k;
size++;
shiftUp(size-1);
}
public int poll(){
int s = arr[0];
sweap(arr[0],arr[size]);
size--;
shiftDown(0);
return s;
}
}
PritorityQueue类特点
PritorityQueue(优先级队列)是JAVA集合框架中提供的一种优先级队列,PritorityQueuez中放置的元素必须要能够比较大小,不然会抛出ClassCastException异常。
不能放入null对象,否则会抛出NullPointerException异常。
没有容量限制,可以插入任意多个元素,内部可以自动扩容。
PritorityQueue默认是小堆(每次获取元素为最小),要转大堆则需要比较器。
import java.util.Comparator;
import java.util.PriorityQueue;
//设置比较器Comparater
class Incom implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
//比较器Comparable
class Incomw implements Comparable<Integer>{
@Override
public int compareTo(Integer o) {
return 0;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Incom());
}
top-k问题
第一种做法:直接将所有元素入PritorityQueue,返回前K个元素。
public class Heap {
public int[] smallestK1(int[] arr, int k) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Incom());
for (int i = 0; i < arr.length; i++) {
priorityQueue.add(arr[i]);
}
int [] n = new int[k];
for (int i = 0; i < k; i++) {
n[i] = priorityQueue.poll();
}
return n;
}
第二种做法:先将前K个元素建堆(大堆),取堆顶元素,与第k个元素开始依次比较,最后返回前K个元素。
public int[] smallestK(int[] arr, int k){
int[] ret =new int[k];
if(k<0||k>=arr.length){
return ret;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
for (int i = 0; i < k; i++) {
priorityQueue.offer(arr[i]);
}
for (int i = k; i < arr.length; i++) {
int cur = priorityQueue.poll();
if(cur>arr[k]){
priorityQueue.poll();
priorityQueue.add(arr[i]);
}
}
return ret;
}
这里的建堆是入PritorityQueue。
比较器(Comparator,Comparable)
JAVA中涉及到两个对象比较,要用Comparator和Comparable接口。
类要实现comparator需要重写里面的compare方法。
class Incom implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
import java.util.Comparator;
class Card {
public int rank; // 数值
public String suit; // 花⾊
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
class CardComparator implements Comparator<Card> {
// 根据数值⽐较,不管花⾊
// 这⾥我们认为 null 是最⼩的
@Override
public int compare(Card o1, Card o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
return o1.rank - o2.rank;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
// 定义⽐较器对象
CardComparator cmptor = new CardComparator();
// 使⽤⽐较器对象进⾏⽐较
System.out.println(cmptor.compare(p, o)); // == 0,表⽰牌
相等
System.out.println(cmptor.compare(p, q)); // < 0,表⽰ p ⽐较⼩
System.out.println(cmptor.compare(q, p)); // > 0,表⽰ q ⽐较⼤
}
}
要实现comparable要实现compareTo方法。
class Incomw implements Comparable<Integer>{
@Override
public int compareTo(Integer o) {
return 0;
}
}
public class Card implements Comparable<Card> {
public int rank; // 数值
public String suit; // 花⾊
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
// 根据数值⽐较,不管花⾊
// 这⾥我们认为 null 是最⼩的
@Override
public int compareTo(Card o) {
if (o == null) {
return 1;
}
return rank - o.rank;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
System.out.println(p.compareTo(o)); // == 0,表⽰牌相等
System.out.println(p.compareTo(q)); // < 0,表⽰ p ⽐较⼩
System.out.println(q.compareTo(p)); // > 0,表⽰ q ⽐较⼤
}
}