Engineering Note

プログラミングなどの技術的なメモ

クイックソート (Pythonによるアルゴリズムとデータ構造)

quick sort

本記事は、ソフトバンクパブリッシングから発行されている「定本 Cプログラマのためのアルゴリズムとデータ構造 (SOFTBANK BOOKS)」を参考にPythonアルゴリズムとデータ構造について学習していきます。

今回は、現在で一番高速な整列アルゴリズムであるクイックソート(Quick Sort)について学んでいきます。

 

 

クイックソートとは

クイックソート(Quick Sort)とは、1962年にC.A.R.Hoareによって発見されたアルゴリズムで、現在最も高速な整列アルゴリズムとして知られています。

クイックソートでは、ある基準となる値(枢軸:Pivot)を決めて、その値より小さいものは左、大きいものは右に振り分けていきます。

以下のfig1はソート前の状態で、ここでは5を枢軸としてソートをしてみます。

 

fig1. クイックソート前

fig1. クイックソート

 

以下のfig2はソートを行った後の状態です。

 

fig2. クイックソート後

fig2. クイックソート


そして、上記リストのインデックス0~2、4~7までを同じようにソートしてくと、最終的にリスト全体の整列が完了します。

また、このように部分的に分割して問題を解決するアプローチを分割統治(Divide and Rule or Divide and Conquer)と呼び、クイックソートでは再帰呼び出し(Recursive Call)を行うことで実行しています。

 

なお、クイックソート安定な整列アルゴリズム(データの位置関係が保存される)ではありません。

 

クイックソートの計算量

クイックソートではO(n \log n)の計算量となりますが、前述した枢軸の決定方法次第で、最悪の場合はO(n^2)となってしまいます。

また、クイックソートでは再帰呼び出しを行うため、再帰呼び出しを行うたびに呼び出し元への戻りアドレスや、呼び出し先で使うローカル変数などがメモリ上のスタック領域に積まれていくため、最悪の場合ではスタック領域のメモリを大幅に消費します。

上記のfig1のように枢軸を右端にした場合、データ数が多いと分割が続き効率が大幅に下がります。

 

Pythonクイックソートを実装してみる

それでは、Pythonクイックソートを実装してみます。 

 

# quick_sort.py
import time
import pickle

def quick_sort_sub(lst, left, right):
    l = left
    r = right
    pivot = lst[int((left+right)/2)]

    while l <= r:
        while lst[l] < pivot:
            l += 1
        while lst[r] > pivot:
            r -= 1
        if l <= r:
            tmp = lst[l]
            lst[l] = lst[r]
            lst[r] = tmp
            l += 1
            r -= 1
    if r > left:
        quick_sort_sub(lst, left, r)
    if l < right:
        quick_sort_sub(lst, l, right)

def quick_sort(lst):
    quick_sort_sub(lst, 0, len(lst)-1)

with open('sample_data.pkl', 'rb') as f:
    lst = pickle.load(f)

start = time.time()
quick_sort(lst)
print("elapsed {} sec".format(time.time() - start))

 

動作確認

それでは上記で作成したスクリプトを実行してみます。

事前に1~20,000までの値からランダムに20,000個生成したリストをpickleデータとして保存しておき、これをロードします。

自環境で実行した結果を以下にまとめます。

 

データ20,000個を整列した結果一覧
アルゴリズム 所要時間(秒)
Python 組込みメソッド(sort()) 0.015
Bubble Sort 72.979
Selection Sort 17.747
Insertion Sort 41.236
Shell Sort 0.253
Quick Sort 0.084

 

やはり一番高速なアルゴリズムといわれるだけあって、これまで作成した整列アルゴリズムの中では一番速い結果となりました。

今回は右と左のインデックスを足して2で割った中央値を枢軸としましたが、右と左と中央値の中からさらに中央値をとって枢軸とする方法がよく利用されるようです。

 

参考書籍

定本 Cプログラマのためのアルゴリズムとデータ構造 (SOFTBANK BOOKS)