純函數式堆(純函數式優先級隊列)part two ----斜二項堆

前言:

這篇文章是基於我看過的一篇論文,主要是關於函數式數據結構,函數式堆(優先級隊列),數據結構

我會以本身的理解寫下來,而後論文中出現的代碼將會使用scala這們語言。dom

論文連接:   Optimal Purely Functional Priority Queues,另一個連接:   論文。    ide

 

正文:

緊接part one的內容,接下來進入斜二項堆。函數

斜二項堆(skew binomial queue)

     斜二項堆支持插入操做O(1)的時間複雜度,經過借用random-access lists中的技術來消除上述的連續優化

進位問題。spa

     爲了解決這個問題,咱們先來了解斜二進制數(Skew binary number),斜二進制最多隻有一次進位。scala

 

斜二進制數(Skew binary number)

      同二進制不一樣的地方在於,斜二進制數第k個數字表明2^(k+1) - 1(2的k+1次方減一),而不是rest

二進制的2^k。每一個位上的數字是0或者1,而二進制不一樣的是,最低非零位能夠爲2。     code

 

下面是二進制與斜二進制對應的位置的權值對比:three

從最低爲開始:   0      1      2       3        4        5         6          7          8         .....    

斜二進制:         1      3      7      15      31       63      127      255      1023     .....    

二進制:            1      2      4       8       16       32       64       128       256      .....

     

下圖是十進制數的斜二進制表示和二進制表示的對比:

     

根據上圖能夠看到只有最低非零位能夠爲2。

而給定一個十進制數轉成斜二進制的方法我尚未找到資料,可是能夠順推出來,或者拼湊法。

 

如今來描述一下斜二進制的加一的狀況,分兩種狀況:

一、若是最低非0位不是2,則加一的時候最低位增長一便可,操做就完成了。好比十進制4斜二進制的表示

     是11,加一,變成12。

二、若是最低非0位是2,則加一的時候,直接將2變成0,而後2的高一位加一,操做就完成了。

     好比十進制13,斜二進制表示是120,加一變成200。

     這兩種狀況參照上圖就很清楚了,還有進位只會產生一次是什麼狀況。

 

      相似二項堆,斜二項堆由斜二項樹組成。

斜二項樹(skew binomial tree):

定義:

一、一個rank爲0的斜二項樹是一個單節點;

二、一個rank爲r+1的斜二項樹包括如下3種狀況:

a、由兩個rank爲r斜二項樹經過簡單連接而成,簡單連接就是和二項樹連接同樣,其中一棵樹做爲另一棵樹的

     最左子樹;

b、A型斜連接,讓兩個rank爲r的斜二項樹做爲一棵rank爲0的樹的子樹。

c、B型斜連接,讓rank爲0的樹和其中一個rank爲r的斜二項樹做爲另一個rank爲r的樹的最左子樹,

     rank爲0的樹放在最左邊。

 

上圖解釋rank爲r + 1的斜二項樹的3種狀況:

    

從上圖能夠看出當r=0時,A型和B型斜連接是相等的。還有二項樹和徹底平衡二叉樹是斜二項樹的特例,

至關於,如只容許簡單連接,則斜二項樹就變成二項樹,若是隻容許A型連接則斜二項樹就變成

徹底平衡二叉樹。

 

      如今來看看斜二項樹的大小,若是一棵rank爲r的斜二項樹只經過A型和B型連接構成,

則該樹包含2^(r+1) - 1個節點。

      不過通常來講,一個rank爲r的斜二項樹的大小爲t,t知足  2^r <= | t |  <= 2^(r+1) - 1。

      由斜二項樹的定義,咱們知道和二項樹不一樣的是rank相同的斜二項樹的形狀大小是能夠不相同的。

 

下面上圖舉例rank爲2的斜二項樹的全部可能形狀(rank爲2的斜二項樹大小爲 4 ~ 7):

一樣一棵斜二項樹是堆有序的,若是樹上的每一個節點都比它的子節點要小。爲了維護堆順序,

作簡單連接時和二項樹相同,作A型連接時,rank爲0的樹根鬚要是最小,B型連接其中一棵rank爲r的樹根最小。

 

斜二項堆:

     相似二項堆的定義,斜二項堆是堆有序的斜二項樹森林,每棵樹的rank都不同,除了rank值最小的樹能夠同時

存在兩棵。由於rank值相同的斜二項樹的形狀也不必定相同。因此給定斜二項堆的大小,所包含的二項樹也不同。

     好比,大小爲4的斜二項堆有四種形態:

一、包含一棵rank爲2大小爲4的二項樹;

二、包含兩棵rank爲1的樹,每棵樹大小爲2;     

三、包含1棵rank爲1大小爲3的樹和一棵rank爲0的樹;

四、包含一棵rank爲1大小爲2的樹和兩棵rank爲0的樹。

 

     查找堆中的最小元素(findMin)操做和合並兩個堆(meld)操做和二項堆差很少。爲了查找堆中的最小元素,

只需要遍歷一次全部樹的根,時間複雜度仍是O(log n)。而對於合併操做,首先對兩個要合併的堆作一些處理,

就是若是堆中rank最小的樹存在兩棵,則將這兩棵樹作個簡單連接,而後才進行兩個堆的合併,接下來的

合併的過程和二項堆的就同樣了,若是找到兩個rank相同的樹,就將這兩棵樹作一個簡單連接

(在合併過程當中都是用簡單連接),而後再將結果合併到由剩下的樹合併而成的堆中時間複雜度也是O(log n)

      而斜二項堆的最大優勢就是上面也提到過的,插入一個新的元素時間複雜度爲O(1)

      在插入一個新的元素時,首先新建一棵rank爲0的樹,而後咱們察看堆中的rank最小兩棵斜二項樹,

若是這兩棵樹的rank值相同,則將這兩棵樹和新建的樹作一個斜連接(是A型或B型連接看具體的狀況),

獲得的rank爲r + 1的斜二項樹就是堆中rank最小的樹,直接加進堆中便可。

       若是rank最小的兩棵樹的rank值不相同,則咱們只需把新建的節點加入堆中便可,無需其餘操做,由於

這時堆中最多可能存在一棵rank爲0的樹。

      對於刪除最小元素的操做,首先仍是先找到最小元素所在的樹,而後把樹根刪除,接着把子樹進行分組,

rank爲0的分一組,rank不爲0的分一組,rank不爲0的子樹也是斜二項樹。而後把rank不爲零的樹和原來的堆合併,

最後把rank爲零的組逐個插入到堆中,論文中說複雜度是O(log n),其實認真想一下,

找最小是O(log n),分組的時間複雜度和子樹的大小有關,而後合併O(log n),單獨插入每一個元素常數是常數時間,

總的複雜度也應該和個數有關。

 

 

  斜二項堆定義:

  斜二項堆定義(參考二項樹的定義寫成):

// 對應論文第12和13頁,Figure 6 和 7
trait SkewBinomialHeap extends Heap {

  type Rank = Int
  
  case class Node( x: A, r: Rank, c: List[Node] )
  
  override type H = List[Node]

  protected def root( t: Node ) = t.x
  
  protected def rank( t: Node ) = t.r  
  
  //和二項樹定義相同
  protected def link( t1: Node, t2: Node ): Node = // t1.r==t2.r
    if ( ord.lteq( t1.x, t2.x )) Node( t1.x, t1.r + 1, t2 :: t1.c ) 
    else Node( t2.x, t2.r + 1, t1 :: t2.c )    
  
  //斜連接
  protected def skewLink( t0: Node, t1: Node, t2: Node): Node = {
      if ( ord.lteq( t1.x, t0.x ) && ord.lteq( t1.x, t2.x ) ) //B型斜連接
    	    Node( t1.x, t1.r + 1, t0 :: t2 :: t1.c )
      else if( ord.lteq( t2.x, t0.x ) && ord.lteq( t2.x, t1.x ) ) //B型斜連接
      	    Node( t2.x, t2.r + 1, t0 :: t1 :: t2.c )
      else                                                //A型斜連接
      	    Node( t0.x, t1.r + 1, List(t1, t2) )
  }
    
  protected def ins( t: Node, ts: H ): H = ts match {
    case Nil 		=> List(t)
    case tp :: ts 	=> // 一樣也只存在t.r <= tp.r 
      if ( t.r < tp.r ) t :: tp :: ts else ins( link( t, tp ), ts )
  }
  
  //若是堆中rank最小的兩棵樹的rank值相同,則將這兩棵樹連接
  protected def uniqify( ts: H ): H = ts match {
     case Nil => empty
     case t :: ts => ins( t, ts )
  }  
  
  //和二項樹的合併邏輯相同
  protected def meldUniq( ts1: H, ts2: H ): H = (ts1, ts2) match {
    case ( Nil, ts ) => ts
    case ( ts, Nil ) => ts
    case ( t1 :: ts1, t2 :: ts2 ) =>
      if ( t1.r < t2.r ) t1 :: meldUniq( ts1, t2 :: ts2 )
      else if ( t2.r < t1.r ) t2 :: meldUniq( t1 :: ts1, ts2 )
      else ins( link( t1, t2 ), meldUniq( ts1, ts2 ) )
  }
  
  override def empty = Nil
  
  override def isEmpty( ts: H ) = ts.isEmpty

  override def insert( x: A, ts: H ) = ts match {
    case t1 :: t2 :: rest => 
      		if ( t1.r == t2.r ) skewLink(Node( x, 0, empty), t1, t2) :: rest 
      		else Node( x, 0, empty) :: ts
    case _ => Node( x, 0, empty) :: ts
  }
  
  override def meld( ts1: H, ts2: H ) = meldUniq( uniqify( ts1 ), uniqify( ts2 ) )
 
  override def findMin( ts: H ) = ts match {
    case Nil => throw new NoSuchElementException("min of empty heap")
    case t :: Nil => root( t )
    case t :: ts =>
      val x = findMin( ts )
      if ( ord.lteq( root(t), x ) ) root( t ) else x
  }  
  
  //斜二項樹最複雜的操做
  override def deleteMin( ts: H ) = ts match {
    case Nil => throw new NoSuchElementException("delete min of empty heap")
    case t :: ts =>     
      
      //輔助函數,將堆中根最小的樹返回,同時返回刪除該樹的堆
      def getMin( t: Node, ts: H ): ( Node, H ) = ts match {
        case Nil => ( t, Nil )
        case tp :: tsp =>
          val ( tq, tsq ) = getMin( tp, tsp )
          if ( ord.lteq( root( t ), root( tq ) ) ) ( t, ts ) 
          else ( tq, t :: tsq )
      }      
      
      //輔助函數,將被刪除的樹的子樹進行分組,rank大於0的一組
      //也就是返回值H,rank等於0的分爲一組也就是List[A]
      def split( ts: H, xs: List[A], c: H ): ( H, List[A] ) = c match {
        case Nil => ( ts, xs )
        case t :: c =>  
        		if ( t.r == 0 ) split( ts, root( t ) :: xs, c ) 
        		else split( t :: ts, xs, c )
      }
      
      val ( Node( _, _, c ), tsq ) = getMin( t, ts )
      val ( tsr, xsr ) = split( empty, List[A](), c ) //對子樹進行分組
      val m = meld( tsq, tsr ) //將rank大於0的子樹tsr合併到tsq堆中
      ( m /:  xsr)( (h, x) => insert( x , h ) ) 
      //等價於 xsr.foldLeft( m )( ( h, x ) => insert( x , h ) ),
      //將rank小於0的樹的值插入到新堆中
  }
}

 

對斜二項堆仍是不太瞭解的讀者能夠看看這個pdf文檔:   Skew Binomial Heap  

對函數式數據結構有興趣的讀者還能夠看看這個pdf文檔:   Purely Functional Data Structures 

 

斜二項堆的介紹就到這裏,part three將會介紹剩下的優化。

相關文章
相關標籤/搜索