1 快速排序
1.1 快速排序原理
快速排序的思路很简单就是在无序数组中选择一个锚点anchor,然后遍历数组将小于anchor的元素放置到锚点数据的左边,将大于anchor的元素放置到锚点的右边。具体实现中是左右轮流遍历,遍历时左边找到大于anchor的元素,右边找到小于anchor的元素二者交换即可。具体实现中和上面的有出入但是大概的思想就是这样。
1.2 流程
1.3 流程可视化
1.4 实现
def partition(l, start, end):
'''
@brief 选择一个锚点将所有元素分割为两部分[start, end],以第一个元素为锚点
@param l list
@param start
@param end
@return 锚点
'''
if end <= start:
return end
value = l[start]
i = start
j = end
while i < j:
while l[j] > value and i < j:
j -= 1
l[i] = l[j]
while l[i] <= value and i < j: #结束之后i指向的是大于value的index
i += 1
l[j] = l[i]
l[i] = value
return i
def quick_sort(l, start, end):
'''
@brief 快速排序[start, end]
@param l list
@param start 左边边界
@param end 右边边界
'''
if end <= start:
return
anchor = partition(l, start, end, hook_func)
quick_sort(l, start, anchor - 1)
quick_sort(l, anchor + 1, end)
2 快速排序优化
2.1 普通的优化方法
这部分的优化方法类似于归并等排序算法的优化方式:当子数组小于一定阈值时启用插入排序。
def quick_sort_II(l, start, end):
'''
@brief 快速排序[start, end),当问题规模足够小时,使用插入排序代替
@param l list
@param start 左边边界
@param end 右边边界
'''
if start >= end:
return
if end - start < 16:
insert_sort.insert_sort(l, start, end, None)
return
anchor = partition(l, start, end)
quick_sort(l, start, anchor - 1)
quick_sort(l, anchor + 1, end)
2.2 三取样切分
快速排序是将数组划分为小于锚点的子数组和大于锚点的子数组两部分。三取样切分则将数组划分为三部分:小于锚点的部分,等于锚点的部分,大于锚点的部分。
def partition3way(l, start, end):
'''
@param 三取样切分,将整个数组分成,小于当前元素,等于当前元素和大于当前元素三个部分[start, end)
@param l
@param start
@param end
@note [start, lt] lower
[lt + 1, i - 1] equal
[gt + 1, end] bigger
'''
value = l[start]
i = start
lt = start - 1
gt = end
while i <= gt:
if l[i] < value:
l[i], l[lt + 1] = l[lt + 1], l[i]
lt += 1
elif l[i] > value:
l[i], l[gt] = l[gt], l[i]
gt -= 1
else:
i+= 1
return lt, gt + 1
def quick_sort3way(l, start, end):
'''
@brief 快速排序[start, end),当问题规模足够小时,使用插入排序代替
@param l list
@param start 左边边界
@param end 右边边界
'''
if start < end:
lt, gt = partition3way(l, start, end)
quick_sort3way(l, start, lt)
quick_sort3way(l, gt, end)
2.3 三切分取样优化
针对三切分取样的一个优化便是先将数组划分为等于anchor的区域,小于anchor的区域,未处理区域,大于anchor的区域,等于anchor的区域,然后在最后将以上内容合并为三部分小于anchor的区域,等于anchor的区域,大于anchor的区域。
def partition3way_faster(l, start, end):
'''
@param 三取样切分,将整个数组分成,小于当前元素,等于当前元素和大于当前元素三个部分[start, end)
@param l
@param start
@param end
@note 将start和end分为四段=anchor,<anchor,>anchor,=anchor [start,p,i,j,q,end]
[start, p) =v
[p, i) <v
[i,j) ?
[j,q) >v
[q,end) =v
'''
anchor = l[start]
i = start + 1
j = end
p = i
q = j
while i < j:
if l[i] == anchor:
l[i], l[p] = l[p], l[i]
i += 1
p += 1
elif l[i] < anchor:
i += 1
else:
l[i], l[j] = l[j], l[i]
j -= 1
if i >= j:
break
if [j] == anchor:
l[j], l[q] = l[q], l[j]
j -= 1
q -= 1
elif l[j] < anchor:
j -= 1
else:
l[i], l[j] = l[j], l[i]
i += 1
#结束之后
#[start, p, ij, q, end]
while p != start:
l[p - 1], l[i - 1] = l[i - 1], l[p - 1]
p = p - 1
i = i - 1
while q != end:
l[q + 1], l[j + 1] = l[q + 1], l[j + 1]
q = q + 1
j = j + 1
return i, j
def quick_sort3way_faster(l, start, end):
'''
@brief 快速排序[start, end),当问题规模足够小时,使用插入排序代替
@param l list
@param start 左边边界
@param end 右边边界
'''
if start < end:
lt, gt = partition3way(l, start, end)
quick_sort3way_faster(l, start, lt)
quick_sort3way_faster(l, gt, end)
2.4 n取样切分
快速排序的一个优化方向就是寻找更好的anchor方便划分,其中一个办法就是随机选取n个数,选择这n个数的中位数作为anchor。
def partition5sample(l, start, end, sample_no=5):
'''
@brief 五取样切分使用从数组中采样的五个数的中位数进行parition
@param l
@param start
@param end
@param sample_no 采样数
'''
def get_mid(l, part_list):
'''
@brief 返回中位数对应的value在l中的索引,如果l的数组为偶数则选择len(l)/2
'''
part_values = [l[i] for i in part_list]
mid_value = part_values[int(len(part_values)/2)]
for i in part_list:
if l[i] == mid_value:
return i, mid_value
part_index_list = [random.randint(start, end) for i in range(sample_no)]
anchor, _ = get_mid(l, part_index_list)
l[anchor], l[start] = l[start], l[anchor]
return partition(l, start, end, hook_func)
def quick_sort5sample(l, start, end):
'''
@brief 快速排序[start, end),parition时使用从数组中随机采样的五个数的中位数进行切分
@param l list
@param start 左边边界
@param end 右边边界
'''
if start < end:
anchor = partition5sample(l, start, end)
quick_sort5sample(l, start, anchor - 1)
quick_sort5sample(l, anchor + 1, end)
2.5 快速排序的非递归版本
def quick_sort_cyc(l, start, end):
'''
@brief 快速排序非递归版本
@param l list
@param start
@param end
@note 建立一个容器,容器的每个元素是一个tuple表示需要进行partition的start和end
'''
rst = stack.stack()
snd = stack.stack()
rst.push((start, end))
while not rst.empty():
while not rst.empty():
start, end = rst.pop()
if end < start:
continue
anchor = partition(l, start, end, hook_func)
snd.push((start, anchor - 1))
snd.push((anchor + 1, end))
rst, snd = snd, rst
3 性能
3.1 不同版本性能对比
数据分布 | 性能对比 |
---|---|
无序 | |
有序 | |
逆序 | |
90%有序 | |
高斯分布 | |
泊松分布 | |
所有元素相同 | |
数组中只有两个值 |
3.2 算法复杂度
算法 | 时间复杂度 | 空间复杂度 |
---|---|---|
快速排序 | O ( n l o g n ) O(nlog{n}) O(nlogn) | O ( l o g n ) O(log n) O(logn) |