数据结构队列的学习中,咱们知道队列是先进先出的。任务被提交到队列中,按照先进先出的原则java
对各个任务进行处理。不过在现实的状况下,任务一般有着优先级的概念,例如短任务、管理员的操做算法
应该优先执行。这是使用队列就没法描述了,这种特殊的应用咱们使用一种称之为优先队列(堆)的方式数组
来解决。数据结构
和队列同样优先队列也支持入队和出队操做,不过区别在于优先队列的出队操做出队的是优先级最高架构
的元素,这里以元素的大小来断定任务的优先级,越小,优先级越高。优先队列的模型以下图:ide
基于这种优先队列的模型,咱们能够多种方法对其进行实现,一种经常使用的方法就是使用链表以O(1)执学习
行 插入操做,,遍历最小元素并删除花费O(N)时间。基于优先队列的插入操做通常状况下多于删除操做这ui
一状况, 前者是一种较好的实现。this
另外可使用二叉查找树来实现,这样一来插入和删除操做都是O(logN),不过二叉树支持多种spa
操做用以实现优先队列未免有点杀猪用牛刀的感受。 下面将介绍二叉堆实现优先队列。
二叉堆就结构性质上来讲就是一个彻底填满的二叉树,知足结构性和堆序性。结构性没必要多说,
就是彻底二叉树应该知足的树结构。堆序性指的是:父节点的键值老是大于或等于(小于或等于)任何
一个子节点的键值,且每一个节点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
最大堆:当父节点的键值老是大于或等于任何一个子节点的键值。
最小堆:当父节点的键值老是小于或等于任何一个子节点的键值。
上面图片直接沿用的维基百科上的,笔者懒得在本身作个图了。
上面对二叉堆的结构性质略有说起,这里咱们进行下详细说明。看看二叉堆是如何
实现的。
仔细观察上述彻底二叉树的结构,咱们能够发现的是对于任意一个位置i,其
结点的左孩子在2i的位置,右孩子在2i+1的位置上,基于上述的性质咱们可使用
数组来实现二叉堆。
由此二叉堆可使用一个数组和一个表明当前堆的大小的整数组成。考虑到涉及到
元素大小的比较,该数组元素实现了Comparable接口。
public class BinaryHeap { /** * Construct the binary heap. */ public BinaryHeap( ) { this( DEFAULT_CAPACITY ); } /** * Construct the binary heap. * @param capacity the capacity of the binary heap. */ public BinaryHeap( int capacity ) { currentSize = 0; array = new Comparable[ capacity + 1 ]; } private static final int DEFAULT_CAPACITY = 100; private int currentSize; // Number of elements in heap private Comparable [ ] array; // The heap array }
以上代码显示的是一个优先队列的架构。至于其提供的具体操做,咱们先看看
二叉堆的堆序性。
简单的说保证优先队列的删除操做快速执行是堆序性质,基于这个特色咱们须要找出最小元素,那么最小元素应该在根结点上,也就是最小堆。这样咱们就能以常数时间执行findMin()操做了。
基于二叉堆的堆序性,咱们来看看二叉堆基本操做是如何实现的吧!
根据优先队列的模型,二叉堆实现的优先队列应该具有插入操做,不过根据其对序性具体的插入操做是如何进行的呢?看下面的演示:
二叉堆插入元素14的状况。
为将一个元素 X 插入到堆中,咱们在下一个可用位置建立一个空穴,不然该堆将不是彻底数。
若是 X 能够放在该空穴中而不破坏堆的序,那么插入完成。不然,咱们把空穴的父节点上的元素
移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续改过程直到 X 能被放入空穴中为止。
这种实现过程称之为上滤:新元素在堆中上滤直到找出正确的插入位置。
实现代码:
public void insert( Comparable x ) throws Overflow { //这里没有进行扩容处理 if( isFull( ) ) throw new Exception( ); // Percolate up int hole = ++currentSize; //上滤,首先找到插入位置,以后元素交换一次 for( ; hole > 1 && x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 ) array[ hole ] = array[ hole / 2 ]; array[ hole ] = x; }
能够知道的是当插入的元素小于堆中全部的元素的时候,必须上滤到根,插入时间为O(logN)
基于优先队列的模型,出队的应该是最小元,按照最小堆的堆序性,找出最小元十分容易,麻烦的地方在于删除以后破坏告终构型,这是须要进行一些额外的操做。当删除一个最小元时,要在根节点创建一个空穴。因为如今堆少了一个元素,所以堆中最后一个元素 X 必须移动到该堆的某个地方。若是 X 能够直接被放到空穴中,那么 deleteMin 完成。
不过这通常不太可能,所以咱们将空穴的两个儿子中比较小者移入空穴,这样就把空穴向下推了一层。重复该步骤直到 X 能够被放入空穴中。所以,咱们的作法是将 X 置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。
演示过程以下:
首先咱们删除根元素13,创建一个空穴,以后判断元素31是否能够放入这个空穴中,明显不能,会破坏堆序性.
以后咱们选择较小的左儿子放入空穴,同时空穴下滑一层,以后判断31是否置于空穴中
同上,26置于空穴中,空穴下滑一层,31能够置于空穴中,过程结束。这一种操做过程称之为下滤:空穴一步步下滑.
源码实现:
public Comparable deleteMin( ) { if( isEmpty( ) ) return null; Comparable minItem = findMin( ); array[ 1 ] = array[ currentSize-- ]; percolateDown( 1 ); return minItem; } private void percolateDown( int hole ) { int child; Comparable tmp = array[ hole ]; for( ; hole * 2 <= currentSize; hole = child ) { child = hole * 2; if( child != currentSize && array[ child + 1 ].compareTo( array[ child ] ) < 0 ) child++; if( array[ child ].compareTo( tmp ) < 0 ) array[ hole ] = array[ child ]; else break; } array[ hole ] = tmp; }
一样的这个操做在最坏的状况下为O(logN),平均而言也为O(logN).
上面对二叉堆实现的优先队列的两个基本操做作了一些讲解。咱们知道的是在将任务提交给
优先队列的时候有时候咱们须要根据实际状况修改任务的优先级。
desreaseKey(p,m)操做下降在位置p处的值,降值幅度为正m,不过这种方式极可能破坏堆序性,所以须要经过上滤操做进行调整。这种方式可以动态的提升某个任务的优先级,使其在可以优先开始。
与上个操做相反,下降任务的优先级。
删除堆中某个任务,不过必须先执行decreasekey(P,+ ∞),而后执行deleteMin操做
这种操做的任务并非正常终止的,而是被用户终止的。
上述讲了二叉堆的方式实现优先队列。那么一个二叉堆又是如何构造的呢?
简单的咱们能够认为它可使用N个相继的insert操做来完成。每一个insert最坏时间为O(logN)
则其构建时间为O(N)。
更为经常使用的算法是先保持其结构性,以后再经过检查每一个位置,下滤操做使其知足堆序性。
一开始知足结构性,可是并不知足堆序性,咱们在元素70的位置进行下滤操做。
代码实现状况以下:
public BinaryHeap( T[] items ){ currentSize = items.length; array = (T[]) new Comparable[ (currentSize + 2) * 11 / 10 ]; int i=1; for( T item : items ){ array[ i++ ] = item; } buildHeap(); } private void buildHeap(){ for( int i = currentSize/2; i>0; i-- ) percolateDown( i ); }
完整源码:
package com.kiritor; import java.util.Arrays; public class BinaryHeap { public BinaryHeap( ) { this( DEFAULT_CAPACITY ); } public BinaryHeap( Comparable[] items ){ currentSize = items.length; array = new Comparable[ (currentSize + 2) * 11 / 10 ]; int i=1; for( Comparable item : items ){ array[ i++ ] = item; } buildHeap(); } public BinaryHeap( int capacity ) { currentSize = 0; array = new Comparable[ capacity + 1 ]; } public void insert( Comparable x ) { // Percolate up int hole = ++currentSize; for( ; hole > 1 && x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 ) array[ hole ] = array[ hole / 2 ]; array[ hole ] = x; } public Comparable findMin( ) { if( isEmpty( ) ) return null; return array[ 1 ]; } public Comparable deleteMin( ) { if( isEmpty( ) ) return null; Comparable minItem = findMin( ); array[ 1 ] = array[ currentSize-- ]; percolateDown( 1 ); return minItem; } private void buildHeap( ) { for( int i = currentSize / 2; i > 0; i-- ) percolateDown( i ); } public boolean isEmpty( ) { return currentSize == 0; } public boolean isFull( ) { return currentSize == array.length - 1; } public void makeEmpty( ) { currentSize = 0; } private static final int DEFAULT_CAPACITY = 100; private int currentSize; // Number of elements in heap private Comparable [ ] array; // The heap array private void percolateDown( int hole ) { int child; Comparable tmp = array[ hole ]; for( ; hole * 2 <= currentSize; hole = child ) { child = hole * 2; if( child != currentSize && array[ child + 1 ].compareTo( array[ child ] ) < 0 ) child++; if( array[ child ].compareTo( tmp ) < 0 ) array[ hole ] = array[ child ]; else break; } array[ hole ] = tmp; } // Test program public static void main( String [ ] args ) { int numItems = 50; BinaryHeap h = new BinaryHeap( numItems ); int i = 37; try { for( i = 37; i != 0; i = ( i + 37 ) % numItems ) h.insert( new Integer( i ) ); System.out.println(Arrays.toString(h.array)); System.out.println(h.findMin()); h.deleteMin(); System.out.println(Arrays.toString(h.array)); } catch( Exception e ) { System.out.println( "Overflow (expected)! " + i ); } } }
简单执行结果:
[null, 1, 2, 7, 6, 3, 12, 10, 9, 8, 5, 4, 13, 24, 22, 11, 34, 21, 19, 16, 27, 17, 15, 14, 25, 38, 31, 49, 36, 23, 18, 47, 48, 35, 42, 45, 46, 32, 29, 43, 40, 30, 37, 41, 33, 28, 20, 39, 44, 26, null] 1 [null, 2, 3, 7, 6, 4, 12, 10, 9, 8, 5, 14, 13, 24, 22, 11, 34, 21, 19, 16, 27, 17, 15, 20, 25, 38, 31, 49, 36, 23, 18, 47, 48, 35, 42, 45, 46, 32, 29, 43, 40, 30, 37, 41, 33, 28, 26, 39, 44, 26, null]
By Kiritor
2013 /06 /16 父亲节