Sorting by swap distance
ゲーム
おはじきを使ったゲームをやっていた。
おはじきに 1 から 100 までの数字を書き缶に入れておく。
缶を振ってから10個とりだして並べる。
両手の指を使っておはじきを入れ替えながら数字の順に並べる。
一応競争なんだけど一斉にやったりしているわけではないので好き勝手に遊んでいた。
すぐに効率的なやり方に気がついた。
1番小さい数字を探して1番左と入れ替える。
2番目に小さい数字を探して左から2番目と入れ替える。
要するに Selection sort だ。
他のみんなもその方法だったのだけれどもひとりだけ違っていた。
こまめに入れ替えている。Bubble sort だった。
彼は言った「これ速いよね」。
その後、競争したのかは覚えていない。
ただ覚えているのは彼がとても絵がうまかったということと、
彼の書いた絵が金賞をとって展覧会で逆さ向きに飾られていたことだけだ。
彼は正しかったのか?
話は創作なんだけれどもずっと気になっていた。
勉強して Bubble sort より効率がいい方法があることがわかった。
でも世の中的には Bubble sort の方が使われているのではないか?
例えば学校で背の順に並べるとき先生は Bubble sort を使っていたような気がする。
どうしてもっと効率のいい方法を使わないのか?
思いついた理由は2つ
- 隣り合っている方が比較しやすいから
- 列を乱したくないから(再度整列させるのが手間だから)
でも間違っていたことに最近気がついた。
『岩波講座ソフトウェア科学 3 アルゴリズムとデータ構造』に書いてあった。
ランダムアクセスできることが多くのアルゴリズムを効率よく実現する上での鍵になる
つまり、現実世界ではおはじきが手の届く範囲を超えたり、おはじきの直径が 2m だったりすると離れているものは簡単に交換できないのだ。
アルゴリズムを Swap 距離でソートする
とりあえず Bubble sort, Selection sort, Quick sort で比較してみる。
def bubble_sort(a) { final int N = a.size() for (int i = 0; i < N-1; i++) { for (int j = 1; j < N-i; j++) { if (a[j-1] > a[j]) swap(a, j-1, j) } } return a } def selection_sort(a) { final int N = a.size() for (int i = 0; i < N-1; i++) { int min = i for (int j = i+1; j < N; j++) { if (a[j] < a[min]) min = j } swap(a, i, min) } return a } def quick_sort(a) { def sort = { left, right -> if (left < right) { def pivot = a[left] int p = left for (int i = left+1; i <= right; i++) { if (a[i] < pivot) swap(a, ++p, i) } swap(a, left, p) call(left, p-1) call(p+1, right) } } sort(0, a.size()-1) return a } // total swap distance distance = 0 def swap(a, i, j) { distance += (i-j).abs() Collections.swap(a, i, j) } def algorithms = [] algorithms << this.&bubble_sort algorithms << this.&selection_sort algorithms << this.&quick_sort def result = [:].withDefault{ 0 } 1000.times { def a = [*1..100].with{ Collections.shuffle(it); it.take(10) } def sorted = a.sort(false) println "a=$a" algorithms.each { distance = 0 assert sorted == it(a.clone()) result[it.method] += distance } } result.sort{ it.value }.each { println sprintf("%15s: %d", it.key, it.value) }
結果はアルゴリズム間に距離の差がなかった。
quick_sort: 22192 selection_sort: 22310 bubble_sort: 22336
selection_sort: 22171 bubble_sort: 22443 quick_sort: 22586
bubble_sort: 22055 quick_sort: 22124 selection_sort: 22287
彼が正しかったかはまだわからないが Bubble sort は現実世界では遅くはなかった。
しかも、交換を終えた場所から次の交換位置まで移動するコストを考えると Bubble sort の方が速そうだ。
その他の現実世界向けのソート
Wikipedia には現実世界の制約を利用したソートが4つ挙げられている
- Bead sort
- Simple pancake sort
- Spaghetti (Poll) sort
- Sorting networks
Bead sort と Pancake sort に関しては Rosetta Code にプログラムでの実装が載っている
記憶にあるランダムアクセスの関連の話題
Java の Collections も RandomAccess かどうかで分岐していたような気がする。
終わりに
現実世界ではまだランダムアクセスが完成していないのでコンピュータ世界のアルゴリズムが必ずしも通用するわけではないことがわかった。
もしかすると、どこかの学校で並べ替えリレーなるものが行われていてカーソルが交換毎に先頭に戻らなければいけない場合に最適なアルゴリズムが考案されているかもしれない。
2012-02-22 追記
最終的な位置まで移動することは一緒なのでどのソートでも移動距離があまり変わらない。
微妙な違いは確率の問題で最終的な位置に移動するまでに要素を逆方向へ移動するロスが発生しているからだ。
例) [5,4,3,2,1] の場合
- Selection sort では 1と5、2と4を交換するだけなのでロスは発生しない
- Bubble sort では1回目の Bubbling のあと [4,3,2,1,5] となるので3と4が最終的な位置から遠ざかっている
例) [2,3,4,5,1] の場合
- Selection sort では1回目の Swap の後 [1,3,4,5,2] となりロスが発生する
ロスが出来る限り発生しないようにするには最終的な位置を予め知っている必要があるのだろうか?
*1:vectorsort なるものの実装が検索しても見つからない