ヒープ(バイナリヒープ)

Good Math, Bad Math : Binary Heaps
ヒープは、スタックやキューと並びシンプルで強力なデータ構造です。ヒープとは、木構造の一種で要素の挿入と最大値(又は最小値)の要素の削除の計算量が共にO(logN)。このヒープを2分木で表現したものを、バイナリヒープと呼びます。バイナリヒープの場合、要素の挿入と削除が行われると、常に根ノードと最大値の要素が常に等しくなるよう、ノードの入れ替えが行われます。
このようなヒープの性質は、優先順位付きキューなど、何らかの基準値によって要素が順位付けされた集合を表現するデータ構造に向いています。また、仕組みが単純な分、実装が容易な点も魅力的です。
恐らく多くの方々にとって、ヒープは一般的なアルゴリズムの教科書で説明されているように、整列プログラムを実装するためのデータ構造としてよく知られているのではないでしょうか。上のヒープの性質を応用すれば、簡単に配列の要素を整列出来ることがすぐに分かるでしょう。すなわち、配列から一つずつ要素を取り出してヒープに挿入すれば、後から自然と要素が整列された順番でヒープから要素が取り出すことが出来ます(なんて簡単!)。
ヒープソートの計算量は、ヒープの挿入と削除の計算量はO(logN)なので、N個の要素を整列する場合、O(NlogN + NlogN) = O(NlogN) です。代表的な整列アルゴリズムであるクイックソートの場合、平均計算量はO(NlogN)ですが、配列を分割するピボットの選び方によって最悪の場合O(N^2)かかる傾向があります。これに対して、ヒープソートは平均と最悪の場合で変わらず常にO(NlogN)しかかかりません。ただし、空間効率という点においてヒープソートは、ヒープを構築するために最低で元の配列の2倍のメモリサイズが必要なので、他の整列アルゴリズムに劣ります。しかし、PCのメモリ資源が潤沢な現在、計算コスト>空間コスト のような状況においては、ヒープソートの採用は十分に検討する価値があると思います。
以下は、バイナリヒープのサンプルプログラムです。こちらで紹介されているソースコードRubyで再実装してみました。参考にどうぞ。

class Heap
  
  def initialize
    @list = []
  end

  def insert(value)
    @list << value
    if @list.length > 1
      last = @list.length - 1
      bubble_up(last)      
    end
  end
  
  def remove_largest
    root = @list.shift
    bubble_down(0)
    root
  end
  
  def to_s
    "[#{@list.join(',')}]"
  end  
  
  private
  
  def parent_of(pos)
    if pos == 0
      nil
    elsif pos == 1 || pos == 2
      0
    else
      if pos % 2 == 0
        (pos - 2)/2
      else
        (pos - 1)/2
      end  
    end
    
  end
  
  def children_of(pos)
    n = @list.length
    left = 2*pos + 1
    right = 2*pos + 2
    if n <= left
      []
    elsif n == right
      [left]
    else
      [left, right]      
    end
  end  

  def swap(i, j)
    tmp = @list[i]
    @list[i] = @list[j]
    @list[j] = tmp
  end
  
  def bubble_up(pos)
    unless pos == 0
      parent = parent_of(pos)
      if @list[parent] < @list[pos]
        swap(parent, pos)
        bubble_up(parent)
      end            
    end    
  end
  
  def bubble_down(pos)
    children = children_of(pos)
    unless children.empty?
      if children.length == 1
        i = children[0]
        if @list[pos] < @list[i]
          swap(pos, i)
          bubble_down(i)
        end
      else
        (i, j) = children
        left = @list[i]
        right = @list[j]
        root = @list[pos]
        if (left > root) || (right > root)
          if left > right
            swap(i, pos)
            bubble_down(i)
          else
            swap(j, pos)
            bubble_down(j)
          end
        end
      end
    end
  end
  
end

def heap_use_example
  heap = Heap.new

  heap.insert(1)
  heap.insert(3)
  heap.insert(5)
  heap.insert(7)
  heap.insert(9)
  p heap.to_s

  list = []
  list << heap.remove_largest
  p heap.to_s
  list << heap.remove_largest
  p heap.to_s
  list << heap.remove_largest
  p heap.to_s
  list << heap.remove_largest
  p heap.to_s
  list << heap.remove_largest
  p heap.to_s

  p list.join(',')  
end

heap_use_example