快速排序(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

代码中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()

 

你可能感兴趣的:(算法设计(Practice))