我在研究三路快速排序时,想出来了一个点子,使得非三路快速排序也可以高效排序大量重复元素的数据。
原理是这样的:
单次排序开始前,先设定一个状态为真
遇到等于基准值的值时,如果状态为真则与小于基准值时做相同的操作,
且无论状态为何值都对其进行取反并赋回原值。
这样可以保证遇到了重复的数据时,重复数据被均匀的分到了基准值两侧,从而避免了大量重复数据带来的严重划分不均匀,并提高了速度,减少了递归深度。
我将其称作交替快排。
以下为 交替快排、三路快排、普通快排 的不同数据情况的速度对比:
运行耗时对比
可以看出,三路快排在处理大量重复数据情况之外的速度有所下降,而交替快排则基本与普通快排相同。
交替快排对完全随机元素进行排序时虽然比普通快排慢一些但是也比三路快排更快。
以下贴出代码:
# -*- coding: utf-8; -*-
def quick_sort_three_ways(arr: list) -> None:
"""三路快排"""
def f(l, r):
while True:
if l >= r:
return
if r - l < 10:
# insertion sort
for i in range(l + 1, r + 1):
j = i
num = arr[i]
while (k:=j-1) >= l and (n:=arr[k]) > num:
arr[j] = n
arr[k] = num
j = k
return
# quick sort
# 调换 pivot
mid = l + (r - l) // 2
pivot = arr[mid]
arr[r], arr[mid] = pivot, arr[r]
i = l
j = l
k = r - 1
while j <= k:
num = arr[j]
if num > pivot:
arr[j], arr[k] = arr[k], num
k -= 1
elif num < pivot:
arr[j], arr[i] = arr[i], num
i += 1
j += 1
else:
j += 1
arr[j], arr[r] = pivot, arr[j]
f(l, i - 1)
l = j + 1 # 尾递归优化
f(0, len(arr) - 1)
def quick_sort_alternate(arr):
"""快速排序, 相同元素交替优化"""
def main(l, r):
while True:
if l >= r:
return
if r - l < 10:
# if r-l < 10 then insert sort
for i in range(l + 1, r + 1):
j = i
num = arr[i]
while (k:=j-1) >= l and (n:=arr[k]) > num:
arr[j] = n
arr[k] = num
j = k
return
mid = (r - l) // 2 + l
arr[r], arr[mid] = arr[mid], arr[r]
pivot = arr[r]
j = l
state = True
for i in range(l, r):
num = arr[i]
if num < pivot:
arr[i], arr[j] = arr[j], num
j += 1
elif num == pivot:
if state:
arr[i], arr[j] = arr[j], num
j += 1
state = not state
arr[r], arr[j] = arr[j], pivot
main(l, j - 1)
# 尾递归优化
l = j + 1
main(0, len(arr) - 1)
def quick_sort(arr):
"""快速排序"""
def main(l, r):
while True:
if l >= r:
return
if r - l < 10:
# r-l < 10 then insert sort
for i in range(l + 1, r + 1):
j = i
num = arr[i]
while (k:=j-1) >= l and (n:=arr[k]) > num:
arr[j] = n
arr[k] = num
j = k
return
mid = (r - l) // 2 + l
pivot = arr[mid]
arr[mid], arr[r] = arr[r], pivot
j = l
for i in range(l, r):
num = arr[i]
if num < pivot:
arr[i], arr[j] = arr[j], num
j += 1
arr[r], arr[j] = arr[j], pivot
main(l, j - 1)
# 尾递归优化
l = j + 1
main(0, len(arr) - 1)
以下是用来生成随机数组的代码:
from random import random
def rand_arr(arr):
for i in range(n):
rand = int(random() * n)
arr[i], arr[rand] = arr[rand], arr[i]
n = 100000
init_arr = [i for i in range(n)]
# 随机
random_arr = init_arr[:]
rand_arr(random_arr)
# 几乎有序
almost_sorted_arr = init_arr[:]
almost_sorted_arr[-1], almost_sorted_arr[n // 2] = \
almost_sorted_arr[n // 2], almost_sorted_arr[-1]
# 倒序
reversed_arr = init_arr[::-1]
# 完全随机生成的数组而不是打乱的数组
random_build_arr = [int(random() * n) for i in range(n)]
# 大量重复
massive_repeat_arr = init_arr[:n // 10] * 10
rand_arr(massive_repeat_arr)
所以,似乎交替快排比三路快排更加实用呢!
如果有人有证据证明这种优化方法已经被发明过了,请联系我。
反正我是没搜到这种优化方法。三数取中我没用因为貌似我测的更慢。多线程优化没加。
插排阈值我设置的10。尾递归优化加了,取l与r中间的数做基准值优化加了,很好用。
有序检查优化没加,在我常用的场景不太划算。
不过在已经排好的重复元素多的数组中,再进行排序。
交替快排应该会比三路快排有着更多的交换次数,这倒是可以用交换前先检查值是否相等来解决,不过这样会略慢一些,所以根据需求看你需要减少交换还是提高其它情况下的速度。虽然除了大量重复依旧比三路快排快。
我在Bilibili同步发布了该文章