コンテンツにスキップ

クイックソート

出典: フリー百科事典『ウィキペディア(Wikipedia)』
クイックソート
Quicksort in action on a list of numbers. The horizontal lines are pivot values.
クイックソートのアニメーション
クラス ソート
最悪計算時間
最良計算時間
平均計算時間
最悪空間計算量 (素朴な実装)
(Hoare 1962)[1]

: quicksort1960

  [2]

[]




(一)

(二)

(三)

(四)



(一)P#

(二)PLO

(三)PHI

(四)LO, HI 
LOHILOHI LO, HI  2, 3 

LOHIHI

23 Dual-pivot Quicksort [3]

[]




: 8 4 3 7 6 5 2 1

77871
1 4 3 76 5 2 8

72
1 4 3 2 6 5 78

757
1 4 3 2 6 5 | 7 8

1 4 3 26 5242
12 3 4 6 5 | 7 8

323
1 2 | 3 4 6 5 | 7 8

1222212
1 | 2| 3 4 6 5 | 7 8

3 4 65665
1 | 2| 3 4 5 6| 7 8

6566
1 | 2| 3 4 5 | 6| 7 8

3454443
1 | 2| 3| 4 5 | 6| 7 8

4555545
1 | 2| 3| 4| 5| 6| 7 8

78888
1 | 2| 3| 4| 5| 6| 7| 8

[]

[]


 1 

[ 1]



3[4]



[5]

 

[]


  

 [1][4] 1[6]

[ 2][4]

  

[]

C[]


C
/** 
 * 値を交換する
 * @param x  - 交換するデータのポインタ
 * @param y  - 交換するデータのポインタ
 * @param sz - データサイズ
 */
void
swap(
    void* x, 
    void* y, 
    size_t sz
) {
    char* a = x;
    char* b = y;
    while (sz > 0) {
        char t = *a;
        *a++ = *b;
        *b++ = t;
        sz--;
    }
}

/** 分割関数
 *
 * 配列をピボットによって分割し、分割位置を返す。
 * @param a   - 分割する配列
 * @param cmp - 比較関数へのポインタ
 * @param sz  - データサイズ
 * @param n   - 要素数
 * @returns 分割位置を示すポインタ
 */
void* 
partition(
    void* a,
    int (*cmp)(void const*, void const*),
    size_t sz,
    size_t n
) {
    // void* に対して直接ポインタ演算はできないので予め char* へ変換する
    char* const base = a;
    if (n <= 1) return base + sz;
    char* lo = base;
    char* hi = &base[sz * (n - 1)];
    char* m  = lo + sz * ((hi - lo) / sz / 2);
    // m が median-of-3 を指すようソート
    if (cmp(lo, m) > 0) {
        swap(lo, m, sz);
    }
    if (cmp(m, hi) > 0) {
        swap(m, hi, sz);
        if (cmp(lo, m) > 0) {
            swap(lo, m, sz);
        }
    }
    while (1) {
        while (cmp(lo, m) < 0) lo += sz; // ピボット以上の要素を下から探す
        while (cmp(m, hi) < 0) hi -= sz; // ピボット以下の要素を上から探す
        if (lo >= hi) return hi + sz;
        swap(lo, hi, sz);
        // ピボットがスワップされた場合、スワップ先を指すよう m を更新する
        if (lo == m) {
            m = hi;
        } else if (hi == m) {
            m = lo;
        }
        lo += sz;
        hi -= sz;
    }
}

/** クイックソート
 *
 * @param a   - ソートする配列
 * @param cmp - 比較関数へのポインタ
 * @param sz  - データサイズ
 * @param n   - 要素数
 */
void
quicksort(
    void* a,
    int (*cmp)(void const*, void const*),
    size_t sz,
    size_t n
) {
    if (n <= 1) return;
    char* p = partition(a, cmp, sz, n);
    char* const base = a;
    size_t n_lo = (p - base) / sz;
    size_t n_hi = (&base[sz * n] - p) / sz;
    quicksort(a, cmp, sz, n_lo); // 左側をソート
    quicksort(p, cmp, sz, n_hi); // 右側をソート
}

cmp(x, y)x < y なら負、x = y ならゼロ、x > y なら正の整数を返す関数とする。

Scheme[編集]

Schemeによるクイックソートの実装例を示す:

(require-extension srfi-1)              ; SRFI 1 の呼び出し方は実装依存(場合によってはデフォルト)。これは Chicken Scheme の例。

(define (qsort f ls)
  (if (null? ls)
      '()
      (let ((x (car ls)) (xs (cdr ls)))
        (let ((before
               (qsort f (filter (lambda (y) ; filter は SRFI 1 の手続き
                                  ;; compose は Chicken、Gauche、Guile、Racket 等に備わってる「合成関数を作る」手続き。
                                  ;; 詳細は Paul Graham の On Lisp
                                  ;; http://www.asahi-net.or.jp/~kc7k-nd/onlispjhtml/returningFunctions.html
                                  ;; を参照。
                                  ((compose not f) x y)) xs)))
              (after
               (qsort f (filter (lambda (y) ; filter は SRFI 1 の手続き
                                  (f x y)) xs))))
          (append before (cons x after))))))

Python[編集]

Pythonによるクイックソートの実装例を示す:

from typing import Any
from collections.abc import MutableSequence, Callable
# median-of-three
# 与えられた3値の中央値を返す
def median3(x, y, z):
    return max(min(x, y), min(max(x, y), z))

# 分割関数
# 配列の指定範囲をピボットに従って分割する
# 
# @param seq   - 分割する配列
# @param keyFn - 配列要素のキー値を計算する関数
# @param first - 分割範囲の最初のインデックス
# @param last  - 分割範囲の最後のインデックス
# @returns 分割点のインデックス
def partition(seq: MutableSequence[Any], keyFn: Callable[[Any], Any], first: int, last: int):
    pivot = median3(keyFn(seq[first]), keyFn(seq[first + (last - first) // 2]), keyFn(seq[last]))
    while True:
        while keyFn(seq[first]) < pivot:
            first += 1
        while pivot < keyFn(seq[last]):
            last -= 1
        if first >= last:
            return last + 1
        seq[first], seq[last] = seq[last], seq[first]
        first += 1
        last -= 1

# クイックソート
#
# @param seq   - ソートする配列
# @param keyFn - 配列要素のキー値を計算する関数
def quicksort(seq: MutableSequence[Any], keyFn: Callable[[Any], Any]):
    def quicksortImpl(seq: MutableSequence, keyFn: Callable[[Any], int], first: int, last: int):
        while first < last:
            p = partition(seq, keyFn, first, last)
            if (p - first) < (last - p):
                quicksortImpl(seq, keyFn, first, p - 1)
                first = p
            else:
                quicksortImpl(seq, keyFn, p, last)
                last = p - 1
    quicksortImpl(seq, keyFn, 0, len(seq) - 1)

脚注[編集]

出典[編集]

  1. ^ a b Hoare 1962.
  2. ^ Skiena 2008, p. 129.
  3. ^ Yaroslavskiy 2009.
  4. ^ a b c Sedgewick 2018, pp. 273–291.
  5. ^ Sedgewick & Wayne 2011, p. 295.
  6. ^ 杉山 1995, p. 159.

注釈[編集]

  1. ^ 「先頭未満/以上」で分割してしまった場合、無限再帰によるスタックオーバーフローも起こりうる
  2. ^ Cによる実装例。「再帰しないクイックソートの実装」の節を参照。

[]





[]


Hoare, C. A. R. (1962). Quicksort. The Computer Journal 5 (1): 11. doi:10.1093/comjnl/5.1.10. 

Skiena, Steven S. (2008). The Algorithm Design Manual. Springer. p. 129. ISBN 978-1-84800-069-8 

Yaroslavskiy, Vladimir (2009). Dual-Pivot Quicksort. 2015102202182

Sedgewick, RobertC14: 2018228199891273-291ISBN 978-47-649-0560-3 

Sedgewick, Robert; Wayne, Kevin (2011-03-19). Algorithms (4th ed.). Addison-Wesley. p. 295. ASIN 032157351X. ISBN 0-321-57351-X. NCID BB06083373. LCCN 2011-707. OCLC 951241174 

, C1995159ISBN 978-45-015-2380-0 

[]


: - PythonC