1. 程式人生 > >快速排序(Quicksort)詳解(動畫程式碼)

快速排序(Quicksort)詳解(動畫程式碼)

快速排序只一種基於分治策略(divide and conquer)的經典排序演算法,並且是一種原地(in-place)排序,實現原地排序的依據是用兩個陣列下標(start,end)來確定當前待排序子陣列的範圍。

切分(partition)步驟(關鍵):

在對子陣列進行排序時,本質上是在確定子陣列中某個數(不妨設就是子陣列中第一個數字pivot=arr[start])在排序後子陣列中的位置下標mid(不是整個陣列的位置),使得在子陣列中[start,mid)的值小於pivot,子陣列中(mid,end]大於等於pivot。

mid確定後在對子陣列[start,mid)和(mid,end]進行快速排序(遞迴過程注意遞迴終止條件)

def sort(arr,start,end):
    if start>=end:
        return
    mid=partition(arr,start,end)
    sort(arr,start,mid-1)
    sort(arr,mid+1,end)

def exchange(arr,i,j):
    temp=arr[i]
    arr[i]=arr[j]
    arr[j]=temp

#待排序(子)陣列在原陣列arr中的下標起點start和下表終點end
#切分的本質是是一個哨兵左邊的元素都比它小,哨兵右側元素大於等於哨兵
#下標i指向的元素為當前最近的一個比哨兵小的元素
def partition(arr,start,end):
    i = start
    j = 0
    pivot = arr[i]
    for j in range(i+1,end+1):
        if arr[j] < pivot:
            i += 1
            exchange(arr,i,j)
        record.append(arr.copy())
    exchange(arr,i,start)
    record.append(arr.copy())#繪製動畫需要的程式碼
    return i

空間複雜度:O(logn)(遞迴呼叫的記憶體開銷)

時間複雜度:O(nlogn)

下面是一種非原地快速排序方法,利用了python神奇的語法特性(列表生成式),子陣列的排序會另外開記憶體進行儲存:

def sort1(ARRAY):
    ARRAY_LENGTH = len(ARRAY)
    if( ARRAY_LENGTH <= 1):
        return ARRAY
    else:
        PIVOT = ARRAY[0]
        GREATER = [ element for element in ARRAY[1:] if element > PIVOT ]
        LESSER = [ element for element in ARRAY[1:] if element <= PIVOT ]
        return quick_sort(LESSER) + [PIVOT] + quick_sort(GREATER)

三向切分快排:

為避免相等元素的重複比較,將子陣列中(相等元素)也鑑別切分出來

設定兩個下標指標(lt,gt)和一個pivot(pivot=arr[start]), lt之前的數[start,lt)比pivot小,gt和gt之後的數[gt,end]比pivot大, [lt,i)裡的數等於pivot,[i,gt)之間的數待i來遍歷。

def quick_sort_partition3(arr,start,end):
    if start >= end:
        return
    lt = start
    gt = end
    pivot = arr[start]
    i = start + 1
    while i <= gt:
        if arr[i] < pivot:
            exchange(arr,lt,i)
            record.append(arr.copy())
            lt += 1
            i += 1
        elif arr[i] > pivot:
            exchange(arr,i,gt)
            record.append(arr.copy())
            gt -= 1
        else:
            i += 1
    quick_sort_partition3(arr,start,lt-1)
    quick_sort_partition3(arr,gt+1,end)

下面是測試程式和動畫展示的程式碼:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation

record=[]
n=150
data=np.random.randint(0,100,n)
record.append(data.copy())
#sort(data,0,n-1)
quick_sort_partition3(data,0,n-1)
bins=np.arange(n+1)
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + data
nrects = len(left)

# here comes the tricky part -- we have to set up the vertex and path
# codes arrays using moveto, lineto and closepoly

# for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the
# CLOSEPOLY; the vert for the closepoly is ignored but we still need
# it to keep the codes aligned with the vertices
nverts = nrects*(1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom

fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
    barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())

def animate(i):
    # simulate new data coming in
    top = bottom + record[i]
    verts[1::5, 1] = top
    verts[2::5, 1] = top
    return [patch, ]

ani = animation.FuncAnimation(fig, animate, len(record), interval=1,repeat=False, blit=True)
plt.show()