Oyster Harbor

アルゴリズムとデータ構造を淡々と紹介していくブログ。

バブルソート(Bubble sort)

バブルソートとは

バブルソート
種別交換ソート
内部ソートYES
安定YES
オンラインNO
最悪計算時間
平均計算時間
最良計算時間
空間計算量

バブルソートはソートの中でもアルゴリズムが単純なもののひとつです。 隣り合うデータを比較・交換していくだけなので、プログラムも簡単です。 データを2つ選び、比較して大小関係が正しくなるように交換する、という操作を繰り返す交換ソートのひとつです。

バブルソートは後述するようにソートの中ではかなり遅いほうなのですが, 実はバブルソートの派生アルゴリズムであるコムソートや奇偶転置ソートなどは非常に高速です。 他のアルゴリズムを知る上でも、バブルソートは重要なソートアルゴリズムとなっています。

アルゴリズム

隣り合う要素を比較して、順序が逆であれば入れ替えるだけです。 もう少し詳しく動作を見るために、次のようなデータを考えます。
957823146
このデータを昇順で並べることを考えます。 まずは1番めと2番めを比較します。9 > 5なので順番が逆です。 そこでこの2つのデータを入れ替えます。 以降は交換したデータを太字で表しています。
597823146
次は2番めと3番めを比較します。また順番がおかしいので入れ替えます。
579823146
次は3番めと4番めを比較…という処理を繰り返していくと次のようになります。
578923146
578293146
578239146
578231946
578231496
578231469

8番めと9番めのデータを比較・交換しました。 このデータを見ると、"9"がどんどん右端へ移動していっていることがわかると思います。 水中の泡が水面へ上昇していく様子に似ているのでバブルソートの名前がついているようです。

ここまでの処理を行うと、必ず「最大の値が右端へ移動する」ことがわかります。 ということは、もう右端は処理の対象にする必要がありませんね? 図では確定したデータ(比較交換の対象にしなくて良いデータ)は灰色で表示しています。

そこで、次は1番めから8番めのデータのみを対象として同じ操作を繰り返します。 バブルソートは、最大(降順の場合は最小)のデータを端へ移動するという操作を繰り返すことによってソートを行うアルゴリズムです。

1番めから8番めのデータに対して比較・交換を行うと次のようになります。

578231469
578231469
572831469
572381469
572318469
572314869
572314689

これで2番めに大きい数である8の位置が確定しました。 このとき、8番めと9番めのデータは比較していないことに注意してください。 9番めのデータは既に確定しているので、比較しても意味がありません。

あとは同じような操作を繰り返すことによって並び替えを行うことが出来ます。 まずは1番めから7番めのデータを処理し、次に1番めから6番めのデータを処理…という操作を繰り返していきます。

最後は1番めから2番めの範囲で比較・交換を行って終了です。

523146789
523146789
231456789
231456789
213456789
123456789
123456789

高速化テクニック

次のようなデータをバブルソートでソートすることを考えてみましょう。

912345678

まずは"9"が一番右端へ移動することはもうおわかりですね?

123456789

これで右端のデータが"9"に確定しました。 次に1番めから8番めのデータに対して同じ処理を繰り返します。 ところが、どのデータを比較しても1度も交換が起きないことが分かります。

123456789

もしデータの交換が一度も行われなかったら、データが既に整列済みであると決定できます。 そのため、データが交換されたかどうかをチェックすることによって無駄な処理を省くことが可能です。 簡単な改良ですが、ソート対象のデータの性質によっては非常に高速になります。

このように、「ほとんどソート済み」あるいは「ソート済み」のデータに対して 高速に処理できるという性質は、場合によってはとても重要になります。 たとえば、既存のソート済みデータに対して新しいデータを追加したいときなどに効果を発揮します。 また、この性質を利用して他のソートアルゴリズムを改良することも出来ます。 このあたりについては、また別の記事で触れたいと思います。

時間計算量

バブルソートの時間計算量を求めてみましょう。 ここでは、データの個数を n とします。100個のデータをソートするのなら、 n=100 です。 話がややこしくなるので、先ほどの高速化テクニックは使わないことにします (先のテクニックを使う場合は、最速の場合 O(n) でソートできます)。

まず一つのデータの位置を確定するのに、 (n-1) 回の比較が必要です。 次は比較回数が1回少なくなるので、 (n-2) 回の比較が必要です。 これの繰り返しが平均時間計算量になります。

まず、 (n-1) + (n-2) + ... + (n-(n-2)) + (n-(n-1)) = (n-1) + (n-2) +...+ 2 + 1 これは単に1からn-1までの総和なので、 \\sum_{i=1}^{n-1} i であることがわかります。 これは等比数列の和を考えると次のように表せますね。 = \\frac{n(n - 1)}{2} = \\frac{n^2-n}{2} 以上から、 O(n^2) であることがわかります。

空間計算量

空間計算量は内部ソートなので、外部メモリを使わずにソートできます。 必要なメモリはデータn個を格納するための領域のみです。 つまり、空間計算量は O(n) であることがわかります。

まとめ

バブルソートは実際にはほとんど使われません。 強いて言えば実装が簡単なことがメリットです。 しかし挿入ソートはバブルソートと同じくらい単純なアルゴリズムなのに、挿入ソートの方が少し速いので、あくまでもソートアルゴリズムのお手本程度に考えておいてよいでしょう。

ただし、最初の方にも触れましたが、バブルソートの改良アルゴリズムがいくつかあり、そちらは非常に高速にソートできます。 特にコームソート(コムソート, Comb sort)は、バブルソートを少し改良したアルゴリズムですが時間計算量が O(n log n) と大幅な改善を実現しています。

ソースコード

C言語

表示/非表示

Java

表示/非表示
記事検索
カテゴリ別アーカイブ
プロフィール

kaz

月別アーカイブ
  • ライブドアブログ
Oyster Harbor