Java 优先级队列

队列(queue)是一种先入先出(FIFO)的数据结构。

栈中的插入和移除数据项的命名一般都是 Push、Pop,而队列至今没有标准化的命名。插入操作可以用 add、offer、enque 等命名,移除操作可以用 poll、remove、deque 等命令,查看队头操作可以用 peek、remove 等命令。

在 Java 中,常见的队列操作以及它们的区别如下所示:

插入offer向队列插入元素,在一个满的队列中加入一个新项会返回false
add向队列插入元素,在一个满的队列中加入一个新项会抛出异常
删除poll从队列中删除第一个元素,在队列为空时返回null
remove从队列中删除第一个元素,在队列为空时会抛出异常
查看队头元素peek在队列的头部查询元素,在队列为空时返回null
element在队列的头部查询元素。在队列为空时会抛出异常

注意:这只是 Queue 接口中的声明规范,并不是强制要求。例如在 PriorityQueue 与 ArrayDeque 中,add 实际调用了 offer 方法,无论使用哪一种方法都会抛出异常。


优先级队列

优先级队列像普通队列一样,拥有队头和队尾,但队列中的元素是按照自然排序或者指定的比较器排序的。这一特性在实际中有很多应用,比如对于重要客户或紧急的消息可以优先处理。自 JDK 1.5 起,Java 容器类库中提供了优先级队列的工具 — PriorityQueue。

PriorityQueue 是基于优先堆的一个无界队列,不允许插入不可比较的对象,否则会抛出 ClassCastException 异常(如果队列只有一个元素则不会报错,因为此时没有比较);也不允许插入 null,否则会抛出NullPointerException。

注:PriorityQueue 是非线程安全的,如果想要在多线程的环境下使用,可以使用 PriorityBlockingQueue。

接下来编写一个使用 PriorityQueue 的例子。

Customer 表示客户,level 等级越高代表客户优先级越高。

public class Customer implements Comparable<Customer>{
    private int level;
    private String name;
    private String info;

    Customer() {}
    Customer(int level) {
        this.level = level;
    }
    Customer(int level, String name, String info) {
        this.level = level;
        this.name = name;
        this.info = info;
    }
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public int compareTo(Customer o) {
        //实现最大的元素在队头
        return level > o.level ? -1 : (level == o.level ? 0 : 1);
    }
}

接下来编写测试类,通过 factory() 方法生成 Customer 数组,设定随机 level 值,如果有多个元素的值相同,则从中任意选择一个。

public class PriorityQueueDemo {
    private static Random random = new Random();
    public static Customer[] factory(int i) {
        Customer[] customers = new Customer[i];
        for(int t = 0; t  < i; t++) {
            customers[t] = new Customer(random.nextInt(10));
        }
        return customers;
    }
    public static void main(String[] args) {
        PriorityQueue priorityQueue = new PriorityQueue();
        Customer[] customers = factory(10);
        for(int i = 0; i < customers.length; i++)
            priorityQueue.add(customers[i]);
        Customer customer;
        for(int j = 0; j < customers.length; j++) {
            customer = (Customer) priorityQueue.remove();
            System.out.println(customer.getLevel());
        }
    }
}

输出结果:

9
9
9
8
8
6
6
1
1
0

PriorityQueue 原理

PriorityQueue 是基于最小堆原理实现。最小堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节的值。完全最小堆的示意图如下(摘自维基百科):

priority queue

完全二叉树采用数组存储。如果根节点从0开始,对于一个节点 i 来说,它的左节点为 2i + 1,右节点为 2i + 2。当插入或移除一个节点时,会调整堆结构使得插入后能满足最小堆的特征。

下面是怎样调整的一个例子(参考PriorityQueue源码):

public class MinHeap<T> {
    private Object[] queue;
    private int size;

    public MinHeap() {
        queue = new Object[11];
    }

    public boolean offer(T t) {
        int k = size;
        if (size == 0)
            queue[0] = t;
        size++;
        moveUp(k, t);
        return true;
    }
    //从堆底一层层上浮
    public void moveUp(int k, T t) {
        Comparable<? super T> key = (Comparable<? super T>) t;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((T) e) > 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
    //从堆顶一层层下沉
    public T poll() {
        if (size == 0)
            return null;
        int s = --size;
        T result = (T) queue[0];
        T end = (T) queue[s];
        queue[s] = null;
        if (s != 0)
            moveDown(0, end);
        return result;
    }

    public void moveDown(int k, T end) {
        Comparable<? super T> key = (Comparable<? super T>) end;
        int half = size >>> 1;
        while (k < half) {
            int left = (k << 1) + 1;
            int right = left + 1;
            Object c = queue[left];
            if (right < size && ((Comparable<? super T>) c).compareTo((T) queue[right]) > 0) {
                c = queue[left = right];
            }
            if (key.compareTo((T) c) <= 0)
                break;
            queue[k] = c;
            k = left;
        }
        queue[k] = key;
    }

    public static void main(String[] args) {
        MinHeap minHeap = new MinHeap();
        minHeap.offer(1);
        minHeap.offer(5);
        minHeap.offer(3);
        minHeap.offer(2);
        minHeap.offer(4);
        minHeap.offer(4);
        int length = minHeap.size;
        for (int i = 0; i < length; i++)
            System.out.println(minHeap.poll());
    }
}

输出结果:

1
2
3
4
4
5

在 PriorityQueue 中,插入、删除操作的时间复杂度为 O(log(n)),查看元素的操作时间复杂度为线性时间。可以参考下面的链接:

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值