[size=medium] 优先队列,顾名思义,就是一种根据一定优先级存储和取出数据的队列。它可以说是队列和排序的完美结合体,不仅可以存储数据,还可以将这些数据按照我们设定的规则进行排序。
先说说优先队列的实现吧。有一点需要澄清,很多人一直以为Priority Queue就是一个Priority Heap,这种说法当然是片面的。既然优先队列只是存储数据和排序的结合,那么根据我们学过的知识,可以列出以下的实现方法:无序数组、无序链表、有序数组、有序链表以及二叉查找树,当然还有堆。这些方法在实现中当然也有优先级,下表就是一些方法的实现基本操作的时间性能对比:
[align=center][img]http://dl.iteye.com/upload/attachment/598737/7c0f4abc-e30b-3992-9e3f-2cacac3d7d9d.png[/img][/align]
[align=center]图1.一些优先队列的实现方案的时间性能对比[/align]
从这张表里我们可以按照时间复杂度这个优先级来进行一次排序,当然,你会选用最后一种二叉查找树作为实现优先队列的方案,但是实际上我们采用的是堆。堆,就是一种二叉树,堆和二叉查找树是类似的,但是也有些许不同:从形状来看,二叉查找树可以有不同的形状,而堆只能是完全二叉树;在时间性能上,二者并无明显的区别。堆也是有序的,我们一般将其分为大顶堆和小顶堆。小顶堆,顾名思义,就是这个堆的子结点的值小于父结点的值;相反的,大顶堆就是堆的子结点的值都大于父结点的值。
在实现的时候,我们常常使用基于数组的堆,即堆中的元素保存在一个数组中,而这个数组元素的保存也是按照一定规律的:如果父结点的位置为n,那么,其对应的左右子结点的位置分别是2n+1和2n+2。也就是说,我们在视觉上(如果用画图形象化表示堆的话)和本质上将堆打扮成两种东西,这样既便于理解,也利于实现,本质的实现是用数组是因为考虑到数组的一些性能。
堆有两个很基本的操作:增加、删除。先来说说增加元素,假设有下面这样一个堆:
[align=center][img]http://dl.iteye.com/upload/attachment/598739/d1506ae2-3a30-3ba9-b4c5-20aede716eef.png[/img][/align]
这时候,有一个元素1要添加进来,这时候应该怎么办呢?第一步,将元素添加到堆的最后一个位置:
[align=center][img]http://dl.iteye.com/upload/attachment/598741/9c0f15ce-a031-3166-9d09-b8cba5364c8c.png[/img][/align]
第二步,将新加入的元素与其父结点的值进行比较,若新结点的值比其父结点的值要小(就像这个例子一样),那么,交换两个结点的值,重复第二步,直到形成一个小顶堆:
[align=center][img]http://dl.iteye.com/upload/attachment/598743/3bb6f3af-d3e1-31ba-98f5-2403553c6d59.png[/img]
[img]http://dl.iteye.com/upload/attachment/598745/179c1f33-e9de-354a-a24e-3c1e27c218c2.png[/img][/align]
这样,一个新的小顶堆诞生了!
然后就是从堆中删除一个元素了,假设在这个新的小顶堆中,我们打算删除堆顶元素:
第一步,将这个1删掉,假设其结点上当前没有值:
[align=center]
[img]http://dl.iteye.com/upload/attachment/599250/357372f1-e2ae-31a3-8178-4681536d7608.jpg[/img]
[/align]
第二步,将最后一个结点的值放在根的位置,然后进行下滤,比较一下两个子结点的值与根节点的值,将堆顶元素与其子结点中较小的交换,然后重复:
[align=center]
[img]http://dl.iteye.com/upload/attachment/599252/bc03d746-8604-33b7-950b-3afd3862e2d8.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/599254/84564f94-5bea-3f5a-970b-91de8bd90fea.jpg[/img]
[/align]
知道了这些基本的原理,对数据量更大的增加和删除也应该是触类旁通了吧。
有人会质疑堆中除堆顶元素之外的其他元素的顺序问题:
[align=center][img]http://dl.iteye.com/upload/attachment/598755/26768d08-8b58-30e0-aa03-b9a973e037b5.png[/img][/align]
比如这里为什么4会放在5的右边兄弟结点上,这明显是受了二叉查找树的影响,因为堆对我们来说,一般只有堆顶元素有用,因此只要保证堆顶元素是最小的就行了(对小顶堆)。
下面,简单介绍一下sun提供的PriorityQueue的一些基本的方法,以此来较为深入地理解优先队列的实现机制:
1.下面是PriorityQueue的声明的第一句话:
[align=center][img]http://dl.iteye.com/upload/attachment/598757/b8b79b03-4e6e-39e5-8eca-8c60be106607.png[/img][/align]
这句话表明:sun提供的优先队列是基于优先堆实现的;
2.下面是声明一个Object数组时的注释:
[align=center][img]http://dl.iteye.com/upload/attachment/598759/bfbb33b9-9eb8-33ea-b524-3034137683b4.png[/img][/align]
由此可知,堆中的元素在数组中的保存方案;并且队列中的优先级最小的元素总是保存在queue[0]中,前提是该优先队列不为空。
下面是往PriorityQueue中上滤的方法:
[align=center][img]http://dl.iteye.com/upload/attachment/598761/2a1ca04a-6187-3d6e-b08e-cd6a1bcd421f.png[/img][/align]
这段代码中,k代表当前加入元素的位置,即最后一个位置+1,x表示要添加的元素。首先得到父结点的值,然后将x的值和父结点的值进行比较,如果子结点的值大于父结点值,不作更改,否则交换两者的值。这个方法主要用于添加元素的时候。与之相对应的有一个下滤方法siftDownComparable(),其功能是向下比较,用在删除元素的实现中;
接下来就讲添加元素的实现:
[align=center][img]http://dl.iteye.com/upload/attachment/598763/9ae8026b-69e6-3a5c-8240-5ff76ec58b7f.png[/img][/align]
这里可以看到用到了siftUp方法,其实,siftUp中分情况调用了 siftUpUsingComparator()、siftUpComparable()。这在刚才已经介绍了上滤的实现了。这里的添加元素就是上滤的过程。
当然,我们在使用时,一般使用的方法是这三种:add、poll以及peek,add用以添加元素,其内部是用offer方法实现的,peek用来得到堆顶元素,但是不删除,而poll在返回堆顶元素之后,将堆顶元素删除。
以上只是对优先队列的简要介绍,作为一个应用比较广泛的数据结构,优先队列还有许许多多奇妙的地方,它们都等着你去发现。
[/size]
先说说优先队列的实现吧。有一点需要澄清,很多人一直以为Priority Queue就是一个Priority Heap,这种说法当然是片面的。既然优先队列只是存储数据和排序的结合,那么根据我们学过的知识,可以列出以下的实现方法:无序数组、无序链表、有序数组、有序链表以及二叉查找树,当然还有堆。这些方法在实现中当然也有优先级,下表就是一些方法的实现基本操作的时间性能对比:
[align=center][img]http://dl.iteye.com/upload/attachment/598737/7c0f4abc-e30b-3992-9e3f-2cacac3d7d9d.png[/img][/align]
[align=center]图1.一些优先队列的实现方案的时间性能对比[/align]
从这张表里我们可以按照时间复杂度这个优先级来进行一次排序,当然,你会选用最后一种二叉查找树作为实现优先队列的方案,但是实际上我们采用的是堆。堆,就是一种二叉树,堆和二叉查找树是类似的,但是也有些许不同:从形状来看,二叉查找树可以有不同的形状,而堆只能是完全二叉树;在时间性能上,二者并无明显的区别。堆也是有序的,我们一般将其分为大顶堆和小顶堆。小顶堆,顾名思义,就是这个堆的子结点的值小于父结点的值;相反的,大顶堆就是堆的子结点的值都大于父结点的值。
在实现的时候,我们常常使用基于数组的堆,即堆中的元素保存在一个数组中,而这个数组元素的保存也是按照一定规律的:如果父结点的位置为n,那么,其对应的左右子结点的位置分别是2n+1和2n+2。也就是说,我们在视觉上(如果用画图形象化表示堆的话)和本质上将堆打扮成两种东西,这样既便于理解,也利于实现,本质的实现是用数组是因为考虑到数组的一些性能。
堆有两个很基本的操作:增加、删除。先来说说增加元素,假设有下面这样一个堆:
[align=center][img]http://dl.iteye.com/upload/attachment/598739/d1506ae2-3a30-3ba9-b4c5-20aede716eef.png[/img][/align]
这时候,有一个元素1要添加进来,这时候应该怎么办呢?第一步,将元素添加到堆的最后一个位置:
[align=center][img]http://dl.iteye.com/upload/attachment/598741/9c0f15ce-a031-3166-9d09-b8cba5364c8c.png[/img][/align]
第二步,将新加入的元素与其父结点的值进行比较,若新结点的值比其父结点的值要小(就像这个例子一样),那么,交换两个结点的值,重复第二步,直到形成一个小顶堆:
[align=center][img]http://dl.iteye.com/upload/attachment/598743/3bb6f3af-d3e1-31ba-98f5-2403553c6d59.png[/img]
[img]http://dl.iteye.com/upload/attachment/598745/179c1f33-e9de-354a-a24e-3c1e27c218c2.png[/img][/align]
这样,一个新的小顶堆诞生了!
然后就是从堆中删除一个元素了,假设在这个新的小顶堆中,我们打算删除堆顶元素:
第一步,将这个1删掉,假设其结点上当前没有值:
[align=center]
[img]http://dl.iteye.com/upload/attachment/599250/357372f1-e2ae-31a3-8178-4681536d7608.jpg[/img]
[/align]
第二步,将最后一个结点的值放在根的位置,然后进行下滤,比较一下两个子结点的值与根节点的值,将堆顶元素与其子结点中较小的交换,然后重复:
[align=center]
[img]http://dl.iteye.com/upload/attachment/599252/bc03d746-8604-33b7-950b-3afd3862e2d8.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/599254/84564f94-5bea-3f5a-970b-91de8bd90fea.jpg[/img]
[/align]
知道了这些基本的原理,对数据量更大的增加和删除也应该是触类旁通了吧。
有人会质疑堆中除堆顶元素之外的其他元素的顺序问题:
[align=center][img]http://dl.iteye.com/upload/attachment/598755/26768d08-8b58-30e0-aa03-b9a973e037b5.png[/img][/align]
比如这里为什么4会放在5的右边兄弟结点上,这明显是受了二叉查找树的影响,因为堆对我们来说,一般只有堆顶元素有用,因此只要保证堆顶元素是最小的就行了(对小顶堆)。
下面,简单介绍一下sun提供的PriorityQueue的一些基本的方法,以此来较为深入地理解优先队列的实现机制:
1.下面是PriorityQueue的声明的第一句话:
[align=center][img]http://dl.iteye.com/upload/attachment/598757/b8b79b03-4e6e-39e5-8eca-8c60be106607.png[/img][/align]
这句话表明:sun提供的优先队列是基于优先堆实现的;
2.下面是声明一个Object数组时的注释:
[align=center][img]http://dl.iteye.com/upload/attachment/598759/bfbb33b9-9eb8-33ea-b524-3034137683b4.png[/img][/align]
由此可知,堆中的元素在数组中的保存方案;并且队列中的优先级最小的元素总是保存在queue[0]中,前提是该优先队列不为空。
下面是往PriorityQueue中上滤的方法:
[align=center][img]http://dl.iteye.com/upload/attachment/598761/2a1ca04a-6187-3d6e-b08e-cd6a1bcd421f.png[/img][/align]
这段代码中,k代表当前加入元素的位置,即最后一个位置+1,x表示要添加的元素。首先得到父结点的值,然后将x的值和父结点的值进行比较,如果子结点的值大于父结点值,不作更改,否则交换两者的值。这个方法主要用于添加元素的时候。与之相对应的有一个下滤方法siftDownComparable(),其功能是向下比较,用在删除元素的实现中;
接下来就讲添加元素的实现:
[align=center][img]http://dl.iteye.com/upload/attachment/598763/9ae8026b-69e6-3a5c-8240-5ff76ec58b7f.png[/img][/align]
这里可以看到用到了siftUp方法,其实,siftUp中分情况调用了 siftUpUsingComparator()、siftUpComparable()。这在刚才已经介绍了上滤的实现了。这里的添加元素就是上滤的过程。
当然,我们在使用时,一般使用的方法是这三种:add、poll以及peek,add用以添加元素,其内部是用offer方法实现的,peek用来得到堆顶元素,但是不删除,而poll在返回堆顶元素之后,将堆顶元素删除。
以上只是对优先队列的简要介绍,作为一个应用比较广泛的数据结构,优先队列还有许许多多奇妙的地方,它们都等着你去发现。
[/size]