Explain Sort Algorithms  

高速アルゴリズム

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

クイックソート ( Quicksort )

 クイックソートは世の中で最も速いソートアルゴリズムとして知られている。(これは、一つのCPUで順次処理を行なった場合である。最近では、計算機の機能が高まり、並列処理や分散処理が可能となっているため、複数のCPUを持った計算機上での並べ換えについては、クイックソートよりも速いアルゴリズムが提案されている。)
 クイックソートは、データの分割法をソートのアルゴリズムに適用した典型なものである。データ分割法の基本原理は、
	1 データのブロック分割
	2 各ブロックごとの処理(並べ換え)
	3 ブロックの統合
  
であり、クイックソートでは特に1のデータの分割にアルゴリズムのよさの秘密が隠されている。例を用いてクイックソートのアルゴリズムを解説しよう。

    48   65   10   36   83    5   21   72
 
 ステップ01
 まず与えられたデータ列の真ん中(左から4番目)に位置するデータを基準値をして定める。例では、36と基準値としよう。

     48   65   10   36   83    5   21   72
  
 ステップ02
データを左から順に基準値よりも大きいデータを捜す。まず48が見つかる。続いて、右から順に基準値よりも小さいデータを捜す。 21が見つかる。ここで、48と21を入れ替えする。現在注目しているデータの位置を次に進める(左から見ている場合は一つ右へ、右からの場合は左へ一つ進める)。

     21   65   10   36   83    5   48   72
  
さらに、ステップ02を続けると、5と65が入れ替わる。

     21    5   10   36   83   65   48   72
 
以下、同様な操作を繰り返し、注目している点が交差するまで行なう。すると

     21    5   10   36   83   65   48   72
  
が得られる。これは、基準値とした位置(左から4番目)を境に、左右ブロックに分割したことになる。左側には4番目のデータ(36)よりも小さいデータ、右側には大きいデータとなる。

 ステップ1ー1
 左側のブロックに対して、ステップ01、02を行なう。


	21       5     10  

	 5  |   21     10  
 
  よって、5と21,10とに分割できる。

 ステップ1ー1ー1
 さらに、左側についてステップ01、02を行なう。この場合、5だけなのでなにもしない。

 ステップ1ー1ー2
 右側の21,10についてステップ01、02を行なう。 上記ステップ1ー1ー1、1ー1ー2により、

     5    10    21 
  
が得られる。

 ステップ1ー2 
83,65,48,72について同様にステップ01、02を行なう。よって、


     5   10   21   36   48   65   72   83 
  
 が得られる。

基準点としたデータが、左、右いずれかのブロックに含まれる時と含まれない時がある。その詳細についてはプログラム6−4を見ていただきたい。ここでは、アルゴリズムの大体の考え方を理解できればよいと考えている。
 クイックソートのプログラムは以下のようである。


 1:  void QuickSort( int data[], int upper, int lower ) {
 2:  	int mid, tmp, i, j;
 3: 
 4: 	i = upper;
 5: 	j = lower;
 6: 	mid = data[(lower+upper)/2];
 7: 	do {
 8: 		while( data[i] < mid ) i++;
 9: 		while( mid < data[j] ) j--;
10:  		if( i <= j ) {
11:  			tmp = data[i];
12:  			data[i] = data[j];
13:  			data[j] = tmp;
14:  			i++;
15:  			j--;
16:  		}
17:  	} while( i <= j );
18:  	if ( upper < j ) QuickSort(data, upper, j);
19:  	if ( i < lower ) QuickSort(data, i, lower);
20:  }
Quicksortメソッドは、プログラム内部でさらに自分自身を呼んでいる。つまり再帰的なアルゴリズムとなっている。先に示した例のデータを用いて実行の様子を表現すると、下記のような構造となっている。
	 (  48  65  10  36  83  5  21  72  )
              |
			 |QuickSort(1,8)
         −−−−− +−−−−−−−
       |     |       |
 	( 21  5  10 )  ( 36 )  (  83  65  48  72 )
        |                         |
QuickSort(1,3)|                         |QuickSort(5,8)
      −−− +−−         −−+−−−−
   |      |         |             |
    ( 5 )      ( 21 10 )       ( 48 ) ( 65 )   ( 83 72 )
          |               |
     QuickSort(2,3)|                              |QuickSort(7,8)
               −−+−−           −−+−−
       |    |          |    |
      ( 10 )    ( 21 )                ( 72 )    ( 83 )
よって分割したブロックを統合すると、

	5   10   21   36   48   65  72   83 
 
となる。

 クイックソートの計算時間量を評価してみよう。今回は、プログラム中の11〜13の実行回数をカウントすることで、オーダーを求めることにする。

1段目	QuickSort(1,n)では、入れ替えの起こる確率を1/2とすると約n/2回
	実行される。
2段目 QuickSort(1,n/2)、QuickSort(n/2,n)では、入れ替えの起こる確率を1/2と
    するとそれぞれ約n/4回実行される。二つの回数を合わせると、約n/2回
    の実行となる。
3段目	4つの関数QuickSort(1,n/4)、QuickSort(n/4,n/2)、QuickSort(n/2,3n/4)、
        QuickSort(3n/4,n)が実行されそれぞれ約n/8回の実行となる。これらを合
    わせると約n/2回の実行となる。
4段目 同様に考えると8回のQuickSort関数を実行し、それぞれ約n/16回実行。
	合わせると約n/2回の実行回数となる。
 
 以上から、各段階では約n/2回の入れ替えが起こることがわかる。
   上記分割が何回(何段まで)起こるかを考えよう。1回の関数の実行では2つに分割され、それらがさらに2つに分割される。これが1つのデータとなるまで繰り返される。よって分割の回数は、log(n)回であると言える。ここで、logとは数学の対数関数を意味し、この場合の対数の底は2である。 n=2^k(nが2のk乗)である時、k=log(n)である。
 よって、プログラム11〜13の実行回数は、n/2 * log(n) 回となり、プログラムはO(nlogn)であると言える。これは、O(n^2)のアルゴリズムと比較すると、非常に効率がよいことがわかる。  今回の解説では分割の基準点を中心として行なったが、先頭データを基準点とする方法もある。完全にランダムなデータに対しては、基準点をどちらに設定しても同じ計算の手間で実行される。しかし、すでに並べ替えが完了しているデータに対して、先頭のデータを基準点とする方法を適用すると、最悪ケースとなりO(n^2)の計算量となることが知られている。

戻る

アプレットに戻る

a93sj030@j.dendai.ac.jp