优先级队列PriorityQueue----堆

优先级队列----堆

1.概念

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

2.性质

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

3.结构与存储

堆是一棵完全二叉树 ,因此可以层序的规则采用顺序的方式来高效存储

注意: 对于非完全二叉树 , 则不适用使用顺序方式进行存储 , 因为为了能够还原二叉树 , 空间中必须要存储空节点 , 就会导致空间利用率比较低

4.堆的创建

4.1 堆向下调整

以大根堆为例 : 对于集合{ 27,15,19,18,28,34 }中的数据,如果将其创建成堆呢 ?

  1. 先设定根节点为当前节点(通过下标获取,标记为cur),比较左右子树的值,找出更大的值,用child来标记。
  2. 比较child和cur的值,如果child比cur大,则不满足大堆的规则,需要进行交换。
  3. 如果child比cur小,满足大堆的规则,不需要交换,调整结束。
  4. 处理完一个节点之后,从当前的child出发,循环之前的过程

时间复杂度 : 树的高度

建堆时间复杂度 : O(N)

4.2 堆向上调整

  1. 先设定倒数的第一个叶子节点为当前节点(通过下标获取,标记为cur),找出他的父亲节点,用parent来标记。
  2. 比较parent和cur的值,如果cur比parent大,则不满足大堆的规则,需要进行交换。
  3. 如果cur比parent小,满足大堆的规则,不需要交换,调整结束。
  4. 处理完一个节点之后,从当前的parent出发,循环之前的过程。

4.3 创建堆示例

/**
 * 大根堆
 */
public int[] elem;

public int usedSize;

public TestHeap() {
    this.elem = new int[10];
}
public void initElem(int[] array) {
    for (int i = 0; i < array.length; i++) {
        elem[i] = array[i];
        usedSize++;
    }
}

/**
 * 时间复杂度: O(n)
 */
public void createHeap() {
    for (int parent = (usedSize-1-1)/2;parent >= 0;parent--) {
        shiftDown(parent,usedSize);
    }
}

/**
 * 父亲节点 和 每棵树的结束下标
 */
private void shiftDown(int parent,int len) {
    int child = 2*parent + 1;
    //最起码要有左孩子
    while (child < len) {
        //一定是有右孩子的情况下
        if (child+1 < len && elem[child] < elem[child+1]) {
            child++;
        }
        //child下标 一定是左右孩子 最大值的下标
        if (elem[child] > elem[parent]) {
            int tmp = elem[child];
            elem[child] = elem[parent];
            elem[parent] = tmp;
            parent = child;
            child = 2*parent + 1;
        }else {
            break;
        }
    }
}

建堆的时间复杂度 : O(N)

5.堆的插入

1 . 先将元素放入到底层空间中 , 看是否需要扩容

2 . 将最后新插入的节点向上调整 , 直至满足堆的性质

public void shiftUp(int child) {
    int parent = (child-1)/2;
    while (child > 0) {
        if (elem[child] > elem[parent]) {
            int tmp = elem[child];
            elem[child] = elem[parent];
            elem[parent] = tmp;
            child = parent;
            parent = (child-1)/2;
        }else {
            break;
        }
    }
}
//向上调整建堆的时间复杂度: N*logN
public void offer(int val) {
    if (isFull()) {
        //扩容
        elem = Arrays.copyOf(elem,2*elem.length);
    }
    elem[usedSize++] = val;//11
    //向上调整
    shiftUp(usedSize-1);//10

}
public boolean isFull() {
    return usedSize == elem.length;
}

向上调整建堆的时间复杂度为 : N*logN

6.堆的删除

注意 : 堆的删除一定删除的是堆顶元素 .

1 . 将堆顶元素对堆中最后一个元素交换

2 . 将堆中有效数据个数减少一个

3 . 对堆顶元素进行向下调整

public void pop() {
    if (isEmpty()) {
        throw new RuntimeException("堆为空");
    }
    swap(elem,0,usedSize-1);
    usedSize--;
    shiftDown(0,usedSize);
}
private void swap(int[] array,int i,int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}
public boolean isEmpty() {
    return usedSize == 0;
}

7.常用接口

7.1 PriorityQueue的特性

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

使用 PriorityQueue的使用要注意 :

  1. 使用时必须导入 PriorityQueue 所在的包 ,

    import java.util.PriorityQueue;
    
  2. PriorityQueue中放置的元素要能够比较大小 , 不能插入无法比较的对象 , 否则会抛出

  3. 不能插入null对象 , 否则会抛出 NullPointerException

  4. 没有容量限制 , 可以插入任意多个元素 , 其内部可以自动扩容

  5. 插入和删除元素的时间复杂度为O(logN)

  6. PriorityQueue 底层使用了堆数据结构

  7. PriorityQueue 默认情况下是小堆—即每次获取到的元素都是最小的元素

7.2 PriorityQueue常用接口介绍

1.优先级队列的构造

构造器功能介绍
PriorityQueue()创建一个空的优先级队列 , 默认容量是11
PriorityQueue(int initialCapacity)创建一个初始容量为initialCapacity优先级队列,initialCapacity 不能小于1,否则会抛illegalArgumentException异常
PriorityQueue(Collection<? extends E>c)用一个集合来创建优先级队列

7.3 比较器

用户自己定义比较器 : 直接实现 Comparator 接口 , 然后重写接口中的compare 方法即可

class Student implements Comparable<Student> {
    public int age;
    public String name;

    public Student(String name,int age) {
        this.age = age;
        this.name = 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 age == student.age && Objects.equals(name, student.name);

    }

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

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }
}
class NameComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
public class TestCompare {

    public static void main(String[] args) {
        Student student1 = new Student("zhangsan",10);
        Student student2 = new Student("zhangsan",20);
        System.out.println(student1 == student2);
        System.out.println(student1.equals(student2));
        System.out.println(student1.compareTo(student2));
        System.out.println("======姓名比较器=====");
        NameComparator nameComparator = new NameComparator();
        int ret = nameComparator.compare(student1,student2);
        System.out.println(ret);

        System.out.println("=======年龄比较器=======");
        AgeComparator ageComparator = new AgeComparator();
        int ret2 = ageComparator.compare(student1,student2);
        System.out.println(ret2);

    }
}

7.4插入/删除/获取优先级最高元素

从源码当中分析

1 . boolean offer(E e)

插入元素e , 插入成功返回 true , 如果 e 对象为空 , 抛出 空指针异常 , 空间不够时候进行扩容

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}

2 . E peek()

获取优先级最高的元素 , 如果优先级队列为空 , 返回 null

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

3 . E poll()

移除优先级最高的元素并返回 , 如果优先级队列为空 , 返回 null

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0];
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);
    return result;
}

4 . int size()

获取有效元素个数

public int size() {
    return size;
}

5 . void clear()

清空

public void clear() {
    modCount++;
    for (int i = 0; i < size; i++)
        queue[i] = null;
    size = 0;
}

6 . boolean isEmpty()

检测优先级队列是否为空 , 空返回 true

7.5 PriorityQueue 的扩容方式

private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}
  • 如果容量小于 64 时 , 按照 oldCapacity 的 2 倍 进行扩容
  • 如果容量大于等于 64 , 按照 oldCapacity 的 1.5 倍 进行扩容
  • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

PriorityQueue 的应用 ----top-k 问题

top-k问题:最大或者最小的前k个数据。比如:世界前500强公司

1 . 用数据集合中前 K 个元素来建堆

​ ① 前 K 个最大的元素 , 则建小堆

​ ②前 K 个最小的元素 , 则建大堆

2 . 用剩余的 N-K 个元素依次与堆顶元素来比较 , 不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

一 . 设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可

public int[] smallestK(int[] arr, int k) {
    int[] ret = new int[k];
    if (arr == null || k == 0) {
        return ret;
    }
    //O(N*logN)
    Queue<Integer> minHeap = new PriorityQueue<>(arr.length);
    for (int x : arr) {
        minHeap.offer(x);
    }
    //K*logN
    for (int i = 0;i < k;i++) {
        ret[i] = minHeap.poll();
    }
    return ret;
}

二 . 找前K个最大的元素

1.将数组前K个元素建立小根堆 , 剩下的元素依次和堆顶元素进行比较
2.小根堆的堆顶元素就是小根堆的最小元素 , 剩下元素比堆顶元素小,则不入堆
3.剩下元素比堆顶元素大,则将堆顶元素放到堆尾删除, 将剩余元素放入堆顶,再 进行排序,使新堆变成小根堆
4.以此往复 ,直到将数组元素比较结束

public int[] maxK2(int[] arr, int k) {
    int[] ret = new int[k];
    if (arr == null || k == 0) {
        return ret;
    }
    Queue<Integer> minHeap2 = new PriorityQueue<>(k);
    //1.遍历数组的前k 个元素 放到堆中 O(K*logK)
    for (int i = 0; i < k; i++) {
        minHeap2.offer(arr[i]);
    }
    //2.遍历剩下的 k-1 个 , 每次和堆顶元素进行比较
    //  当堆顶元素小的时候 , 就出堆 O(N-K)*logK
    for (int i = k; i < arr.length; i++) {
        int val = minHeap2.peek();
        if (val < arr[i]) {
            minHeap2.poll();
            minHeap2.offer(arr[i]);
        }
    }
    for (int i = 0; i < k; i++) {
        ret[i] = minHeap2.poll();
 }
    return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值