快速排序(Quicksort)詳解(動畫程式碼)
阿新 • • 發佈:2018-11-25
快速排序只一種基於分治策略(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()