シェルソート
Copyright(C) 15July2010 coskx TNCT
Donald L. Shellが開発したソートのアルゴリズムである。高速ソートの一種であるシェルソートについて解説する
1.シェルソート | |
次の一連の図は「4→2→1シェルソート」により,昇順ソートをしているところを模式的に示している。
1.ソート前の状態
2.最初は「4-ソート」といって,4つずつ離れたデータのグループごとに,昇順挿入ソートを行っている。「4-ソート」が終わってもグループごとにソートされているだけなので,全体としてソートされるわけではない。
3.次は「2-ソート」といって,2つずつ離れたデータのグループごとに,昇順挿入ソートを行っている。「2-ソート」が終わってもグループごとにソートされているだけなので,全体としてソートされるわけではない。
4.最後は「1-ソート」といって,全体を1グループとして昇順挿入ソートを行う。すでにかなり大小関係によって分布が偏っているので,昇順挿入ソートを実行しても,移動手数は少なくてすむはずである。
このように,いくつかのグループに分けて,予備的にソートを行い,だいたいソートを終わらせて,最後に手直し的なソートを行うようにするのが,シェルソートである。どの回のソートにおいても,移動量はそれほど大きくはならないのが,このソートの高速さの理由である。
ここに示したソートは,4,2,1ソートの順で行ったが,倍数にならない数列によるソートの方が,効率がよい。さまざまな数列が用いられるが,最後は1で終わるようにしなければならない。よく用いられるのは1から始めて,3倍して1を加える数列(1,4,13,40,121,・・・)を逆にたどる数列である。ただし,要素数の1/9以下で構成される数列がよいとされている。
ここで示したソートを,実行するプログラムは複数の実現方法が考えられる。4重ループで実現するプログラムと3重ループで実現するプログラムの2つを紹介する。また原理通りに行っているプログラムと,番兵法を用いてさらに高速化したプログラムも紹介する。
2.シェルソートのプログラミング例1 (4重ループで実現) | |
最初に模式図をそのまま実現する方法を示す。要素数nの配列x[]について,先頭要素番号headで始まるgapごと離れた要素グループに対して挿入ソートを行う関数を作っておく。例えばgap=4でheadは0,1,2,3の4つを行うと「1」に示した「4-ソート」を実行したことになる。すなわち「k-ソート」では,gap=kとし,head=0からgap-1まで変化させながらループさせることになる。そしてこの「headに関するループ」について,gapを数列に従って減少変化させながらgap=1になるまでループさせる2重ループになる。
次に示すのが要素数nの配列x[]について,先頭要素番号headで始まるgapごと離れた要素グループに対して挿入ソートを行う関数である。通常の挿入ソートを手直しして作ることができる。
List 1
要素数nの配列x[]について,先頭要素番号headで始まるgapごと離れた要素グループに対して挿入ソートを行う関数 |
void gapinsertionsort(int x[], int num,
int head, int gap) |
もし,番兵法を導入して,改良を加えると次のようになる。
List 2
要素数nの配列x[]について,先頭要素番号headで始まるgapごと離れた要素グループに対して挿入ソートを行う関数 |
void gapinsertionsort_withSentinel2(int
x[], int num, int head, int gap) |
List 1,list
2の挿入ソートを呼び出すのが,シェルソート本体となる。
最初にgapの数列を1から順に作り,num/9を超える前で止める。gapを3で割りながら,1<=gapの範囲で繰り返しを行う。
このループの内部で,先頭要素番号を表すheadを0からhead<gapまで繰り返している。
List 3
シェルソート関数 |
void shellsort_mainloop(int x[], int
num) |
3.シェルソートのプログラミング例2 (3重ループで実現) | |
「2」で示したシェルソートは4重ループになっていた。ここではよくつかわれている3重ループで記述する方法を取り上げるが,移動の手間は4重ループプログラムとほとんど変わらない。
3重ループのうち,内側の2重ループは,挿入ソートと同様だが,挿入ソートが隣同士の比較により,要素を移動していたところを,gap離れた要素同士の比較により,要素を移動しているところが異なるところである。最外側のループはgapを数列に従って変化させている。
この記述でも,「1」に示したグループごとのソートの実行が確保されていることがわかる。
List 4
シェルソート |
void shellsort(int x[], int
num) |
シェルソートに対しても番兵法を適用できる。しかし,毎回,グループの先頭要素番号を計算する必要があるため,4重ループ記述の方がスマートかもしれない。
List 5
シェルソート関数 番兵法 |
void shellsort_withSentinel2(int x[],
int num) |
4.シェルソートのプログラミング例 | |
シェルソート関数を用いたプログラミング例を次に示す。挿入ソートも参考のため残してある。
main中のソート関数を1つだけ残すようにすればよい。
List 4 シェルソートプログラミング例 |
#include <stdio.h> #define SIZE 1000 int data[SIZE]; void printData(int x[], int
num) void generateData(int x[], int
num) void insertionsort(int x[], int
num) void shellsort(int x[], int
num) void insertionsort_withSentinel(int
x[], int num) void shellsort_withSentinel(int x[],
int num) void insertionsort_withSentinel2(int
x[], int num) void shellsort_withSentinel2(int x[],
int num) int main() |