排序算法-快速排序算法
快速排序被称为二十世纪最著名的算法之一
1. 普通快速排序算法
核心思想:
快速排序:每一趟排序过程将待排序序列根据关键字大小排序分割成两个部分,第一个部分的所有数据值小于关键字,第二个部分的所有数据值大于等于关键字.然后再依次对分割后的每个部分递归使用快速排序算法进行排序。
分割部分的主要操作:
假设待分割的序列为: i n p u t _ a r r [ l , l + 1 , . . . , r ] input\_arr[l, l+1, ... , r] input_arr[l,l+1,...,r]
(1)取关键字 v = i n p u t _ a r r [ l ] v= input\_arr[l] v=input_arr[l], 对于小于v 的所有元素放入 i n p u t _ a r r [ l + 1 , j ] input\_arr[l+1, j] input_arr[l+1,j] ,否则所有元素放入 i n p u t _ a r r [ j + 1 , r ] input\_arr[j+1,r] input_arr[j+1,r] ,每次的重点都在于对 i n p u t _ a r r [ j ] input\_arr[j] input_arr[j] 值的更新, j j j 表示最后一个小于关键字的元素存储的位置。
(2)交换关键字与 i n p u t _ a r r [ j ] input\_arr[j] input_arr[j] 的位置,即保证了 i n p u t _ a r r [ l , j ) < v input\_arr[l, j) < v input_arr[l,j)<v, i n p u t _ a r r [ j + 1 , r ] > = v input\_arr[j+1,r] >= v input_arr[j+1,r]>=v;
(3)递归的对分割后的两部分进行排序。
# 快速排序
# 快速排序:每趟排序选择一个关键字,将小于关键字的移动到序列左侧,否则移动到序列右侧
def partition(input_arr, left, right):
j = left
select_v = input_arr[left]
# 将序列元素分成三个部分[v, <v, >=v]
# arr[l+1: j] < select_v, arr[j+1: i] >= select_v
# j表示分割符号,也可以理解成是select_v的正确排序位置
for i in range(left+1, right+1):
if input_arr[i] < select_v:
input_arr[i], input_arr[j + 1] = input_arr[j + 1], input_arr[i]
j += 1
input_arr[left], input_arr[j] = input_arr[j], input_arr[left]
return j
def __quick_sort(input_arr, left, right):
if left > right:
return
p = partition(input_arr, left, right)
__quick_sort(input_arr, left, p-1)
__quick_sort(input_arr, p+1, right)
2.对于几乎有序的序列-快速排序算法的改进
快速排序分割序列的时候不能像归并排序一样保证分割后的子序列长度一致,其生成的二叉树的平衡度比归并排序要差
快速排序算法对近乎有序的序列,排序时间复杂度几乎退化为 O ( n 2 ) O(n^2) O(n2)
原因在于:每次选择的关键字都是序列中元素值最小的,那么对于分割后的序列,几乎所有的数据都分布在第二个部分,所以对整个数据的排序的重任都落在了第二个部分,其再次进行分割递归实现排序算法,整体复杂度几乎退化为 O ( n 2 ) O(n^2) O(n2)
改进方法:对关键字的选择,不使用默认的第一个值,随机选择一个关键字,再将其与第一个位置交换位置
python实现:
# 对近乎有序的序列,时间复杂度几乎为O(n^2)
# 快速排序:每趟排序选择一个关键字,将小于关键字的移动到序列左侧,否则移动到序列右侧
# 改进方法,对关键字的选择采取随机选择的方法,然后与序列第一个交换位置
def random_partition(input_arr, left, right):
j = random.randint(left, right)
input_arr[j], input_arr[left] = input_arr[left], input_arr[j]
j = left
select_v = input_arr[left]
# 将序列元素分成三个部分[v, <v, >=v]
# arr[l+1: j] < select_v, arr[j+1: i] >= select_v
# j表示分割符号,也可以理解成是select_v的正确排序位置
for i in range(left+1, right+1):
if input_arr[i] < select_v:
input_arr[i], input_arr[j + 1] = input_arr[j + 1], input_arr[i]
j += 1
input_arr[left], input_arr[j] = input_arr[j], input_arr[left]
return j
def random_partition(input_arr, left, right):
if left > right:
return
p = partition(input_arr, left, right)
__quick_sort(input_arr, left, p-1)
__quick_sort(input_arr, p+1, right)
3. 对众多重复数据的序列-快速排序算法的改进
再次强调一下: 快速排序分割序列的时候不能像归并排序一样保证分割后的子序列长度一致,其生成的二叉树的平衡度比归并排序要差
快速排序算法对拥有众多重复数据的序列,排序时间复杂度几乎退化为 O ( n 2 ) O(n^2) O(n2)
原因在于:对于众多的重复数据,每次选择的关键字很大可能选择为重复数据中的某一个,对序列进行分割,由于分割的判断算法为,第一部分小于选择的关键字,第二部分则为大于等于选择的关键字,对于众多重复数据而言,那么与关键字相等的数据依据我们的判断标准,大部分的数据数据也将都分布在第二个部分,所以对整个数据的排序的重任都落在了第二个部分,其再次进行分割递归实现排序算法,整体复杂度几乎退化为 O ( n 2 ) O(n^2) O(n2)
改进方法:对重复元素的分割,不再使其分布在某一个区间,而是均等地划分到两个部分,使其左边区间的值小于等于关键字,右边区间的值大于等于关键字。
python实现:
# 对具有很多重复元素的序列,快速排序的时间复杂度退化为O(n^2)
def repeat_partition(input_arr, left, right):
j = random.randint(left, right)
input_arr[j], input_arr[left] = input_arr[left], input_arr[j]
select_v = input_arr[left]
# input_arr[left + 1, i) <= v, input_arr(j, right] >= v
i = left + 1
j = right
while True:
while i <= right and input_arr[i] < select_v:
i += 1
while j >= left+1 and input_arr[j] > select_v:
j -= 1
# 循环终止条件
if i > j:
break
input_arr[i], input_arr[j] = input_arr[j], input_arr[i]
i += 1
j -= 1
# 循环终止的时候,i 停留在第一个大于等于 v 的位置, j停留在第一个小于等于v的位置
# 所以对input_arr[left], input_arr[j]进行交换位置
input_arr[left], input_arr[j] = input_arr[j], input_arr[left]
return j
def __quick_sort(input_arr, left, right):
if left > right:
return
p = partition(input_arr, left, right)
__quick_sort(input_arr, left, p-1)
__quick_sort(input_arr, p+1, right)
4.经典三路快速排序算法
三路快排的核心思想为:对于重复的元素不进行分割操作,将原来待排序的序列分成三部分
[第一部分:小于关键字; 第二部分:等于关键字; 第三部分:大于关键字]
# 三路快排,对重复元素不进行左右交换操作
def three_ways_quick_sort(input_arr, left, right):
if left >= right:
return
j = random.randint(0, right-left) + left
input_arr[j], input_arr[left] = input_arr[left], input_arr[j]
select_v = input_arr[left]
lt = left
gt = right + 1
i = lt + 1
# input_arr[left + 1, lt] < v, input_arr[gt, right] > v, [lt+1, i - 1] == v 下一个循环中i为待考察的元素
while i < gt:
if input_arr[i] < select_v:
input_arr[lt + 1], input_arr[i] = input_arr[i], input_arr[lt + 1]
lt += 1
i += 1
elif input_arr[i] > select_v:
input_arr[gt - 1], input_arr[i] = input_arr[i], input_arr[gt - 1]
gt -= 1
else:
i += 1
input_arr[lt], input_arr[left] = input_arr[left], input_arr[lt]
three_ways_quick_sort(input_arr, left, lt-1)
three_ways_quick_sort(input_arr, gt, right)
def quick_sort(input_arr):
three_ways_quick_sort(input_arr, 0, len(input_arr) - 1)
return input_arr
每天进步一点点…
上述代码本人均通过小样本测试用例,如若有误之处,还望指正。