今回は、先ずはバブルソート同様低速なソート法を考えます。しかし、それをもとにした高速なソート法も同時に紹介したいと思います。
それでは、今回の要点です。
では、いってみましょう。
今回も新しいソート法を考えてみます。今回のソート法は人間がよく使う方法です。
トランプをするとき、手札を数字の順番通りに並べることがよくあります。こういうとき、以前のバブルソート、選択法と同じように並べ替える人は少ないと思います。(そうしている人いたら、ごめんなさい。)
普通は、並んでいない部分から1枚抜き取り、並んでいる部分に大小の合うように差し込んでいくと思います。これが今回のソート法、挿入法です。
図.1 挿入法 |
---|
コンピュータにやらせるときは、右の図のようにします。
先ず、初めの1要素をソートされた列と見なします。そして、2番目の要素をここに挿入します。これでこの2要素はソートされました。
次、3番目の要素をソートされた列に挿入します。これで3要素がソートされました。
あとは同じように要素をソートされた列に挿入していき、挿入するものがなくなった時点でソート完了です。
では、プログラムを見てみましょう。
プログラム |
---|
// Sort3.cpp #include "SortData.h" #include "SortSub.h" // 挿入法によるソート void InsertionSort(int* pnData, int nNumOf) { int iInsert; // 挿入する要素のインデックス int iSwap; // 交換していくときのループ変数 // 挿入する要素のインデックスは1から始まる for(iInsert = 1; iInsert < nNumOf; iInsert++) { // 挿入は順次交換することで行う for(iSwap = iInsert; iSwap > 0; iSwap--) { // 順序がきちんとした時点でループを終える if(pnData[iSwap - 1] < pnData[iSwap]) break; Swap(pnData[iSwap - 1], pnData[iSwap]); } } } int main() { InsertionSort(g_anData, g_nNumOf); DispArray(g_anData, g_nNumOf); return 0; } |
実行結果 |
1, 1, 4, 6, 9, 9, 10, 11, 13, 14, 15, 16, 23, 25, 28, 29, 31, 33, 34, 39, 39, 40, 40, 41, 42, 44, 48, 51, 51, 53, 54, 55, 56, 63, 67, 68, 73, 73, 76, 77, 78, 79, 79, 87, 89, 99, 99, 101, 101, 103, 110, 112, 116, 117, 117, 118, 124, 128, 136, 137, 137, 138, 148, 150, 152, 155, 157, 160, 161, 164, 166, 167, 168, 169, 173, 173, 174, 175, 185, 186, 188, 192, 199, 203, 213, 215, 222, 228, 228, 229, 231, 235, 239, 240, 240, 242, 249, 249, 254, 255, |
はい、きちんとソートできましたね。
確かにソートはできるのですが、これでは交換回数が多くバブルソートと大して変わりがありません。事実、速度はバブルソートと大して変わりません。乱数のソートで試したところ、データ数が少なければバブルソートの方が、多ければ挿入法の方が若干速いみたいです。
では、そんなものを何故紹介したのかというと、このソート法を元にした高速なソート法が開発されているからです。名前はシェルソートといいます。shell は「殻」という意味の単語ですが、シェルソートのシェルは開発者の名前 D.Shell から来ています。
挿入法が何故遅いのかというと、挿入位置が遠くにあった場合に沢山交換が起こるからです。
そこで先ずは離れた要素間でソートするようにすれば、極端に位置のずれた要素も少ない交換回数でソートできます。ただ、もちろんそれだけではソートは完了しません。その幅を狭めてまたソートし、それを幅が1になるまで続ければ、ソートが完了します。こうすれば、もっと交換回数を減らすことができるはずです。
これを図にすると次のようになります。
図.2 シェルソート |
---|
先ず、一番上の列を見て下さい。ここで、同じ色の要素同士でソートをします。すると、その下の列のようになります。
で、次に分け方を変えます。同じ色の要素同士の距離が短くなっていますね。そして、また同じ色の要素同士でソートします。すると、大体大雑把にソートされていることが目に見えて分かります。
そして、最後に全体でソートをし直すと終了です。挿入法では、ソートされた状態に近いと、挿入位置が近いため処理は格段に減ります。こうして、初めは各グループの要素数が少ないので速く、後の方はソートされている位置に近くて速くなり、全体的にかなりの高速化が期待できます。
では、プログラムを見てみましょう。
プログラム |
---|
// Sort4.cpp #include "SortData.h" #include "SortSub.h" // シェルソート void ShellSort(int* pnData, int nNumOf) { int nSpan; // ソート間隔 // 初めは nNumOf / 2 だけ離れた場所の要素同士でソートする // 以後、間隔を2分の1していき、最終的には nSpan が1になる // 2進数で考えると分かりやすいが、最終的には nSpan は必ず1に到達する for(nSpan = nNumOf / 2; nSpan >= 1; nSpan /= 2) { int iInsert; // 挿入する要素のインデックス int iSwap; // 交換していくときのループ変数 // 各グループのソートをまとめて行う for(iInsert = nSpan; iInsert < nNumOf; iInsert++) { for(iSwap = iInsert; iSwap >= nSpan; iSwap -= nSpan) { if(pnData[iSwap - nSpan] < pnData[iSwap]) break; Swap(pnData[iSwap - nSpan], pnData[iSwap]); } } } } int main() { ShellSort(g_anData, g_nNumOf); DispArray(g_anData, g_nNumOf); return 0; } |
実行結果 |
1, 1, 4, 6, 9, 9, 10, 11, 13, 14, 15, 16, 23, 25, 28, 29, 31, 33, 34, 39, 39, 40, 40, 41, 42, 44, 48, 51, 51, 53, 54, 55, 56, 63, 67, 68, 73, 73, 76, 77, 78, 79, 79, 87, 89, 99, 99, 101, 101, 103, 110, 112, 116, 117, 117, 118, 124, 128, 136, 137, 137, 138, 148, 150, 152, 155, 157, 160, 161, 164, 166, 167, 168, 169, 173, 173, 174, 175, 185, 186, 188, 192, 199, 203, 213, 215, 222, 228, 228, 229, 231, 235, 239, 240, 240, 242, 249, 249, 254, 255, |
この例では分からないと思いますが、随分速くなっています。どれくらい速いかというと、10万要素の乱数列をソートしてみたところ2,3秒(MMXペンティアム300MHzで)で終わりました。同じものを生の挿入法を使ってソートすると30分くらいかかりました。
このように、高速なソートアルゴリズムは要素数の多いときに劇的に効果が現れます。遅いソート法を使っていると、それは大きな損になります。
次回はさらに高速なアルゴリズムを紹介します。速度にムラはあるものの、高速ソートアルゴリズムの代表格に君臨しています。次回を楽しみにしていて下さい。
それでは、今回の要点です。
それでは、次回まで。
Last update was done on 2000.9.20
この講座の著作権はロベールが保有しています