二叉堆(转载)

来源:互联网 发布:西安 长沙 知乎 编辑:程序博客网 时间:2024/05/24 16:15

满足如下结构性和堆序性,即为二叉堆。

结构性质:堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树。


容易证明,一棵高为 h 的完全二叉树有 2h 到 2h+1-1 个节点。这意味着完全二叉树的高是⌊ log N ⌋,显然它是 O( log N )。

一个重要的观察发现,因为完全二叉树这么有规律,所以它可以用一个数组表示而不需要使用链。


对于数组中任一位置 i 上的元素,其左儿子在位置 2i 上,右儿子在左儿子后的单元(2i + 1)中,它的父亲则在位置 ⌊ i/2 ⌋上。

堆序性质:任意节点都小于它的所有后裔。



基本的堆操作

insert( 插入 )

为将一个元素 X 插入到堆中,我们在下一个可用位置创建一个空穴,否则该堆将不是完全数。如果 X 可以放在该空穴中而不破坏堆的序,那么插入完成。否则,我们把空穴的父节点上的元素移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续改过程直到 X 能被放入空穴中为止。



这样一般的策略叫做上滤( percolate up );新元素在堆中上滤直到找出正确的位置。

[java] view plaincopy
  1. public void insert( T x ){  
  2.     if( currentSize == array.length - 1 ){  
  3.         enlargeArray( array.length*2 + 1 );  
  4.     }  
  5.       
  6.     //上滤  
  7.     int hole = ++currentSize;  
  8.     for( ; hole>1 && x.compareTo( array[ hole/2 ])<0; hole/=2 ){  
  9.         array[ hole ] = array[ hole/2 ];  
  10.     }  
  11.     array[ hole ] = x;  
  12. }  

如果欲插入的元素是新的最小元从而一直上滤到根处,那么这种插入的时间将长达 O( log N );

平均看来,上滤终止得要早,业已证明,执行一次插入平均需要 2.607 次比较,因此平均 insert 操作上移元素 1.607层。


deleteMin(删除最小元)

当删除一个最小元时,要在根节点建立一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素 X 必须移动到该堆的某个地方。如果 X 可以直接被放到空穴中,那么 deleteMin 完成。不过这一般不太可能,因此我们将空穴的两个儿子中比较小者移入空穴,这样就把空穴向下推了一层。重复该步骤直到 X 可以被放入空穴中。因此,我们的做法是将 X 置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。



这种一般的策略叫做下滤(percolate down)。

[java] view plaincopy
  1. public T deleteMin(){  
  2.     if( isEmpty() )  
  3.         throw new UnderflowException();  
  4.       
  5.     T minItem = array[1];  
  6.     array[1] = array[ currentSize-- ];  
  7.     percolateDown( 1 );  
  8.       
  9.     return minItem;  
  10. }  
  11.   
  12. private void percolateDown( int hole ){  
  13.     int child;  
  14.     T tmp = array[ hole ];  
  15.       
  16.     for( ; hole*2 <= currentSize; hole=child ){  
  17.         child = hole*2;  
  18.           
  19.         if( child != currentSize && array[child+1].compareTo( array[child] ) <0 ){  
  20.             child++;  
  21.         }  
  22.           
  23.         if( array[child].compareTo( tmp ) < 0 ){  
  24.             array[hole] = array[child];  
  25.         }  
  26.         else{  
  27.             break;  
  28.         }  
  29.     }  
  30.     array[hole] = tmp;  
  31. }  

这种操作最坏情形运行时间为 O( log N )。平均而言,被放到根处得元素几乎下滤到堆的底层(即它所来自的那层),因此平均运行时间为 O( log N )。


decreaseKey(降低关键字的值)

decreaseKey( p, △ ) 操作降低在位置 p 处得项的值,降值幅度为正量△。由于这可能破坏堆序性质,因此必须通过上滤对堆进行调整。

该操作对系统管理员是有用的:系统管理员能够使他们的程序以最高的优先级来运行。


increaseKey(增加关键字的值)

increaseKey( p, △ )操作增加在位置 p 处得项的值,增值的幅度为正的量△。这可以用下滤来完成。

许多调度程序自动地降低正在过多地消耗 CPU 时间的进程的优先级。


delete(删除)

delete( p ) 操作删除堆中位置 p 上的节点。该操作通过首先执行 decreaseKey( p, ∞ )然后再执行 deleteMin( ) 来完成。

当一个进程被用户中止(而不是正常终止)时,它必须从优先队列中除去。


上述三种操作均以对数最坏情形时间运行。


buildHeap(构建堆)

有时二叉堆是 由一些项的初始集合构造而得。这种构造方法以 N 项作为输入,并把它们放到一个堆中。显然,这可以使用 N 个相继的 insert 操作来完成。由于每个 insert 将花费O( 1 )平均时间,以及 O(log N) 的最坏时间,因此该算法的总的运行时间是 O(N) 平均时间而不是 O(N log N) 最坏时间。

一般的算法是将 N 项以任意顺序放入树中,保持结构特性。此时,如果 percolateDown( i ) 从节点 i 下滤,那么 buildHeap 程序则可以由构造方法用于创建一棵堆序的树。


图6-15中的第一棵树是无序树。从图6-15到图6-18中其余7棵树表示出 7 个 percolateDown 中每一个的执行结果。每条虚线对应两次比较:一次是找出较小的儿子节点,另一个是较小的儿子与该节点的比较。注意,在整个算法中只有 10 跳虚线,它们对应 20 次比较。

[java] view plaincopy
  1. public BinaryHeap( T[] items ){  
  2.     currentSize = items.length;  
  3.     array = (T[]) new Comparable[ (currentSize + 2) * 11 / 10 ];  
  4.       
  5.     int i=1;  
  6.     for( T item : items ){  
  7.         array[ i++ ] = item;  
  8.     }  
  9.     buildHeap();  
  10. }  
  11.   
  12. private void buildHeap(){  
  13.     forint i = currentSize/2; i>0; i-- )  
  14.         percolateDown( i );  
  15. }  
为了确定 buildHeap 的运行时间的界,我们必须确定虚线的条数的界。这可以通过计算堆中所有节点的高度的和来得到,它是虚线最大条数。现在我们想说明的是:该和为 O(N)。

定理:包含 2h+1-1 个节点、高为 h 的理想二叉树的节点的高度的和为 2h+1-1 - ( h+1 )。

也就是: 节点的个数(N) ≈ 节点高度的和

完整代码:

[java] view plaincopy
  1. public class BinaryHeap<T extends Comparable<? super T>> {  
  2.   
  3.     private static final int DEFAULT_CAPACITY = 10;  
  4.       
  5.     /** 
  6.      * 堆中元素个数 
  7.      */  
  8.     private int currentSize;  
  9.       
  10.     private T[] array;  
  11.       
  12.     public BinaryHeap() {  
  13.         super();  
  14.         currentSize = 0;  
  15.         array = (T[]) new Comparable[ DEFAULT_CAPACITY ];  
  16.     }  
  17.   
  18.     public BinaryHeap( T[] items ){  
  19.         currentSize = items.length;  
  20.         array = (T[]) new Comparable[ (currentSize + 2) * 11 / 10 ];  
  21.           
  22.         int i=1;  
  23.         for( T item : items ){  
  24.             array[ i++ ] = item;  
  25.         }  
  26.         buildHeap();  
  27.     }  
  28.       
  29.     private void buildHeap(){  
  30.         forint i = currentSize/2; i>0; i-- )  
  31.             percolateDown( i );  
  32.     }  
  33.       
  34.     private void enlargeArray( int size ){  
  35.         Comparable[] newArray = new Comparable[size];  
  36.         forint i=1 ; i<array.length ; i++ ){  
  37.             newArray[i] = array[i];  
  38.         }  
  39.           
  40.         array = (T[]) newArray;  
  41.     }  
  42.       
  43.     public void insert( T x ){  
  44.         if( currentSize == array.length - 1 ){  
  45.             enlargeArray( array.length*2 + 1 );  
  46.         }  
  47.           
  48.         //上滤  
  49.         int hole = ++currentSize;  
  50.         for( ; hole>1 && x.compareTo( array[ hole/2 ])<0; hole/=2 ){  
  51.             array[ hole ] = array[ hole/2 ];  
  52.         }  
  53.         array[ hole ] = x;  
  54.     }  
  55.       
  56.     public boolean isEmpty(){  
  57.         return currentSize == 0;  
  58.     }  
  59.       
  60.     /** 
  61.      * 删除最小 
  62.      * @return 
  63.      * 
  64.      * Date   :2012-7-6 
  65.      * Author :GongQiang 
  66.      */  
  67.     public T deleteMin(){  
  68.         if( isEmpty() )  
  69.             throw new UnderflowException();  
  70.           
  71.         T minItem = array[1];  
  72.         array[1] = array[ currentSize-- ];  
  73.         percolateDown( 1 );  
  74.           
  75.         return minItem;  
  76.     }  
  77.       
  78.     /** 
  79.      * 下滤 
  80.      * @param hole 
  81.      * 
  82.      * Date   :2012-7-6 
  83.      * Author :GongQiang 
  84.      */  
  85.     private void percolateDown( int hole ){  
  86.         int child;  
  87.         T tmp = array[ hole ];  
  88.           
  89.         for( ; hole*2 <= currentSize; hole=child ){  
  90.             child = hole*2;  
  91.               
  92.             if( child != currentSize && array[child+1].compareTo( array[child] ) <0 ){  
  93.                 child++;  
  94.             }  
  95.               
  96.             if( array[child].compareTo( tmp ) < 0 ){  
  97.                 array[hole] = array[child];  
  98.             }  
  99.             else{  
  100.                 break;  
  101.             }  
  102.         }  
  103.         array[hole] = tmp;  
  104.     }  
  105. }  
  106.   
  107. class UnderflowException extends RuntimeException{  
  108.       

0 0
原创粉丝点击