Explain Sort Algorithms  

高速アルゴリズム

 ここでは高速に処理ができるアルゴリズムを紹介する。ここで高速と呼んでいるものは、O(nlogn)に分類できるものである。  

ヒープソート ( Heapsort )

 ヒープ(heap)とは、以下の条件を満たす2分木のことである。2分木とは、1つの頂点から2本以下の枝が出ている木のことであり、計算機分野では重要な構造の1つである。
 ヒープ:2分木の各頂点には次の2式を満たさなければならない。
      1. h(i) ≧ h(2i)     i=1,2,3,...
            2. h(i) ≧ h(2i+1)   i=1,2,3,...
 
 つまり、2分木の各頂点に位置するデータの値は、常に上に位置するデータの値以下になっていなければならない。
 例
                  h(1)
                                |
          |−−−−−−−−−−−−−−−−−−|
         h(2)                  h(3)
        |−−−−−+−−−−−|      |−−−−−+−−−−−|
   h(4)          h(5)         h(6)                   h(7)
  |−−+−−|     |−−+−−|
h(8)        h(9)       h(10)

 この場合、h(1)≧h(2),h(3)
       h(2)≧h(4),h(5)
      h(3)≧h(6),h(7)
      h(4)≧h(8),h(9)
      h(5)≧h(10)
 
である。ヒープによる並べ換えは、ヒープがすでに与えられているものとして、ヒープが空になるまで以下の2つのステップを繰り返す。
ステップ 1 最上位のh(1)を取り出す。
ステップ 2 残りのヒープh(2),h(3),...,h(n)をヒープに再構成する。
        再構成は、以下のa), b) にしたがって行なう。
       a) h(1)の位置にh(n)を移動する。もとのh(n)は削除する。
        b) h(1)の位置からshiftdown操作を行なう。
        shiftdown操作: h(i) < max(h(2i), h(2i+1))のとき、
                h(2i)とh(2i+1)の大きい方をh(i)と交換する。
                               交換したところから、同様な操作を繰り返す。

 shiftdown操作の例を以下に示す。

初期状態:
              84
         |−−−−−+−−−−−|
         73          66
     |−−−+−−−|   |−−−+−−−|
    22        37  11       49 
  |−−+−−|
  6
 
状態2:初期状態においてh(1)である84を取り出し、h(n)である6をh(1)の位置に移動する。  
                6       −−−>84
         |−−−−−+−−−−−|
        73           66
     |−−−+−−−|   |−−−+−−−|
     22     37   11     49 
  |−−+−−|
  
h(1)の位置(6)からshiftdown操作を行なう。

               73       −−−>84
         |−−−−−+−−−−−|
         6           66
     |−−−+−−−|   |−−−+−−−|
     22     37   11     49 



               73       −−−>84
         |−−−−−+−−−−−|
         37          66
     |−−−+−−−|   |−−−+−−−|
     22      6   11     49 

状態3:h(1)である73を取り去り、h(n)である49をh(1)の位置に移動する。

               49       −−−> 73 84
         |−−−−−+−−−−−|
         6           66
     |−−−+−−−|   |−−−+−−−|
     22     37   11     

 h(1)からshiftdown操作を行なう。

               66       −−−> 73 84
         |−−−−−+−−−−−|
         6           49
     |−−−+−−−|   |−−−+−−−|
     22     37   11     
 
状態4:66を取り除き、11をh(1)位置に移動し、shiftdown操作を行なう。

               49       −−−> 66 73 84
         |−−−−−+−−−−−|
         6           11
     |−−−+−−−|   
     22     37        

状態5:以下同様な操作を繰り返す。

             37       −−−> 49 66 73 84
       |−−−−−+−−−−−|
       6           11
   |−−−+−−−|   
   22             

状態6:

          22       −−−> 37 49 66 73 84
    |−−−−−+−−−−−|
    6           11

状態7:

          11    −−−> 22 37 49 66 73 84
    |−−−−−+−−−−−|
    6 

状態8:

          6   −−−> 11 22 37 49 66 73 84
    |−−−−−+−−−−−|
 
よって、取り除かれたデータは並べ換えが済んでいる。

 次に、最初のヒープを構成する方法について述べる。

ステップ1 任意の2分木を与える。この2分木の各頂点のデータの値をそれぞれ
       h(1)、h(2)���ぃ�(3)����...をする。
ステップ2 この2分木で、枝が1本も出ていない頂点、つまり
       h(k),h(k+1)、...���ぃ�(n) (k=[n/2]+1)
       は、下位にデータを持たないため、ヒープの条件が満足されていると
       解釈できる。 
ステップ3 残りの頂点をh(k-1),h(k-2),...,h(1)の順にとりあげ、その頂点を
            下位の部分木の根(h(1))とみたてヒープを構成していく。その際には、
            前記のshiftdown操作を使う。

 ヒープ生成の例

初期状態:
               21
         |−−−−−+−−−−−|
        53           12
     |−−−+−−−|   |−−−+−−−|
     46     87   59      40 
  |−−+−−|
 70
  
状態2:初期状態において70,40,59,87は下位にデータを持たないので、ヒープの条件を満足していると考えられる。まず、46を2分木の根とみたてshiftdown操作を行なう。
               21
         |−−−−−+−−−−−|
         53           12
     |−−−+−−−|   |−−−+−−−|
    70        87  59       40 
  |−−+−−|
  46
 
状態3:状態2において70,87,59,40,46は、ヒープの条件を満足していると考えられる。次に12を2分木の根とみたてshiftdown操作を行なう。
               21
         |−−−−−+−−−−−|
         53           59
     |−−−+−−−|   |−−−+−−−|
     70       87  12       40 
  |−−+−−|
  46
 
状態4:状態3において59,70,87,12,40,46は、ヒープの条件を満足していると考えられる。次に53を2分木の根とみたてshiftdown操作を行なう。
               21
         |−−−−−+−−−−−|
         87          59
     |−−−+−−−|   |−−−+−−−|
     70       53  12       40 
  |−−+−−|
  46
 
状態5:状態4において87,59,70,53,12,40,46は、ヒープの条件を満足していると考えられる。次に21についてshiftdown操作を行なう。
               87
         |−−−−−+−−−−−|
         70          59
     |−−−+−−−|   |−−−+−−−|
     46       53  12       40 
  |−−+−−|
  21
 
よって、任意に与えた2分木に対して、ヒープが構成できた。

 上記で説明した考えをもとに、データの並べ換えを行なう。実際に計算機で行なう場合、2分木を表現することは面倒なことである。そこで、今回は配列を工夫し2分木にみたてておこなう。下記のような配列を考えてみよう。

   ------+-----+-----+-----+-----+-----+-----+-----+-----+----
  |h(1)|h(2) h(3)|h(4) h(5) h(6) h(7)|h(8) h(8)・・・
     ------+-----+-----+-----+-----+-----+-----+-----+-----+----
 
便宜的に、配列の区切りを入れている所と入れていない所がある。これは、2分木の階層を明らかにするためである。上記配列は、下記の2分木を表現していることを理解していただきたい。
                  h(1)
                                |
          |−−−−−−−−−−−−−−−−−−|
          h(2)                 h(3)
        |−−−−−+−−−−−|      |−−−−−+−−−−−|
   h(4)          h(5)         h(6)                   h(7)
  |−−+−−|     |−−+−−|
 h(8)       h(9)       h(10)
 アルゴリズムの進行とともにヒープが縮小するため、配列の後尾が空となる。そこで、ヒープから取り出したデータを配列の後ろから順に詰めて行くことにより、ソートが完了した時点で、配列内にデータが並べ替わっていることになる。

 配列の場合の例

(1) 初期ヒープの生成

初期データ:


 	21  53  12  46  87  59  40  70
 
状態2:初期状態において70,40,59,87は下位にデータを持たないので、ヒープの条件を満足していると考えられる。まず、46を2分木の根とみたてshiftdown操作を行なう。

	21  53  12  70  87  59  40  46
 
    状態3:状態2において70,87,59,40,46は、ヒープの条件を満足していると考えられる。次に12を2分木の根とみたてshiftdown操作を行なう。

	21  53  59  70  87  12  40  46
状態4:状態3において59,70,87,12,40,46は、ヒープの条件を満足していると考えられる。次に53を2分木の根とみたてshiftdown操作を行なう。

	21  87  59  70  53  12  40  46
状態5:状態4において87,59,70,53,12,40,46は、ヒープの条件を満足していると考えられる。次に21を2分木の根とみたてshiftdown操作を行なう。

        87  70  59  46  53  12  40  21
(2) ソート 初期状態:

	87  70  59  46  53  12  40  21
状態2:先頭の87(h(1))を取り出しす。 21(h(k))を2分木の根(h(1))に移動し、shiftdown操作を行なう。 取り出した87は配列の最後に格納する。

	70  53  59  46  21  12  40  87
状態3:先頭の70(h(1))を取り出しす。40を2分木の根(h(1))に移動し、shiftdown操作を行なう。 取り出した70は配列の最後から2番目にに格納する。

	59  53  40  46  21  12  70  87
状態4:先頭の59(h(1))を取り出す。12を2分木の根(h(1))に移動し、shiftdown操作を行なう。 取り出した59は配列の最後から3番目に格納する。

	53  46  40  12  21  59  70  87
状態5:以下同様な操作を繰り返す。

	46  21  40  12  53  59  70  87
状態6:

	39  20  12  46  53  59  70  87
状態7:

        21  12  40  46  53  59  70  87
状態8:

	12  21  40  46  53  59  70  87
 上記の考えをプログラムすると、次のようになる。

 1:  void HeapSort(int data[],int n)  {
 2: 	int top,bottom,tmp;
 3:
 4: 	bottom = n/2 +1;
 5: 	top = n;
 6: 	while (bottom > 0) {
 7:	        bottom--;
 8: 		Shiftdown(data,top,bottom);
 9: 	}
10:  	while (top > 0) {
11:  		tmp = data[0];
12:  		data[0] = data[top];
13:  		data[top] = tmp;
14:  		top--;
15:  		Shiftdown(data,top,bottom);
16:  	}
17:  }
18:  
19:  void Shiftdown(int data[],int top,int bottom) {
20:  	int i, tmp;
21:  	
22:  	i = 2 * bottom;
23:  	while(i <= top) {
24:  		if(i < top && data[i+1]>data[i])  i++;
25:  		if(data[bottom]>=data[i])  break;
26:  		tmp = data[bottom];
27: 		data[bottom] = data[i];
28:  		data[i] = tmp;
29:  		bottom = i;
30:  		i = 2 * bottom;
31:  	}
32:  }
 6〜9で初期のヒープを作成し、10〜16でヒープの再構成を行なっている。
 ヒープソートの計算時間量は、O(nlogn)である。簡単に、理由を述べる。主に関数shiftdown内の入れ換えの回数に着目してやればよい。つまり、26〜28の実行回数である。2分木の根から順に、下位の部分木の頂点にあるデータと比較するため、1回のshiftdown操作では、log(n)回(logとは対数関数であり、この場合2分木であるため対数の底を2として考えている。nはデータの個数である。)比較を行なっていることになる。
 関数shiftdownの呼ばれる回数は、データの個数nに比例している。そのため、n*log(n)が得られる。基本的な考え方は以上の通りである。
 ヒープソートは、クイックソートに比べて平均約2倍のスピードを要する。しかしながら、ヒープソートは、どのような配置のデータにも常にO(nlogn)で処理がなされ、外部記憶装置内のデータにも適用可能なことから、利用度の高いアルゴリズムであるといえる。

戻る

アプレットに戻る

a93sj030@j.dendai.ac.jp