快速排序只一种基于分治策略(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
代码中j指针负责遍历整个子数组,而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()