最後となるソートの 3 回目は、「高速なソート」であるクィックソート、 一寸変わり種のコムソート、用途は限定されてるけど高速な数えソートです。
プログラムの中でも「不朽の名作」といわれるアルゴリズムの一つです。 一つのアルゴリズムのみを使う場合では、ほぼ最速と言われています。
このソート法も、基本は divide and conquer
です。
さらに、再帰を使ったコードにするとよりエレガントに書けるため、
再帰呼び出しの練習用アルゴリズムとして用いられることがあります。手順は以下の通りです。
最後の「1. 〜 3.」の「3.」ってのが再帰呼び出しがある事を意味するんですが、解りますか?
図にすると図 (a) のようになります。 下から 2 番目は、分割しているように見えませんが、 2 分割できているときと同じように操作するとこのようになります。
「2つのグループに分けている様子」が分かると、
もっと解りやすいんですが……。
しきい値の採り方としては以下のようなものがあります。
ちなみに、このクイックソートにも弱点があって、その中でも有名なのが「逆順に並んだデータ」でしょう。 クイックソートのオーダは平均で O(n*log2n) なんですが、この場合だと O(n2) 程度になってしまい、場合によってはバブルソートより遅くなることもあるそうです。
バブルソートの変種ですが、親のバブルソートとはえらい違いで、クイックソートの半分くらいの速度が出るそうです。
「コム」とは "comb"、すなわち「櫛 (クシ)」のことです。
また、割と新しいアルゴリズムらしいです。詳しくは知りませんが。
バブルソートは隣接した (1 だけ離れた) 要素間で比較・交換を行うが、 コムソートは n だけ離れた要素間で比較・交換を行う。
n は 要素数より小さい任意の数から始まって、i = ∞ で ni = 1 となる。 実際には n1 = 要素数 - 1、ni+1 = ni / 1.3 あたりが良いらしい。
実装では n = ( n * 10 + 3 ) / 13 として、小数点演算を避けると共に、 +3 して n = 0 となるのを完全に防ぐ方法が用いられる。
ということが参照した文献に載ってましたので、これをそのまま実装しました。 バブルソートを少し弄るだけで良いので、実装は簡単です。
たぶん正式な名前があると思うんですが、私は数えソートと呼んでいます。これは、今まで出てきたソートとは違い、
という、いろんな意味でとんでもないソートです。
ソースも短いので、以下にそのまま載せてしまいます。出てきた回数をカウントして、 小さい方から回数分出力する、というのがアルゴリズムの仕組みです。
/* 数えソート。こればっかりはプロトタイプも仕方ない *
* 0〜255 の値のみに有効。汎用性はもう少し上げられる */
#define TBLSIZE 256
void countsort( int *value, size_t nel )
{
size_t cnttbl[TBLSIZE];
int i, j;
/* 初期化 */
memset( cnttbl, 0, TBLSIZE * sizeof( int ) );
/* カウント */
for( i = 0 ; i < nel ; i++ )
if( 0 <= value[i] && value[i] < TBLSIZE )
cnttbl[value[i]]++;
/* 出力 */
j = 0;
for( i = 0 ; i < TBLSIZE ; i++ )
for( ; cnttbl[i] > 0 ; cnttbl[i]-- )
{
value[j] = i;
j++;
}
}
Cで書いてみたサンプル (download)
クイックソートにちょっと気になる部分が有るんですが、「それっぽく直ってる」し
「十分なサンプルを用いたテストでも誤動作はなかった」ので、まぁこれで良しとします。御免。
残った3つは、また暫くしたら掲載ということにしておきます。とりあえず、
これらの問題が解決されれば組める……と、思います。