Java-数构优先级队列

1.优先级队列(堆)

1.1概念

队列是一种先进先出的结构,但是有些情况下,操作的数据带有优先级,一般出队列时,可能需要优先级高的元素先出队列。因此优先级队列可以实现两个基本操作,一个是返回最高优先级对象,另一个时添加新的对象。

2.优先级队列的模拟实现

JDK中PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上做了一些调整。

2.1堆的概念

如果有一个 关键码的集合 K = {k0 k1 k2 ... kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储在一 个一维数组中 ,并满足: Ki <= K2i+1 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0 , 1 , 2... ,则 称为 小堆 ( 或大堆) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:

  • 堆中某个节点的值总是不大于或者不小于父亲节点的值
  • 堆是一颗完全二叉树

2.2堆的创建

2.2.1堆的向下调整

可以利用二叉树的性质,确定双亲结点和左右孩子。

java 复制代码
import java.util.Arrays;

public class TestHeap {
    private int[] elem;
    private int usedSize;

    public TestHeap() {
        this.elem = new int[10];
    }
    public void initHeap(int[] array){
        for (int i = 0; i < array.length; i++) {
            elem[i]=array[i];
            usedSize++;//将元素放入堆中
        }
    }
    public void createHeap(){
        for (int parent=(usedSize-1-1)/2;parent>=0;parent--){//最后一棵子树根结点的位置
            shiftDown(parent,usedSize);
        }
    }

    private void shiftDown(int parent, int usedSize) {//向下调整
        int child=(2*parent)+1;//左孩子
        while(child<usedSize){
            if(child+1<usedSize&&elem[child]<elem[child+1]){
                    child++;//如果右边子树的值更大那么child+1拿右边子树进行交换
            }
            if(elem[child]>elem[parent]){
                    swap(child,parent);
                    parent=child;
                    child=2*parent+1;
            }else{
                break;
            }
        }
    }

    private void swap(int i, int j) {
        int tmp=elem[i];
        elem[i]=elem[j];
        elem[j]=tmp;

    }
    public  void offer(int val){
        if (isFull()){
            this.elem= Arrays.copyOf(elem,2*elem.length);
        }
        this.elem[usedSize]=val;
        shiftUp(usedSize);
        usedSize++;
    }

    private void shiftUp(int child) {
        int parent=(child-1)/2;//用孩子结点确定双亲结点
        while (child>0){
            if (elem[child]>elem[parent]){
                swap(child,parent);
                child=parent;
                parent=(child-1)/2;
            }else {
                break;
            }
        }
    }

    public boolean isFull() {
        return usedSize==elem.length;
    }
    public int poll(){
        int tmp=elem[0];
        swap(0,usedSize-1);//将最后一个元素和堆顶元素交换
        usedSize--;
        shiftDown(0,usedSize);//重新调整
        return tmp;
    }
    public void heapSort(){
        int end=usedSize-1;
        while(end>0){
            swap(0,end);
            shiftDown(0,end);
            end--;
        }
    }
}

3.常用接口

3.1PriorityQueue的特性

java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的。

注意:

  1. 使用优先级队列必须导入importjava.util.PriorityQueue包
  2. PriorityQueue中放置的元素必须能够比较大小,否则会抛出ClassCastException 异常
  3. 不能插入null对象,否则会抛出NullPointerException
  4. 没有容量限制,会自动扩容。
  5. 插入和删除元素的时间复杂度为****
  6. 默认情况下都是小堆

(时间复杂度解释)


优先级队列的扩容说明:

  • 如果容量小于64时,是按照oldCapacity的2倍方式扩容的
  • 如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的
  • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

3.2PriorityQueue常用接口

1.构造方法

创造一个空的优先级队列,默认容量是11.

创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛IllegalArgumentException异常

注意:默认情况下,PriorityQueue队列是小堆,如果要实现大堆,需要用户自行提供比较器。

java 复制代码
class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}

3.3TOP-k问题

面试题 17.14. 最小K个数 - 力扣(LeetCode)

java 复制代码
class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int ret[]=new int[k];
        if(arr==null||k<=0){
            return ret;
        }
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());
         for (int i = 0; i < k; i++) {
            priorityQueue.offer(arr[i]);
        }
        for(int i=k;i<arr.length;i++){
        int top=priorityQueue.peek();
        if(arr[i]<top){
            priorityQueue.poll();
            priorityQueue.offer(arr[i]);
        }
    }
        for(int i=0;i<k;i++){
            ret[i]=priorityQueue.poll();
        }
        return ret;
    }
    }
    
  • 最大堆的堆顶元素是当前堆中最大的数
  • 当新元素比堆顶小的时候,替换堆顶,这样最终堆中保留的就是最小的 k 个数

4.堆的应用

4.1堆排序

1.建堆

  • 升序:大根堆
  • 降序小根堆

2.利用堆删除的思想进行排序,向下调整

5.对象的比较

5.1覆写基类equals

java 复制代码
class Student {
    public String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

5.2基于Comparble接口类比较

java 复制代码
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 比较大
}
}

5.3基于比较器的比较

覆写 Comparator 中的 compare 方法

这种方法对类的侵入性比较弱。

java 复制代码
class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}