ソート

ソート (英: sort) は、データの集合を一定の規則に従って並べること。日本語では整列（せいれつ）、並べ替え（ならべかえ）、分類（ぶんるい）などと訳される。 主に配列や連結リストのような、リストデータ構造に分類されるコレクション（コンテナ）に格納されている要素データを、全順序関係によって並べ替えることを指す。また、単に「ソート」といった場合、値の小さい方から大きい方へ順に並べる昇順（しょうじゅん、英: ascending order）を指すことが多い。その反対に値を大きい方から小さい方へ順に並べることを降順（こうじゅん、英: descending order）という。 対象となるコレクションのデータ構造や必要とされる出力、また時間的コストと空間的コストの兼ね合いによって、ソートに使われるアルゴリズムは異なる。 効率的なソートは、ソート済みのデータを必要とする他のアルゴリズム（探索やマージ）の最適化にとっても重要である。また、ソートされたデータの方が人間にとっても読みやすい。形式的には、その出力は以下の2つの条件を満たさなければならない。 設定された順序（昇順または降順）に対して、逆になるような順序の出力があってはならない。 出力は入力の並べ替えでなければならない。 情報工学や計算機科学の黎明期から、ソートは単純な問題でありながら効率的に解くことが難しく、そのためもあって盛んに研究されてきた。例えばバブルソートについては、早くも1956年には解析が行われている。しかし、21世紀初にも新たなソートアルゴリズムが発明されている（例えば、Timsort（英語版）は2002年に、図書館ソートは2004年に発表された）。すでに考案されているソートにも様々なアルゴリズムがあり、またアルゴリズムという概念を学習するのに最適なので、情報工学や計算機科学での入門的題材として広く親しまれている。例えば、分割統治法、データ構造、乱択アルゴリズム、計算量解析、O記法、時間と空間のトレードオフ、下限などが含まれる。 グラフィカルユーザインタフェース (GUI) においてよく使われるウィジェットのひとつとしてリストビュー (list view) が挙げられるが、各レコードのフィールドを表す任意のカラムについてリスト中のアイテムを昇順／降順ソートできるようになっていることが多い。 計算機科学では、ソートアルゴリズムを次のように分類する。 同じ値に関して、ソート前の順序がソート後も維持されているソートを安定ソートという。 安定ソートでないソートであっても、ソート条件に元の順序を含めることで必ず安定ソートにすることが可能である。しかしながら、別途元の順序を記憶する領域が必要になることから、内部ソートであっても外部ソートになってしまう。（→#内部ソートと外部ソート） ソートされるデータの格納領域を変更して処理を進めていくIn-placeのソートを内部ソートという。クイックソートのような再帰を利用するアルゴリズムは、再帰のために O(log n) の領域を必要とすることからIn-placeであるか否かは議論が分かれるところであるが、これも内部ソートに含めるのが一般的である。このことから内部ソートは、ソートされるデータの格納域以外には O(1) か O(log n) の領域しか必要としない。 逆に、ソートされるデータの格納領域以外に O(n) 以上の一時的な記憶領域が必要であるソートを外部ソートという。 メモリ使用量（およびその他の計算資源の使用量）による分類である。すべての内部ソートは、インデックスや参照、複製を作成して処理することで事実上の外部ソートにすることができる。アルゴリズムとしての本質ではないので一般的には無視されるが、高速化や柔軟な処理のために冗長な記憶領域を使用する場合がある。例えば、非安定ソートアルゴリズムで安定ソートを実現する場合など。（→#安定ソート） 個々の項目を比較演算で大小判定することを基本とするソートを比較ソートという。 比較ソートには#比較ソートの理論限界が存在する。 入力されるリストの項目数 n に基づいた計算量による分類。典型的なソートアルゴリズムでは、最善で O(n log n) 、最悪で O(n2) である。理想は O(n) である。 比較ソートでは、一般的な（ランダムに並んだ）データの並べ替えに対しては少なくとも O(n log n) の比較回数が必要である。（→#比較ソートの理論限界） 汎用手法による分類。挿入、交換、選択、マージなどがある。交換ソートにはバブルソートやシェーカーソートやコムソートなどが含まれる。選択ソートにはヒープソートなどが含まれる。 再帰が必須、不可能、どちらでも可能、という分類。実装上の都合で再帰に関わる制限がある場合に注目される。 配列に格納されたn個のデータをソートする場合について、各アルゴリズムの性能を示す。 計算時間の表記に用いている記号 O（オー）については、ランダウの記号を参照。 以下の表で、n はソートすべきデータ要素数である。平均実行時間と最悪実行時間は時間計算量を示している。このとき、ソートキーの長さは一定と仮定しており、比較や交換といった操作は定数時間で行われるとする。メモリ使用量は、入力データの格納域以外に必要となる領域を示している。これらは、いずれも比較ソートである。 次の表は、比較ソート以外のソートアルゴリズムの一覧である。そのため、下限が O(n log n) で制限されない。k はキーの長さ、s は実装で使われるチャンクのサイズである。これらの一部は、キーが十分に長く、各要素のキーが重複しないことを前提としている。すなわち、n << 2k を仮定している（<< は「十分小さい」）。 次の表は、あまりにも性能が悪いので通常は用いられないソートアルゴリズム、および特別なハードウェアが必要なソートアルゴリズムの一覧である。 ソートを行う際、要素間の順序の決定を2要素の大小関係の比較処理のみを用いて行うことにする。この時、 n {\displaystyle n} 要素からなるリストをソートするアルゴリズムの最悪比較回数は Ω ( n log ⁡ n ) {\displaystyle \Omega (n\log n)} 回となる。すなわち、どんな比較ソートアルゴリズムであっても入力によっては（一般的な入力に対しては）漸近的に n log ⁡ n {\displaystyle n\log n} 回の比較が必要となる。 この理由を説明する。ある比較ソートアルゴリズムが与えられた際、様々な入力に対する処理の手順を二分決定木として表すことができる。内部ノードは2要素の比較処理を表し、内部ノードから子ノードへの2本の枝は比較結果に応じた処理を表す。また、葉は処理の終了を表す。この時、木の高さが最悪比較回数となる。 n {\displaystyle n} 要素からなるリストが入力される時、どのような二分決定木を作ったとしても、木の高さが Ω ( n log ⁡ n ) {\displaystyle \Omega (n\log n)} になることを示す。二分決定木の高さを m {\displaystyle m} とした時、木の形によらず、葉の数は 2 m {\displaystyle 2^{m}} 以下になる。 n {\displaystyle n} 要素からなるリストの（添字の）置換は n ! {\displaystyle n!} 個存在するが、任意の入力に対してアルゴリズムが正しい出力をするためには、 n ! {\displaystyle n!} 個の異なるリストを入力した際、それらがすべて異なる葉に到達できるようにしなければならない。よって、葉の数は n ! {\displaystyle n!} 以上でなければならない。以上より、 n ! ≤ 2 m {\displaystyle n!\leq 2^{m}} である。スターリングの公式より ( n e ) n < n ! {\displaystyle \left({\frac {n}{e}}\right)^{n}<n!} なので、 ( n e ) n < 2 m {\displaystyle \left({\frac {n}{e}}\right)^{n}<2^{m}} 。よって、 m > log 2 ⁡ ( n e ) n = n ( log 2 ⁡ n − log 2 ⁡ e ) {\displaystyle m>\log _{2}\left({\frac {n}{e}}\right)^{n}=n(\log _{2}n-\log _{2}e)} 。よって m = Ω ( n log ⁡ n ) {\displaystyle m=\Omega (n\log n)} 。 以上より、任意の比較ソートアルゴリズムの最悪計算量は Ω ( n log ⁡ n ) {\displaystyle \Omega (n\log n)} となる。すなわち、比較ソートアルゴリズムの最悪計算量の下界は漸近的には n log ⁡ n {\displaystyle n\log n} となる。 マージソート、ヒープソートなどの比較ソートアルゴリズムの最悪計算量は O ( n log ⁡ n ) {\displaystyle O(n\log n)} であるため、これらのアルゴリズムの最悪計算量は上記の下界と漸近的に一致していると言える。つまり、これらのアルゴリズムの最悪計算量は漸近的に最適である。 ソート対象の配列が主記憶を使い切るような（あるいは越えるような）大きさであった場合、より低速な補助記憶装置が使われるので、アルゴリズムのメモリ使用パターンが重要となる。そのような状況では、主記憶上ですべてソートできることを前提としたアルゴリズムは効率が極端に悪化する可能性がある。このような状況では、比較演算回数はあまり重要ではなくなり、ディスクとのメモリ領域のスワップ回数が重要となる。したがって、なるべくスワップ回数を増やさないようにするために、配列全体を走査する回数や比較の局所性が比較回数よりも重要となる。 例えば、再帰型のクイックソートは主記憶上では性能が良いが、ソート対象の配列が主記憶に収まらない場合はスワップが頻繁に発生して、性能が極端に低下する。したがって、そのような場合は比較回数が多くても他のアルゴリズムを使った方がよい。 対策の一つとして、ソート対象の配列の要素が（関係データベースのような）複雑なレコードだった場合、その配列をそのままソートするのではなく、比較的小さいインデックスを生成して、インデックスの配列をソートするという方法がある。インデックスをソートすれば、元の配列のソートは一回の走査で可能であるが、インデックス経由でアクセスするだけならそれをする必要もない。インデックスは元の配列のレコードよりも小さいので、メモリに収まる可能性が高くなり、スワップ問題を削減することができる。この方式を「タグソート（tag sort）」などと呼ぶこともある。 別の技法として、2つのアルゴリズムを組み合わせて、それぞれの利点を利用する方法がある。例えば、配列をチャンクに分割して個々のチャンクが主記憶上でソートできる大きさにする。チャンク内のソートはメモリ上で効率的に動作するソートアルゴリズムを使い、その結果をマージソートでマージする。これは、元の配列を単純にマージソートでソートするよりも効率が悪いが、全体をクイックソートでソートするよりもメモリ使用量が少なくてすむ。 これらの技法を組み合わせることも可能である。あまりにも巨大なデータをソートする場合、インデックスのソートにも複数のアルゴリズムを組み合わせて仮想記憶の性質に合うよう設計する必要がある。