🐌

実用度の低いソートアルゴリズム

2023/08/12に公開

ボゴソート - 運を天に任せる

https://ja.wikipedia.org/wiki/ボゴソート

def sort(a)
  a.tap do |a|
    until a.each_cons(2).all? { |a, b| a <= b }
      a.shuffle!
    end
  end
end
a = 10.times.collect { rand(10) }  # => [8, 2, 5, 6, 5, 1, 3, 5, 2, 2]
sort(a)                            # => [1, 2, 2, 2, 3, 5, 5, 5, 6, 8]

ググると「シャッフルしてから確認する」コードを紹介している記事がいくつもでてくるのはいただけない。野暮なことを言うと順序が逆で「ソートされていなければシャッフルする」としないといけない。そうしないと天和を見逃してしまう。

重複なし要素数Nと時間の関係

N 時間 シャッフル回数
0 0秒 0
1 0秒 0
2 0秒 0
3 0秒 1
4 0秒 2
5 0秒 357
6 0秒 629
7 0秒 2510
8 0秒 36579
9 0秒 678770
10 1秒 1979481
11 11秒 15717718
12 1分11秒 99328631
13 8時間4分42秒 15597635494

13 からが厳しい。

ストゥージソート - 見掛け倒しな遅さを発揮?

https://ja.wikipedia.org/wiki/ストゥージソート

def sort(a, l = 0, r = a.size.pred)
  if a[r] < a[l]                # 最後 > 先頭 なら
    a[l], a[r] = a[r], a[l]     # 入れ替え
  end
  n = r - l + 1                 # 部分列のサイズ
  if n < 3
    return
  end
  t = n / 3                     # t は全体の 1/3
  sort(a, l, r - t)             # 先頭 2/3 をソート
  sort(a, l + t, r)             # 末尾 2/3 をソート
  sort(a, l, r - t)             # 先頭 2/3 をソート (再度)
  a
end
a = 10.times.collect { rand(10) }  # => [2, 9, 4, 5, 3, 3, 6, 1, 1, 0]
sort(a)                            # => [0, 1, 1, 2, 3, 3, 4, 5, 6, 9]

遅いだけだとあまりネタという感じがしないのだけど速そうなことをしてそうに見えて実はめちゃくちゃ遅いというところが評価ポイントなのかもしれない。

スリープソート - 値に比例して待つ

def sort(a)
  [].tap do |values|
    a.collect { |e|
      Thread.start do
        sleep e * 0.001
        values << e
      end
    }.each(&:join)
  end
end
t = Time.now
a = 10.times.collect { rand(10) }  # => [7, 9, 9, 0, 6, 3, 7, 5, 8, 8]
sort(a)                            # => [0, 3, 5, 6, 7, 7, 8, 8, 9, 9]
Time.now - t                       # => 0.011513

値をそのまま秒換算の sleep に渡すとかなり待たされるので単位を小さくした方がいい。ただ、小さくしすぎると順番が少しずれたりする。

単なる配列要素のソートには使いづらいが、各スレッドが持つ情報を元にスレッド間の優先度を調整するアイデアは Puma の高速化に寄与したらしい。

https://www.speedshop.co/2020/09/17/we-made-puma-faster-with-sleep-sort.html

ビーズソート - 速度は環境次第

縦横にビーズを並べて垂直落下させると半分の山の形ができるので頂上から幅を数える方法。Wikipedia の図がわかりやすい。

def sort(a)
  width = a.max                 # 横幅
  height = a.size               # 縦幅

  field = {}                    # 二次元領域

  # ビーズを配置する
  a.each.with_index do |count, y|
    count.times do |x|
      field[[x, y]] = true
    end
  end

  # 落下させる
  loop do
    fall_down = false
    width.times.each do |x|
      (height - 2).downto(0) do |y| # 重ならないように地面に近い方から
        from = [x, y]               # ここに
        if field[from]              # ビーズがあって
          to = [x, y.next]          # 移動先が
          if !field[to]             # 空いていたら
            field[to] = field[from] # 移動する
            field.delete(from)
            fall_down = true
          end
        end
      end
    end
    # すべてのビーズが接着したら終わる
    unless fall_down
      break
    end
  end

  # 上の層からビーズを数える
  height.times.collect do |y|
    width.times.count { |x| field[[x, y]] }
  end
end
a = 10.times.collect { rand(10) }  # => [1, 9, 9, 8, 9, 4, 8, 7, 7, 2]
sort(a)                            # => [1, 2, 4, 7, 7, 8, 8, 9, 9, 9]
  • 本来は特殊な装置などを用意して行う (とのこと)
  • 物理的な環境がビーズの落下速度に影響して実行速度が変わる
  • 負の値を含むソートはできない (ということになっている)
    • ビーズの個数を現実的に表せないため

Discussion