クイックソート
クイックソート (quicksort) は、1960年にアントニー・ホーアが開発したソートのアルゴリズム。分割統治法の一種。
出典: フリー百科事典『ウィキペディア(Wikipedia)』
良く使われるソートのアルゴリズムです。
分割統治法によるアルゴリズムで、「配列の中から ‘pivot’ と呼ばれる適当な数を選び、 ‘pivot’ を基準にそれより小さい数を ‘pivot’ より前に、大きい数を ‘pivot’ より後に並べる」という処理を行うことで、当然ながら ‘pivot’ より前にはより小さい数が ‘pivot’ より後には大きい数が並びます。
次に ‘pivot’ を基準に配列を2分割し、同様の処理を行います。
最終的に、配列のサイズが0か1になるまで再帰的に処理することで、ソートされます。
下記は、 配列の最後 を ‘pivot’ をとする場合のクイックソートの例です。
バブルソートなど\( O ( N^2) \)の計算量のソートアルゴリズムに対し、クイックソートの平均計算量は\( O ( \log N ) \) で効率的なソートアルゴリズムになります。最悪計算量は \( O ( \log N^2 ) \) になりますが、乱択アルゴリズムを用いることで、 最悪の状態をほぼ避けることができます。
内部ソートですが、安定ソートではありません。
クイックソートの計算量の証明
下記動画で 50:00ごろからクイックソート、 1:08:00ごろから乱択アルゴリズムを用いるクイックソートの計算量について解説しています。
クイックソートとマージソートの比較
| クイックソート | マージソート | |
|---|---|---|
| 配列の分割 | 2等分とは限らない | 2等分される |
| 最悪計算量 | \( O ( N^2) \) | \( O ( N \log N ) \) |
| 内部ソート | 内部ソートである | 内部ソートではない |
| 安定ソート | 安定ソートではない | 安定ソートである |
| 型による相性 | 配列 | 連結リスト |
| 参照局所性 | 良い | 悪い |
Python での実装
Python で実装します。
‘pivot’ はランダムに選ぶことにします。
import random
def quick_sort(array, low_index, high_index):
if low_index < high_index:
# pivot より小さい要素を左側に、pivot より大きい要素を右側に持って行って分割。
pivot_index = partition(array, low_index, high_index)
# 分割した左側を再帰的にソート
quick_sort(array, low_index, pivot_index -1)
# 分割した右側を再帰的にソート
quick_sort(array, pivot_index+1, high_index)
return array
def partition(array, low_index, high_index):
# pivot をランダムに選ぶ
pivot_index = low_index + random.randrange(high_index - low_index + 1)
# pivot は配列の一番右側に配置し、array[high_index] が pivot になる。
array[high_index], array[pivot_index] = array[pivot_index], array[high_index]
# pivot と他の要素を比較
for i in range(low_index, high_index):
# ある要素がpivotより小さい場合
if array[i] < array[high_index]:
# その要素は左端と交換
array[i], array[low_index] = array[low_index], array[i]
# 左端は pivot より小さい要素として固定され、ポインタを右に一つ進める。
low_index += 1
# pivot を pivot より小さい要素の右側にすることで、array[low_index] が pivot になる。
array[low_index], array[high_index] = array[high_index], array[low_index]
# 再帰のため pivot のインデックスを返す。
return low_index
if __name__ == '__main__':
random.seed(1)
array = [random.randrange(100) for i in range(10)]
print('未ソート', array)
# 未ソート [17, 72, 97, 8, 32, 15, 63, 97, 57, 60]
print('ソート済み', quick_sort(array, 0, len(array)-1))
# ソート済み [8, 15, 17, 32, 57, 60, 63, 72, 97, 97]