优先级队列——堆


概念

1 优先级队列

优先级队列(PriorityQueue)是具有优先级的队列, 优先级高的元素先出队, Java中的PriorityQueue底层用了堆这样的数据结构.

2 堆

2.1 堆的概念
堆实际上是一个特殊的完全二叉树, 这颗树的某个结点总是大于或小于父节点.
堆分为大根堆和小根堆, 根结点最小的堆称为小根堆, 反之称为大根堆.
2.2 堆的存储方式
堆是一个完全二叉树, 中间不会有空结点, 所以可以按层序遍历的顺序存储在一维数组中.
在这里插入图片描述
下标从0开始, i 表示下标, i 的父亲节点下标为 (i-1)/2
i 的左孩子下标为 2 * i + 1
i 的右孩子下标为 2 * i + 2

模拟实现堆

1 堆的创建

提供一个数组 [22, 29, 15, 42, 35, 6 2, 34, 56, 19, 31], 如何将它调整为堆呢?

以大根堆为例: 在这里插入图片描述
向下调整建堆
从最后一个非叶子节点开始向前遍历.
每个非叶子节点与孩子节点比较, 若孩子节点较大, 则与孩子节点交换.
交换后需要继续向下调整, 直到比孩子节点大或没有孩子节点.

代码:

// 向下调整建堆的时间复杂度为O(n)
public void createHeap(int[] array) {
    elem = Arrays.copyOf(array,array.length);
    usedSize = array.length;
    // 从最后一个非叶子节点 开始 向下调整
    for (int i = (usedSize-1-1) / 2; i >= 0; i--) {
        shiftDown(i,usedSize);
    }
}

/**
* @param parent 是每棵子树的根节点的下标
* @param len  是每棵子树调整结束的结束条件
*             向下调整的时间复杂度:O(log(2)n)
*/
private void shiftDown(int parent, int len) {
    // 孩子结点下标
    int child = parent * 2 + 1;

    while (child < len) {
        // 比较两个孩子结点大小
        if (child+1 < len && elem[child] < elem[child + 1]) {
            child++;
        }
        // 孩子结点大于根结点,交换
        if (elem[child] > elem[parent]) {
            swap(elem, child, parent);
            // 因为发生了变化,所以继续向下调整
            parent = child;
            child = parent * 2 + 1;
        } else {
            break;
        }
    }
}

2 堆的插入

在堆的末尾插入新的节点, 然后将该节点向上调整
在这里插入图片描述
代码:

// 插入
public void push(int val) {
    if (isFull()) {
        // 扩容
        elem = Arrays.copyOf(elem, usedSize + usedSize / 2);
    }
    // 放入数组
    elem[usedSize++] = val;
    // 向上调整
    shiftUp(usedSize - 1);
}

// 向上调整的时间复杂度是O(log(2)n)
private void shiftUp(int child) {
    // 父亲结点下标
    int parent = (child - 1) / 2;

    while (parent >= 0) {
        // 孩子结点 大于 父亲结点 交换
        if (elem[child] > elem[parent]) {
            swap(elem, child, parent);
            // 继续向上调整
            child = parent;
            parent = (child - 1) / 2;
        } else {
            break;
        }
    }
}

向上调整也可以建堆, 但是比向下调整效率低,
因为对于一个完全二叉树, 下层的节点数比较多,
下层的节点需要向上调整的次数多, 而向下调整的次数少

// 向上调整建堆
public void createHeap2(int[] array) {
    elem = Arrays.copyOf(array,elem.length);
    usedSize = array.length;
    // 从第二个叶子结点 开始 向上调整
    for (int i = 1; i < usedSize; i++) {
        shiftUp(i);
    }
}

3 堆的删除

删除堆顶元素后仍要保持是大根堆

public void pollHeap() {
    if(isEmpty()) {
        return;
    }
    // 将最后一个元素 与 堆顶元素交换
    // 并将usedSize - 1
    elem[0] = elem[--usedSize];

    // 此时除了 堆顶元素,其余的都符合大根堆
    // 向下调整
    shiftDown(0, usedSize);
}

4 完整代码

import java.util.Arrays;

// 大根堆
public class TestHeap {
    public int[] elem;
    public int usedSize;

    public TestHeap() {
        this.elem = new int[10];
    }

    /**
     * 向下调整建堆的时间复杂度:o(n)
     *
     * @param array
     */
    public void createHeap(int[] array) {
        elem = Arrays.copyOf(array,array.length);
        usedSize = array.length;
        // 从最后一个非叶子结点 开始 向下调整
        for (int i = (usedSize-1-1) / 2; i >= 0; i--) {
            shiftDown(i,usedSize);
        }
    }

    /**
     * 向上调整建堆
     *
     * @param array
     */
    public void createHeap2(int[] array) {
        elem = Arrays.copyOf(array,elem.length);
        usedSize = array.length;
        // 从第二个叶子结点 开始 向上调整
        for (int i = 1; i < usedSize; i++) {
            shiftUp(i);
        }
    }

    /**
     * @param parent 是每棵子树的根节点的下标
     * @param len  是每棵子树调整结束的结束条件
     *             向下调整的时间复杂度:O(log(2)n)
     */
    private void shiftDown(int parent, int len) {
        // 孩子结点下标
        int child = parent * 2 + 1;

        while (child < len) {
            // 比较两个孩子结点大小
            if (child+1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            // 孩子结点大于根结点,交换
            if (elem[child] > elem[parent]) {
                swap(elem, child, parent);
                // 因为发生了变化,所以继续向下调整
                parent = child;
                child = parent * 2 + 1;
            } else {
                break;
            }
        }
    }

    /**
     * 入队:仍然要保持是大根堆
     * @param val
     */
    public void push(int val) {

        if (isFull()) {
            // 扩容
            elem = Arrays.copyOf(elem, usedSize + usedSize / 2);
        }
        // 放入数组
        elem[usedSize++] = val;

        // 向上调整
        shiftUp(usedSize - 1);
    }

    // 向上调整的时间复杂度:O(log(2)n)
    private void shiftUp(int child) {
        // 父亲结点下标
        int parent = (child - 1) / 2;

        while (parent >= 0) {
            // 孩子结点 大于 父亲结点 交换
            if (elem[child] > elem[parent]) {
                swap(elem, child, parent);
                // 继续向上调整
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }

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

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

    /**
     * 出队【删除】:每次删除的都是优先级高的元素
     * 堆顶元素
     * 仍然要保持是大根堆
     */
    public void pollHeap() {
        if(isEmpty()) {
            return;
        }
        // 将最后一个元素 与 堆顶元素交换
        // 并将usedSize - 1
        elem[0] = elem[--usedSize];

        // 此时除了 堆顶元素,其余的都符合大根堆
        // 向下调整
        shiftDown(0, usedSize);
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }

    /**
     * 获取堆顶元素
     * @return
     */
    public int peekHeap() {
        if (isEmpty()) {
            return -1;
        }
        return elem[0];
    }
}

PriorityQueue的使用

1 注意事项

关于Java中PriorityQueue的使用要注意:

  1. 使用时必须导入PriorityQueue所在的包,即:import java.util.PriorityQueue;
  2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
  3. 不能插入null对象,否则会抛出NullPointerException
public static void main(String[] args) {
    PriorityQueue<Student1> priorityQueue = new PriorityQueue<>();
    priorityQueue.offer(new Student1("张三",13));
    priorityQueue.offer(new Student1("李四",12));
    // PriorityQueue中放置不能比较大小的数据 插入第二个元素时,会抛出ClassCastException异常
    // priorityQueue.offer(null);
    // 插入null对象,会抛出NullPointerException
}
  1. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  2. 插入和删除元素的时间复杂度为O(log(2)n)
  3. PriorityQueue底层使用了堆数据结构
  4. PriorityQueue默认情况下是小根堆—即每次获取到的元素都是最小的元素

2 自定义类型如何插入PriorityQueue

代码:

// 自定义类
class Student implements Comparable<Student>{
    public int age;
    public String name;
    public Student(String name, int age) {
        this.age = age;
        this.name = name;
    }
    
    // 重写了Comparable接口的 compareTo方法
    @Override
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);
    }
}

// 两个比较器
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 static void main(String[] args) {
    PriorityQueue<Student> priorityQueue3 = new PriorityQueue<>(new NameComparator());
    priorityQueue3.offer(new Student("张三",19));
    priorityQueue3.offer(new Student("李四",13));

    // 手动传了一个比较器
    // 如果传了比较器  就优先使用手动传入的方法进行比较
    PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new AgeComparator());
    priorityQueue2.offer(new Student("张三",19));
    priorityQueue2.offer(new Student("李四",13));

    // 没传比较器,默认用类的实现的接口Comparable 中的compareTo 方法
    // 如果没有实现这个接口,也没传比较器,会抛出ClassCastException异常
    PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>();
    priorityQueue1.offer(new Student("张三",19));
    priorityQueue1.offer(new Student("李四",13));
}

3 如何建立大根堆

因为PriorityQueue默认建的是小根堆, 那我们怎么让它建一个大根堆呢?
代码:

// 比较器
class testComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        // Integer 类中默认的比较是 o1.compareTo(o2)
        return o2.compareTo(o1);
    }
}

public static void main(String[] args) {
    int[] array = { 22,29,15,42,35,62,34,56,19,31};
    PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>();
    for (int i = 0; i < array.length; i++) {
        priorityQueue1.offer(array[i]);
    }
    System.out.println(priorityQueue1);

    // 默认是小根堆
    // 如何创建大根堆呢
    // 手动传入一个 反过来的 比较器
    PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(new testComparator());
    for (int i = 0; i < array.length; i++) {
        priorityQueue2.offer(array[i]);
    }
    System.out.println(priorityQueue2);
}

运行结果:
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值