`

PriorityQueue实现原理

阅读更多

PriorityQueue是个基于优先级堆的极大优先级队列
此队列按照在构造时所指定的顺序对元素排序,既可以根据元素的自然顺序来指定排序(参阅 Comparable),
也可以根据 Comparator 来指定
,这取决于使用哪种构造方法。优先级队列不允许 null 元素。
依靠自然排序的优先级队列还不允许插入不可比较的对象(这样做可能导致 ClassCastException)。
此队列的头是按指定排序方式的最小元素。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。
队列检索操作 poll、remove、peek 和 element 访问处于队列头的元素。
优先级队列是无界的,但是有一个内部容量,控制着用于存储队列元素的数组的大小。
它总是至少与队列的大小相同。随着不断向优先级队列添加元素,其容量会自动增加。无需指定容量增加策略的细节。
注意1:该队列是用数组实现,但是数组大小可以动态增加,容量无限
注意2:此实现不是同步的。不是线程安全的。如果多个线程中的任意线程从结构上修改了列表, 则这些线程不应同时访问 PriorityQueue 实例,这时请使用线程安全的PriorityBlockingQueue 类
注意3:不允许使用 null 元素
注意4:此实现为插入方法(offer、poll、remove() 和 add 方法)提供 O(log(n)) 时间;
 为 remove(Object) 和 contains(Object) 方法提供线性时间;
 为检索方法(peek、element 和 size)提供固定时间。
注意5:方法iterator()中提供的迭代器并不保证以有序的方式遍历优先级队列中的元素
至于原因可参考下面关于PriorityQueue的内部实现
如果需要按顺序遍历,请考虑使用 Arrays.sort(pq.toArray())。
注意6可以在构造函数中指定如何排序。如:
PriorityQueue()
          使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序来排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity)
          使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序来排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
          使用指定的初始容量创建一个 PriorityQueue,并根据指定的比较器comparator来排序其元素。
注意7:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法。
PriorityQueue的内部实现
PriorityQueue对元素采用的是堆排序,头是按指定排序方式的最小元素。堆排序只能保证根是最大(最小),整个堆并不是有序的。

方法iterator()中提供的迭代器可能只是对整个数组的依次遍历。也就只能保证数组的第一个元素是最小的
实例1的结果也正好与此相符。
实例1:
     public static void main(String[] args) {
        PriorityQueue<String> pq = new PriorityQueue<String>();
        pq.add("dog");
        pq.add("apple");
        pq.add("fox");
        pq.add("easy");
        pq.add("boy");
        
        while (!pq.isEmpty()) {
         System.out.print("left:");
            for (String s : pq) {
                System.out.print(s + " ");
            }
            System.out.println();
            System.out.println("poll(): " + pq.poll());
        }
    }
输出的结果如下: 
left:apple boy fox easy dog 
poll(): apple
left:boy dog fox easy 
poll(): boy
left:dog easy fox 
poll(): dog
left:easy fox 
poll(): easy
left:fox 
poll(): fox
可以看到,虽然PriorityQueue保持了队列顶部元素总是最小,但内部的其它元素的顺序却随着元素的减少始终处于变化之中
察看源代码来一探究竟。从Netbeans中非常方便的连接到PriorityQueue的add函数实现,
最终跟踪到函数private void siftUpComparable(int k, E x),定义如下:
     private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
相对于add的操作,该函数的入口参数k是指新加入元素的下标,而x 就是新加入的元素。
乍一看,这个函数的实现比较令人费解,尤其是parent的定义。通过进一步分析了解到,
PriorityQueue内部成员数组 queue其实是实现了一个二叉树的数据结构,这棵二叉树的根节点是queue[0],
左子节点是queue[1],右子节点是queue[2],而 queue[3]又是queue[1]的左子节点,依此类推,给定元素queue
该节点的父节点是queue[(i-1)/2]。因此 parent变量就是对应于下标为k的节点的父节点。
弄清楚了这个用数组表示的二叉树,就可以理解上面的代码中while 循环进行的工作是,
当欲加入的元素小于其父节点时,就将两个节点的位置交换。这个算法保证了如果只执行add操作,那么queue这个二叉树是有序的:
该二叉树中的任意一个节点都小于以该节点为根节点的子数中的任意其它节点。这也就保证了queue[0],即队顶元素总是所有元素中最小的。
需要注意的是,这种算法无法保证不同子树上的两个节点之间的大小关系。举例来说,queue[3]一定会小于queue[7],但是未必会小于queue[9],因为queue[9]不在以queue[3]为根节点的子树上。
弄清楚了add的操作,那么当队列中的元素有变化的时候,对应的数组queue又该如何变化呢?察看函数poll(),
最终追中到函数private E removeAt(int i),代码如下:
     private E removeAt(int i) {
        assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            queue = null;
        else {
            E moved = (E) queue[s];
            queue[s] = null;
            siftDown(i, moved);
            if (queue
 == moved) {
                siftUp(i, moved);
                if (queue
 != moved)
                    return moved;
            }
        }
        return null;
    }

这个函数的实现方法是,将队尾元素取出,插入到位置i,替代被删除的元素,然后做相应的调整,保证二叉树的有序,
即任意节点都是以它为根节点的子树中的最小节点。进一步的代码就留给有兴趣的读者自行分析,
要说明的是,对于 queue这样的二叉树结构有一个特性,即如果数组的长度为length,
那么所有下标大于length/2的节点都是叶子节点,其它的节点都有子节点。
总结:可以看到这种算法的实现,充分利用了树结构在搜索操作时的优势,效率又高于维护一个全部有序的队列

分享到:
评论

相关推荐

    Java的优先队列PriorityQueue原理及实例分析

    Java 的优先队列 PriorityQueue 原理及实例分析 Java 的优先队列 PriorityQueue 是 Queue 接口的实现,可以对其中元素进行排序,可以放基本数据类型的包装类(如:Integer,Long 等)或自定义的类。PriorityQueue ...

    C++ 优先队列实例(Priority_queue)

    首先,C++标准库提供了`&lt;queue&gt;`和`&lt;priority_queue&gt;`头文件,用于实现常规队列和优先队列。优先队列不像普通队列那样遵循先进先出(FIFO)原则,而是根据元素的优先级决定哪个元素先出队。优先级高的元素会优先处理...

    JDK源码之PriorityQueue解析

    二、优先队列的实现原理 优先队列的实现方式是使用二叉堆的结构,需要满足以下两条性质:(1)任何结点的值都小于或等于其子节点的值;(2)所有结点从上到下,从左到右填入,即一棵完全二叉树。基于这两条规律,...

    解析Java中PriorityQueue优先级队列结构的源码及用法

    PriorityQueue在JDK中内置,基于二叉堆数据结构实现,特别是最小堆,这意味着队列头部的元素总是具有最低的优先级。在Java 7中,PriorityQueue的底层实现是一个近似完全二叉树的数组,保证了队列的高效操作。 ...

    数据结构的原理及其java实现

    本篇文档将深入探讨数据结构的基本原理及其在Java中的实现方式。 一、数组 数组是最基础的数据结构,它是一系列相同类型元素的集合,通过索引进行访问。在Java中,数组可以通过声明数组变量并初始化来创建,例如: ...

    优先队列算法实现(Java)

    本实现是用Java编程语言完成的,Java提供了一个内置的PriorityQueue类,但这里可能是自定义实现,以便更好地理解其工作原理和优化。 1. **优先队列的基本概念** - 优先队列是一个队列,但它不同于普通的FIFO(先进...

    详解Java阻塞队列(BlockingQueue)的实现原理

    "详解Java阻塞队列(BlockingQueue)的实现原理" Java阻塞队列(BlockingQueue)是Java.util.concurrent包下重要的数据结构,提供了线程安全的队列访问方式。BlockingQueue的实现原理主要是基于四组不同的方法用于...

    java 数据结构 堆的原理.doc

    在Java中,`java.util.PriorityQueue`类是实现堆的主要工具,它是一个无界优先队列,内部使用了最小堆的原理。PriorityQueue不保证队列中元素的顺序,但插入和删除元素时会保持元素的排序。插入元素(add()方法)会...

    C#实现Dijkstra算法

    C#代码实现时,可以使用`System.Collections.Generic.PriorityQueue`类来创建优先队列,用`System.Linq`进行一些便捷操作。同时,为了存储节点的信息,可以定义一个自定义的Node类,包含节点ID、距离和是否已访问等...

    排序算法9种--java实现

    Java中,可以使用优先队列(PriorityQueue)类来实现堆排序。 7. 计数排序(Counting Sort):计数排序不是基于比较的排序算法,适用于非负整数排序。统计每个数出现的次数,然后根据统计结果进行排序。Java实现时...

    java实现的最短路径问题

    在计算机科学中,最短路径问题是一个常见的图论问题,其目标是找到在给定的图中,两个节点之间的最短...通过理解算法的工作原理和Java实现细节,我们可以灵活地应用于各种图结构问题,为软件开发和数据分析带来便利。

    哈夫曼树的C++实现

    - **优先队列(PriorityQueue)**:通常使用最小堆实现,用于存储哈夫曼树的节点,每次弹出频率最小的节点进行合并。 3. **编码生成**:自底向上遍历哈夫曼树,左子节点代表“0”,右子节点代表“1”。从根节点到...

    最短路径matlab代码实现

    2. MATLAB实现:创建邻接矩阵表示图,用优先队列(如优先级队列数据结构`priorityQueue`)存储节点及其距离。每次从队列中取出距离最小的节点,更新其相邻节点的距离。 三、Floyd-Warshall算法 1. 算法原理:Floyd-...

    java各种经典算法大全 还包括C语言的实现

    Java和C都提供了内置的排序方法,如Java的`Arrays.sort()`和C的`qsort()`,但理解算法原理并能手动实现是提升编程技能的关键。 2. **查找算法**:二分查找、线性查找、哈希查找等。二分查找适用于有序数组,而哈希...

    java 实现二叉排序树 堆

    在Java中,我们可以使用`PriorityQueue`类来实现堆。堆分为大顶堆(父节点的值大于或等于其子节点的值)和小顶堆(父节点的值小于或等于其子节点的值)。`PriorityQueue`默认是小顶堆,但可以通过比较器自定义为大...

    数据结构函数

    数据结构函数 ...本文详细介绍了队列Merge函数、SwapChildren函数、Merge1函数、PriorityQueueInsert1函数、FindMin函数、IsEmpty函数、printright函数、calheigh函数和Max函数的实现代码和原理。

    【死磕Java集合】-集合源码分析.pdf

    本文将对Java集合框架的源码进行分析,深入探讨其实现原理和机制。 一、LinkedList源码分析 LinkedList是一种以双向链表实现的List,它除了作为List使用,还可以作为队列或者栈来使用。它实现了List、Queue和Deque...

    java各类排序算法的实现

    Java实现中,可以使用Java的`PriorityQueue`类辅助实现,时间复杂度为O(n log n)。 7. **计数排序**:这是一种非基于比较的排序算法,适用于待排序元素范围不大的情况。通过统计每个元素出现的次数,然后计算出每个...

    一口气说出Java 6种延时队列的实现方法(面试官也得服)

    DelayQueue 是一个 BlockingQueue,无界阻塞队列,内部使用的是 PriorityQueue,PriorityQueue 使用完全二叉堆来实现队列元素排序。在向 DelayQueue 队列中添加元素时,会给元素一个 Delay(延迟时间)作为排序条件...

    java 实现huaffman压缩和解压缩

    首先,让我们深入理解哈夫曼编码的基本原理: 1. **哈夫曼树构建**:给定一组字符及其频率,构建哈夫曼树的过程是一个优先队列(通常使用最小堆实现)的操作。每次从队列中取出两个频率最小的节点,合并成一个新的...

Global site tag (gtag.js) - Google Analytics