Last Updated: 2/6/2024, 5:44:57 AM
# クイックソート
# 1. 概要
クイックソートは名前の通り、高速にソートを行えます。
# 2. コード
コピペで実行できます。
# 簡単なもの
クイックソートは、基準になる値を1つ決め、 それより大きい値のグループと小さい値のグループに分けます。 そしてこの作業を繰り返し、ソートされたリストを得ます。
#
# 対話モード >>> に
# コピペで実行できます。
#
def linked_quick_sort(lst):
if not lst:
return
#
# 1. 分割
#
smaller = []
pivot = lst.pop()
bigger = []
while lst:
e = lst.pop()
if e <= pivot:
smaller.append(e)
else:
bigger.append(e)
#
# 2. ソート
#
linked_quick_sort(smaller)
linked_quick_sort(bigger)
#
# 3. 結合
#
smaller.reverse()
while smaller:
lst.append(smaller.pop())
lst.append(pivot)
bigger.reverse()
while bigger:
lst.append(bigger.pop())
lst = [8, 3, 1, 6, 10, 4, 7, 14, 13]
linked_quick_sort(lst)
print(lst)
# [1, 3, 4, 6, 7, 8, 10, 13, 14]
Qiita の記事の方がシンプルな書き方です。
pop
, append
を使い書きました。
これは次で示す添字表記 lst[i]
と対比させるためです。
マージソート、ヒープソートでも pop
, append
を使ったものと
添字表記 lst[i]
のものを対比して書いています。
# 難しいもの
#
# 対話モード >>> に
# コピペで実行できます。
#
def quick_sort(lst):
_quick_sort(lst, 0, len(lst) - 1)
def _quick_sort(lst, begin, end):
if begin == end + 1:
return
#
# 1. 分割
#
left, right = begin, end
pivot = lst[end]
# **大事**
# pivot を番兵にしている
# これがないと right が begin を突き抜けて
# リストを一周してしまう
lst[left], lst[right] = lst[right], lst[left]
while True:
while lst[left] < pivot:
left = left + 1
while pivot < lst[right]:
right = right - 1
if right <= left:
break
lst[left], lst[right] = lst[right], lst[left]
left = left + 1
right = right - 1
#
# 2. ソート
#
_quick_sort(lst, begin, left - 1)
_quick_sort(lst, right + 1, end)
lst = [3333, 5123, 9981, 1243, 7412]
quick_sort(lst)
print(lst)
# [1243, 3333, 5123, 7412, 9981]
番兵 (opens new window) という言葉は、 頭の片隅にあってもいい言葉かもしれません。
# 過程
本当にちゃんと実装したの?って感じなので、 途中結果を表示するコードを書きました。
#
# 対話モード >>> に
# コピペで実行できます。
#
def quick_sort(lst):
_quick_sort(lst, 0, len(lst) - 1)
def _quick_sort(lst, begin, end):
if begin == end + 1:
return
#
# 1. 分割
#
left, right = begin, end
pivot = lst[end]
# **大事**
# pivot を番兵にしている
# これがないと right が begin を突き抜けて
# リストを一周してしまう
lst[left], lst[right] = lst[right], lst[left]
while True:
while lst[left] < pivot:
left = left + 1
while pivot < lst[right]:
right = right - 1
if right <= left:
break
lst[left], lst[right] = lst[right], lst[left]
left = left + 1
right = right - 1
print_progress(lst, begin, left, end)
#
# 2. ソート
#
_quick_sort(lst, begin, left - 1)
_quick_sort(lst, right + 1, end)
def print_progress(lst, *args):
print(' '.join(f'{e:2d}' for e in lst))
print(' '.join(progress(lst, *args)))
print()
def progress(lst, begin, pivot_index, end):
n = len(lst)
prog = []
prog = prog + [' '] * begin
prog = prog + ['<<'] * (pivot_index - begin)
prog = prog + ['pv']
prog = prog + ['>>'] * (end - pivot_index)
prog = prog + [' '] * (n - end - 1)
return prog
import random
lst = [random.randint(0, 99) for i in range(16)]
quick_sort(lst)
print(lst)
実行結果は以下のようになります。 ランダムな整数 16 個を作成して途中結果を表示します。 コメントは手書きで付け加えました。
# 最初のリストです。
# 基準となる値 pivot はリストの末尾が選ばれます。
# 50 が pivot になります。
14 33 40 71 75 80 48 27 93 3 36 77 19 70 27 50
# 50 を基準にして
# 小さい数は左に
# 大きい数は右に分かれました。
# 次は 27 が pivot になります。
14 33 40 48 27 3 36 19 27 50 70 77 93 80 75 71
<< << << << << << << << << pv >> >> >> >> >> >>
# 27 を基準にして
# 小さい数は左に
# 大きい数は右に分かれました。
# 次は 19 が pivot になります。
14 3 19 27 36 27 48 40 33 50 70 77 93 80 75 71
<< << << pv >> >> >> >> >>
# 19 を基準にして
# 小さい数は左に
# 大きい数は右に分かれました。
# 次は 3 が pivot になります。
14 3 19 27 36 27 48 40 33 50 70 77 93 80 75 71
<< << pv
# 19 を基準にして
# 小さい数は左に
# 大きい数は右に分かれました。
# 左には数はないので右に移ります。
3 14 19 27 36 27 48 40 33 50 70 77 93 80 75 71
pv >>
# 14 は1つだけ
3 14 19 27 36 27 48 40 33 50 70 77 93 80 75 71
pv
# 再帰を遡り
# 27 が pivot であった時の
# 右側をまた2つに分けていきます。
3 14 19 27 27 33 40 48 36 50 70 77 93 80 75 71
<< pv >> >> >>
# 以下繰り返しです。
3 14 19 27 27 33 40 48 36 50 70 77 93 80 75 71
pv
3 14 19 27 27 33 36 48 40 50 70 77 93 80 75 71
pv >> >>
3 14 19 27 27 33 36 40 48 50 70 77 93 80 75 71
pv >>
3 14 19 27 27 33 36 40 48 50 70 77 93 80 75 71
pv
3 14 19 27 27 33 36 40 48 50 70 71 75 80 93 77
<< pv >> >> >> >>
3 14 19 27 27 33 36 40 48 50 70 71 75 80 93 77
pv
3 14 19 27 27 33 36 40 48 50 70 71 75 77 93 80
<< pv >> >>
3 14 19 27 27 33 36 40 48 50 70 71 75 77 93 80
pv
3 14 19 27 27 33 36 40 48 50 70 71 75 77 80 93
pv >>
3 14 19 27 27 33 36 40 48 50 70 71 75 77 80 93
pv
3 14 19 27 27 33 36 40 48 50 70 71 75 77 80 93
# おわりに
クイックソートは一見して難しそうですが、 大きいものと小さいものに分けて、 最終的に分けられたものが1つだけになったら、 切り戻ってきます。 二分探索木をイメージがあると理解しやすいかなと思います。
読めてませんが、鬼のようにとてもよくまとまっています。
ここまでくると高速でメモリを消費しないヒープソートに軍配が上がりそうですが、 ヒープソートは安定ソートではないそうです。 安定ソートという言葉があるので、 ここまで来られたら、頭の片隅に置いておいてもいいかもしれません。
高速かつ安定なアルゴリズム TimSort とは、 どのようなものなのでしょうか。