クイックソート

リハビリ第2弾(ref. スランプ脱出法, フィボナッチ数列)


from random import shuffle
data = range(100)
shuffle(data)

# recursion
def qsort(data):
    if not data: return data
    pivot = data[0]
    rest = data[1:]
    greater = [x for x in rest if x > pivot]
    lesser = [x for x in rest if x <= pivot]
    return qsort(lesser) + [pivot] + qsort(greater)

print qsort(data)


ループ版難しい。これじゃ1回しかpivotで振り分けてないしな…

# loop
def qsort_loop(data):
    """
    ... pivot a b c ... last done ... のとき
    if a < pivot:
        ... a pivot b c ...
    else:
        ... pivor b c ... last a done ...
        が素朴だけどどこれはコピーがたくさんあるので
        ... pivor last b c ... a done ...

    """
    if not data: return data
    pivot = data[0]
    cur = 1
    done = len(data)
    while cur != done:
        v = data[cur]
        if v < pivot:
            data[cur - 1] = v
            data[cur] = pivot
            cur += 1
        else:
            data[cur] = data[done - 1]
            data[done - 1] = v
            done -= 1
    
    return data

print qsort_loop(data)



できた。

# loop
def qsort_loop(data):
    """
    ... pivot a b c ... last done ... のとき
    if a < pivot:
        ... a pivot b c ...
    else:
        ... pivor b c ... last a done ...
        が素朴だけどどこれはコピーがたくさんあるので
        ... pivor last b c ... a done ...

    """
    stack = [(0, len(data))]
    while stack:
        start, end = stack.pop()
        print (start, end)
        if end - start <= 1: continue
        pivot = data[start]
        cur = start + 1
        done = end
        while cur != done:
            v = data[cur]
            #print data
            #print cur, done, pivot, v
            if v < pivot:
                data[cur - 1] = v
                data[cur] = pivot
                cur += 1
            else:
                data[cur] = data[done - 1]
                data[done - 1] = v
                done -= 1

            #print data
            #print
        stack.append((start, cur - 1))
        stack.append((cur, end))
    
    return data

print qsort_loop(data)

最初

-        stack.append((start, cur - 1))
-        stack.append((cur, end))
+        stack.append((start, cur))
+        stack.append((cur + 1, end))

ってなっていて大体ソートし終わった最後にぐちゃぐちゃになっていた。コメントアウトしてあるprint文を補ってrange(5)にして流れを追ってみた。



速度の比較

from time import clock

def profile(n):
    print n,
    original_data = range(n)
    shuffle(original_data)
    data = original_data[:]

    t = clock()
    qsort(data)
    print clock() - t,

    data = original_data[:]
    t = clock()
    qsort_loop(data)
    print clock() - t

for i in range(13):
    profile(100 * (2 ** i))
100 0.000448 0.000429
200 0.000967 0.001027
400 0.002129 0.002277
800 0.004327 0.005072
1600 0.00907 0.011751
3200 0.018466 0.022997
6400 0.039396 0.050003
12800 0.085261 0.112591
25600 0.171988 0.224489
51200 0.362911 0.484281
102400 0.767855 1.092627
204800 1.677372 2.279966
409600 3.564921 4.905796

あれあれ。意外と再起呼び出しが健闘するなぁ。再起版の方がメモリを無駄遣いするはずなのだけども、ループ版のポインタ演算のコストが高いせいで見えてこないのかな。