電波ビーチ

☆(ゝω・)v

AOJ ALDS1_6_C Quick Sort

ソートアルゴリズムの実装自体やったことないぜ!!!!!!!!!メリークリスマスイヴ!!!!!!!!!

n 枚のカードの列を整列します。1枚のカードは絵柄(S, H, C, またはD)と数字のペアで構成されています。これらを以下の疑似コードに基づくクイックソートで数字に関して昇順に整列するプログラムを作成してください。partition は ALDS1_6_B の疑似コードに基づくものとします。

また、与えられた入力に対して安定な出力を行っているかを報告してください。ここでは、同じ数字を持つカードが複数ある場合、それらが入力で与えられた順序であらわれる出力を「安定な出力」とします。

安定な出力かどうか とは

クイックソートは与えられた配列の要素同士を交換するので安定でない配列ということらしい。安定な出力をするソートにはマージソートがある。

クイックソートマージソートを実装してそれぞれに配列を投げて得られた結果が同じならば安定、異なれば非安定ということにする。

マージソート

分割統治してソートする感じ。半分ずつに分割する部分とそれを統合(merge)する部分がある

  • 半分ずつに分ける
  • 分けたうちの左側と右側を比較
    • 先頭から比較していって小さい方を選択
  • 比較してできた結果からさらに大きい単位で比較

ソート済のLとRをみたときにそれぞれ上の処理をやるとできた結果Aは全体でソートされている。これを小さい単位(2数の比較)から大きくしていく感じ

def merge(a, l, m, r)
  l_arr = a[l, m-l] << 10000000000000000000
  l_i = 0
  r_arr = a[m, r-m] << 10000000000000000000
  r_i = 0

  (l...r).each do |idx|
    # 小さい方から入れていく
    if l_arr[l_i] <= r_arr[r_i]
      a[idx] = l_arr[l_i]
      l_i+=1
    else
      a[idx] = r_arr[r_i]
      r_i+=1
    end
  end
end

def merge_sort(a, left, right)
  if 1 < right-left
    mid = (right+left)/2
    merge_sort(a, left, mid)
    merge_sort(a, mid, right)
    merge(a, left, mid, right)
  end
end

これのmergeメソッドのとこが統合に当たる。あるいは配列ごと作ってぶちこむ手もある。LLならこっちのが簡単そう*1。どっちもやってるこたぁおんなじ

def merge(left, right)
  ret = []
  loop do
    break if left.size.zero? && right.size.zero?
    # 先頭の小さい方から入れていく
    if left.empty? || (!right.empty? && right.first <= left.first)
      ret << right.shift
    elsif right.empty? || (!left.empty? && left.first < right.first)
      ret << left.shift
    else
      p "fuck"
      exit
    end
  end 
  ret
end

def merge_sort(a)
  return a if a.size == 1
  mid = a.size/2
  left = a[0, mid]
  right = a[mid, mid+1]
  left = merge_sort(left)
  right = merge_sort(right)
  return merge(left, right)
end

クイックソート

これも分割統治。配列から適当なpivotを決めて、pivotより左側に「pivotより小さい要素」、右側に「pivotより大きい要素」がくるようにする。この操作をpartitionと呼ばうらしい。partitionによって配列の更新と、更新後のpivotのインデックスを返すようにすれば、pivotより小さい部分の配列と大きい部分の配列に分割できるのでそれらに対して同じように処理していくのだ。partitionの部分はなんか説明しにくいので見てくれ

def partition(a, l, r)
  pivot = a[r-1]
  j = l-1
  (l...r).each do |i|
    if a[i] <= pivot
      j+=1
      a[j], a[i] = a[i], a[j]
    end
  end
  return j
end

def quick_sort(a, l, r)
  if 1 < r-l
    q = partition(a, l, r)
    quick_sort(a, l, q)
    quick_sort(a, q+1, r)
  end
end

だいたいの言語のsort関数なんかはサイズが小さいときはマージソートで大きいときはクイックソート使うらしいみたいなのを聞いたことがある。知らんけど

実装

入力される要素に合わせて上で書いたやつを書き換えたりなんかしたり

def partition(a, l, r)
  pivot = a[r-1][1]
  j = l-1
  (l...r).each do |i|
    if a[i][1] <= pivot
      j+=1
      a[j], a[i] = a[i], a[j]
    end
  end
  return j
end

def quick_sort(a, l, r)
  if 1 < r-l
    q = partition(a, l, r)
    quick_sort(a, l, q)
    quick_sort(a, q+1, r)
  end
end


def merge(a, l, m, r)
  l_arr = a[l, m-l] << ['', 10000000000000000000]
  l_i = 0
  r_arr = a[m, r-m] << ['', 10000000000000000000]
  r_i = 0
  
  (l...r).each do |idx|
    # 小さい方から入れていく
    if l_arr[l_i][1] <= r_arr[r_i][1]
      a[idx] = l_arr[l_i]
      l_i+=1
    else
      a[idx] = r_arr[r_i]
      r_i+=1
    end
  end
end

def merge_sort(a, left, right)
  if 1 < right-left
    mid = (right+left)/2
    merge_sort(a, left, mid)
    merge_sort(a, mid, right)
    merge(a, left, mid, right)
  end
end

n = gets.to_i
a = []
n.times do
  a << gets.chomp.split.map{|x| x.match?(/\d/) ? x.to_i : x}
end
b = a.clone
quick_sort(a, 0, n)
merge_sort(b, 0, n)

puts a == b ? "Stable" : "Not stable"
a.each do |av|
  puts "#{av[0]} #{av[1]}"
end

はい よくできました えらいね。クリスマスプレゼントは職がいいです

*1:LLなら軽率に配列を作ったりぶっ壊すのが楽という雑魚認識