O(N*log(N))のSorting Algorithm - Heap Sort(整列2分木法)
http://d.hatena.ne.jp/yokolet/20080628#1214690163で、計算量の話をしたのですが、"Sorting"に、O(N^2)とO(N*logn(N))のsorting algorithmsの比較の図が掲載されていました。この図を見ると、入力データ数が大きくなると、明らかにO(N*log(N))で済む方が速いことがわかります。O(N*log(N))の計算量で済むsortingのひとつ、Heap Sort(整列2分木法)について調べてみたので忘れないようにメモです。
Heap Sortというのはheapと呼ばれるデータ構造を利用するソート法です。このデータ構造は与えられた入力値を2分木にマッピングしていきますが、ツリーのどの場所でも、親の要素は2つの子の要素よりも常に小さい(または大きい)状態を保っている木です。つまり、root(根)の部分には必ず最小値(または最大値)がくるようになっています。例えば、16, 1, 9, 6, 10, 2, 11, 8, 4, 5という数字をheapで表現すると
1 / \ 4 2 / \ / \ 6 5 9 11 / \ | 8 16 10
のようになります。このようなheapを表現するのに使われるのがPriority Queueと呼ばれる配列です。このheapの場合、
[1, 4, 2, 6, 5, 9, 10, 8, 16, 10]
というように、ツリーのroot、一段下がって左から右へ、さらに一段下がって左から右へと並べて配列を作るとPriority Queueになります。
Heap sortを行うには、heapのroot nodeを削除して、代わりにthe deepest & rightmost node(最も下の最も右)をroot nodeに置いたのち、各要素を比較してheapを作り直す操作を再帰的に実行します。
初期状態 1 / \ 4 2 / \ / \ priority queue 6 5 9 11 [1, 4, 2, 6, 5, 9, 10, 8, 16, 10] / \ | 8 16 10 root nodeを取り出して、the deepest,rightmostをroot nodeに置いた状態 10 / \ 4 2 / \ / \ 6 5 9 11 / \ 8 16 最上段の親と2つの子の要素を比較して並べ替えた状態 2 / \ 4 10 / \ / \ 6 5 9 11 / \ 8 16 並べ替えが終わった状態 2 / \ 4 9 / \ / \ priority queue 6 5 10 11 [2, 4, 9, 6, 5, 10, 11, 8, 16] / \ 8 16 次のステップでは2を削除して16をrootに持ってきて、heapを作り直す。
この作業を繰り返すと、常に最小値(または最大値)をツリー構造から取り出すことになるので、取り出した値をその順番で並べればソートが出来上がるというものです。
Javaでは1.5でjava.util.PriorityQueueが追加されたので、このクラスを使ってHeap Sortを行ってみました。
import java.util.ArrayList; import java.util.Iterator; import java.util.PriorityQueue; public class HeapSort { private ArrayListsortedList = new ArrayList (); private HeapSort() { ArrayList inputList = new ArrayList (); int inputs = {16, 1, 9, 6, 10, 2, 11, 8, 4, 5}; for (int input : inputs) { inputList.add(input); } PriorityQueue queue = new PriorityQueue(inputList); sort(queue); System.out.println(sortedList); } private void sort(PriorityQueue queue) { print(queue); Integer value = (Integer)queue.poll(); if (value != null) { sortedList.add(value); sort(queue); } } private void print(PriorityQueue q) { Iterator iterator = q.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next() + "|"); } System.out.println(); } public static void main(String args) { new HeapSort(); } } 実行結果 1|4|2|6|5|9|11|8|16|10| 2|4|9|6|5|10|11|8|16| 4|5|9|6|16|10|11|8| 5|6|9|8|16|10|11| 6|8|9|11|16|10| 8|10|9|11|16| 9|10|16|11| 10|11|16| 11|16| 16| [1, 2, 4, 5, 6, 8, 9, 10, 11, 16]
というようにheapのroot nodeやそれ以下のツリー構造が順番に入れ替わってソートされていく様子がわかります。計算量は2分木を使っているので、1/2*1/2...のところからO(log(N))、要素を比較して入れ替えるところは線形なのでO(N)、全体でO(N*log(N))になるというものです。
なお、 java.util.PriorityQueueはComparatorを指定すれば文字列を比較してsortingする用途にも使えます。