シェルソートで配列を並び替える
シェルソートを使用する
シェルソート (shell sort) は、間隔(ギャップ) を取った部分列に対して挿入ソートを繰り返すことで、全体を整列させる比較ソートである。ギャップが大きいうちは離れた要素同士の交換で「粗く」並びを整え、ギャップを徐々に小さくしていくことで、最終的にギャップ 1 のとき通常の挿入ソートとして収束する。
- ギャップ列の決定: 例として初期ギャップを
⌊n/2⌋とし、各フェーズで半分に縮小して最後に 1 にする(古典的な増分列)。実装では Knuth 列など別の増分列を選ぶことも多い。 - ギャップごとの挿入ソート: 現在のギャップ
gについて、インデックスg, g+1, …, n-1を順に見ていき、各位置の要素を左へ「g離れた」要素との比較によって挿入位置へ運ぶ(要素が逆順なら交換し、j >= gになるまで繰り返す)。 - 繰り返し: ギャップが 1 になるまで手順 2 を繰り返す。ギャップ 1 のフェーズは通常の挿入ソートと同じになる。
procedure shell_sort(A)
n = length(A)
gap = floor(n / 2)
while gap > 0
for i from gap to n - 1
j = i
while j >= gap and A[j - gap] > A[j]
swap(A[j], A[j - gap])
j = j - gap
gap = floor(gap / 2)
増分列によって最悪時間計算量は異なる。上記の「半分に縮小する」列では最悪 O(n²) と報告されているが、バブルソートのような単純な隣接交換のみの走査より早くなることが多い。
ギャップが大きいフェーズで要素が大きく動けるため、ギャップ 1 の段階での逆転数が抑えられやすいという直観がある。空間計算量は O(1) の追加領域で実装できる インプレース ソートである。安定ではない(等しいキーの相対順序が保証されない)ことが一般的である。
バブルソートのように隣接要素だけを見るより早くなることがあり、実装もインプレースで比較的単純である。一方でクイックソートやマージソートと比べたときの平均的な速度や最悪ケースの見通しは増分列の選び方に依存するため、本番用途では言語標準のソート実装を利用するのが無難である。