【排序篇】快速排序、快速选择
一. 快速排序
1. 快速排序实现逻辑顺序
- 选择一个排序字段边界值作为基准值
- 将原数组分割成前后两个部分,保证前部分小于基准值,后部分大于基准值【partition 操作】
- 对基准值的左右两侧,递归地进行第一步和第二步
2. 快速排序实现示例
## 应注意程序单个函数的流程性,保证逻辑流畅
## 习惯判断程序函数的适用边界,当不满足函数使用时退出函数,防止程序出错或流程不直观
## refer_value 参数取值时需注意是在总列表里提取,而非新列表
def quick_sort(lists, start, end):
if start >= end:
return
left = start
right = end
## 1. 选择一个排序字段边界值作为基准值
refer_value = lists[left]
## 2. partition 操作
while left < right:
while (left < right and lists[right] >= refer_value):
right -= 1
if (left < right):
lists[left] = lists[right]
left += 1
while (left < right and lists[left] <= refer_value):
left += 1
if (left < right):
lists[right] = lists[left]
right -= 1
lists[left] = refer_value
## 3. 对基准值的左右两侧,递归地进行第一步和第二步
quick_sort(lists, start, left - 1)
quick_sort(lists, left+1, end)
return
test_list = [0,2,3,4,5,6,7,8,9,7,5,3,32,1,1]
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
3. 快速排序优化
3.1 单边递归优化
3.1.1 优化思想
主要思想通过减少函数调用减少算法时间,通过在同一个函数内调用同一侧的排序函数,从而减少函数调用时间
3.1.2 优化实现代码
## partition 操作
def partition(lists, left, right):
refer_value = lists[left]
while left < right:
while (left < right and lists[right] >= refer_value):
right -= 1
if (left < right):
lists[left] = lists[right]
left += 1
while (left < right and lists[left] <= refer_value):
left += 1
if (left < right):
lists[right] = lists[left]
right -= 1
lists[left] = refer_value
return left
def quick_sort(lists, start, end):
while (start < end):
idx = partition(lists, start, end)
## 3. 对基准值的左右两侧,递归地进行第一步和第二步
quick_sort(lists, idx+1, end)
end = idx - 1
return
test_list = [0,2,3,4,5,6,7,8,9,7,5,3,32,1,1]
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
3.2 基准值选取优化
3.2.1 优化思想
合适的基准值可以减少赋值,数值转换的操作。换言之,当我们找到列表中的最小值、最大值时,在执行partition操作时,我都将执行n次赋值操作,当我们找的直接越接近列表的中间值时,所需要执行的赋值操作越少
3.2.2 优化策略
1.三点去中法:取列表中最左边,中间,最右边的三个数,选取三个数的中等数当作基准值,后续将基准值与partition的临时基准值进行位置替换后,开始快速排序
3.2.3 优化实现代码
在测试代码中,数据量越大优化的效果越明显,且基准值选取优化普遍会降低测试时间
import random
import time
def quick_sort(lists, start, end):
if start >= end:
return
left = start
right = end
left_value = lists[left]
right_value = lists[right]
mid = int((left+right)/2)
mid_value = lists[mid]
refer_value = left_value + right_value + mid_value \
- min(left_value, right_value, mid_value) \
- max(left_value, right_value, mid_value)
if refer_value == right_value:
lists[left], lists[right] = lists[right], lists[left]
elif refer_value == mid_value:
lists[left], lists[mid] = lists[mid], lists[left]
## 1. 选择一个排序字段边界值作为基准值
refer_value = lists[left]
## 2. partition 操作
while left < right:
while (left < right and lists[right] >= refer_value):
right -= 1
if (left < right):
lists[left] = lists[right]
left += 1
while (left < right and lists[left] <= refer_value):
left += 1
if (left < right):
lists[right] = lists[left]
right -= 1
lists[left] = refer_value
## 3. 对基准值的左右两侧,递归地进行第一步和第二步
quick_sort(lists, start, left - 1)
quick_sort(lists, left+1, end)
return
test_list = [random.randint(0, 1000000) for i in range(1000000)]
start_time = time.time()
quick_sort(test_list, 0, len(test_list)-1)
print(time.time() - start_time)
# print(test_list)
3.3 partition 操作优化
3.3.1 优化思想
通过减少程序实现中的比较操作,来减少时间花销,来提高程序的运行效率。不再采用选择值赋值的形式,而且采用选择一个大于基准值与一个小于基准值进行交换
3.3.2 实现代码
import random
def quick_sort(lists, start, end):
if start >= end:
return
left = start
right = end
left_value = lists[left]
right_value = lists[right]
mid = int((left+right)/2)
mid_value = lists[mid]
refer_value = left_value + right_value + mid_value \
- min(left_value, right_value, mid_value) \
- max(left_value, right_value, mid_value)
## 2. partition 操作
while left <= right:
while (lists[right] > refer_value):
right -= 1
while (lists[left] < refer_value):
left += 1
if (left <= right):
lists[right], lists[left] = lists[left], lists[right]
left += 1
right -= 1
## 3. 对基准值的左右两侧,递归地进行第一步和第二步
quick_sort(lists, start, right)
quick_sort(lists, left, end)
return
test_list = [random.randint(0, 10) for i in range(10)]
print(test_list)
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
4. 三种优化合并实现方案
import random
## 优化版本
def quick_sort(lists, start, end):
while (start < end):
left = start
right = end
## 基准值选择优化
left_value = lists[left]
right_value = lists[right]
mid = int((left+right)/2)
mid_value = lists[mid]
refer_value = left_value + right_value + mid_value \
- min(left_value, right_value, mid_value) \
- max(left_value, right_value, mid_value)
## partition 操作优化
while left <= right:
while (lists[right] > refer_value):
right -= 1
while (lists[left] < refer_value):
left += 1
if (left <= right):
lists[right], lists[left] = lists[left], lists[right]
left += 1
right -= 1
## 单边优化
## 对基准值的左右两侧,递归地进行第一步和第二步
quick_sort(lists, left, end)
end = right
return
test_list = [random.randint(0, 10) for i in range(10)]
print(test_list)
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
5. 参考知识点:
1.【partition 操作】 :也称为分割操作,遍历将小于基准值的元素放在基准值的前面,将大于基准值的元素放在基准值的后面
- 快速排序算法的时间复杂度不稳定,很有可能退化到最坏的时间复杂度 o ( n 2 ) o(n^2) o(n2)
6. 思考疑问&解答
- 为什么不采用单边遍历
最终 L,R将会合并在中间分界索引上值,及refer_value最终应当保留的位置,根据partition操作,左部分应小于refer_value,右部分应大于refer_value。假使采用单边遍历方式,那么会涉及到更多的数据替换操作。 - 如何选择从start还是end开始遍历
从左右开始遍历都可以,取决于refer_value所选择的位置索引。假使选择边索引为start的位置,应当从右边开始遍历;假使选择边索引为end的位置,应当从左边开始遍历。
二. 快速选择(Quick Selection)
解决问题:在一堆无序的数字中查找第k位元素
快速选择算法是基于快速排序算法的一种拓展算法,它可以在不对数据整体进行排序的前提下,快速找到排名第 k 位的元素,而且时间复杂度还能优化到 o ( n ) o(n) o(n)
1. 实现代码
def quick_sort(lists, start, end, k):
if start >= end:
return
left = start
right = end
## 1. 选择一个排序字段边界值作为基准值
refer_value = lists[left]
## 2. partition 操作
while left < right:
while (left < right and lists[right] >= refer_value):
right -= 1
if (left < right):
lists[left] = lists[right]
left += 1
while (left < right and lists[left] <= refer_value):
left += 1
if (left < right):
lists[right] = lists[left]
right -= 1
lists[left] = refer_value
if left == k-1:
return
## 3. 对基准值的左右两侧,递归地进行第一步和第二步
## 减少函数运行,基准值某一侧数据不需要排序
if left > k -1 :
quick_sort(lists, start, left - 1, k)
else:
quick_sort(lists, left+1, end, k)
return
test_list = [4,8,7,6,9,5,3,2,1]
k = 5
quick_sort(test_list, 0, len(test_list)-1, k)
print(test_list[k-1])
问题可延申至TOP-K问题