排序算法专题之快速排序
Posted Python算法之旅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法专题之快速排序相关的知识,希望对你有一定的参考价值。
在各种基于关键码比较的内排序算法中,快速排序是实践中平均速度最快的算法之一。
快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。根据划分算法的不同,快速排序有许多不同的实现方法,本文将给出一些常见版本并分析其优化过程,抓住问题的本质。
说明:选取适当的枢纽元尽可能将数组划分为长度相当的两个子序列,是快速排序高效的关键。为了表达的方便,我们简单的选取a[left]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。




扫码加入“Python算法之旅”微信群,和斌哥面对面交流,更多资料和更有趣的话题等你一起来分享。
排序算法专题之快速排序
1. 非原地快速排序
例1.快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。
我们可以在列表中选择一个元素作为"基准"(枢纽元),将列表分成左右两个子序列,其中左序列的元素均小于枢纽元,而右序列的元素均不小于枢纽元。为了表达的方便,我们简单的选取a[0]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。
函数名:quick_sort(a)
参数表:a -- 待排序列表。
返回值:升序排序后的新列表。
def quick_sort(a):
if len(a) <= 1: return a
left, right = [], []
p = a[0] #选取a[0]作为枢纽元
for x in a[1:]:
if x < p:
left.append(x)
else:
①
return quick_sort(left) + [p] + quick_sort(right)
2. 原地快速排序(单向扫描)
在函数内嵌套定义快速排序子函数,不必另外分配空间。
方法一:单向扫描划分数组。使用两个下标i和j,分别指向最后一个小记录的下标和第一个未处理记录的下标。
优点:思路清晰,代码简洁易懂。
缺点:只向一个方向扫描,每遇到小记录元素就与下标i所指的大记录元素交换位置,不能将该大记录元素一步到位交换到右侧的正确位置,导致某些大记录元素需要交换多次才能到达正确位置。
一个极端的例子,a=[5,6,4,2,3,1],值为6的元素被交换了4次,才最终到达正确位置,都快赶上冒泡排序的低效了。
函数名:quick_sort_1(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_1(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[left] #选取a[left]作为枢纽元
i = left
for j in range(left+1, right+1):
if a[j] < p: #确保小记录元素小于枢纽元
i += 1
if i != j: #将小记录元素交换到左侧
a[i], a[j] = a[j], a[i]
① #将枢纽元放到正确位置
q_sort(left, i-1)
②
q_sort(③ ) #调用快速排序子函数
3. 原地快速排序(双向扫描1)
方法二:双向扫描划分数组。方法一中被交换的大记录元素不能一步到位交换到右侧的正确位置,导致出现重复交换。
我们可以通过从数组的两端交替向中间扫描,每扫描一次就把“不合群”的元素交换到另一侧的方法,确保每个位置的元素最多只被交换一次,这样效率要高于方法一。
为了保证小记录元素小于枢纽元,我们需要选取a[right]作为枢纽元,这样向右扫描时才不会越界。
函数名:quick_sort_2(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_2(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[right] #选取a[right]作为枢纽元
i, j = left, right
while i < j:
while a[i] < p: #确保小记录元素小于枢纽元
①
while i < j and a[j] >= p: #确保大记录元素不小于枢纽元
②
if i < j: #将“不合群”的元素交换到另一侧
a[i], a[j] = a[j], a[i]
③ #将枢纽元放到正确位置
q_sort(left, i-1)
q_sort(i+1, right)
q_sort(0, len(a)-1) #调用快速排序子函数
4. 原地快速排序(双向扫描2)
方法三:双向扫描划分数组。方法二中采取先扫描,再交换的方法,每次交换前都要先判断是否满足i < j,交换完毕后,又要再次判断是否满足i < j,以决定是否进入循环。这样重复判断,效率较低。我们将交换操作放到扫描之前,可以少做一次判断。我们开始选取a[left]作为枢纽元,经过第一次交换操作后,枢纽元到达right位置。
函数名:quick_sort_3(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_3(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[left] #选取a[left]作为枢纽元
i, j = left, right
while i < j:
① #第一次交换操作后,枢纽元到达right位置
while a[i] < p: #确保小记录元素小于枢纽元
②
while i < j and a[j] >= p: #确保大记录元素不小于枢纽元
③
④ #将枢纽元放到正确位置
q_sort(left, i-1)
q_sort(i+1, right)
q_sort(0, len(a)-1) #调用快速排序子函数
1. 非原地快速排序
例1.快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。
我们可以在列表中选择一个元素作为"基准"(枢纽元),将列表分成左右两个子序列,其中左序列的元素均小于枢纽元,而右序列的元素均不小于枢纽元。为了表达的方便,我们简单的选取a[0]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。
函数名:quick_sort(a)
参数表:a -- 待排序列表。
返回值:升序排序后的新列表。
def quick_sort(a):
if len(a) <= 1: return a
left, right = [], []
p = a[0] #选取a[0]作为枢纽元
for x in a[1:]:
if x < p:
left.append(x)
else:
right.append(x)
return quick_sort(left) + [p] + quick_sort(right)
2. 原地快速排序(单向扫描)
在函数内嵌套定义快速排序子函数,不必另外分配空间。
方法一:单向扫描划分数组。使用两个下标i和j,分别指向最后一个小记录的下标和第一个未处理记录的下标。
优点:思路清晰,代码简洁易懂。
缺点:只向一个方向扫描,每遇到小记录元素就与下标i所指的大记录元素交换位置,不能将该大记录元素一步到位交换到右侧的正确位置,导致某些大记录元素需要交换多次才能到达正确位置。
一个极端的例子,a=[5,6,4,2,3,1],值为6的元素被交换了4次,才最终到达正确位置,都快赶上冒泡排序的低效了。
函数名:quick_sort_1(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_1(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[left] #选取a[left]作为枢纽元
i = left
for j in range(left+1, right+1):
if a[j] < p: #确保小记录元素小于枢纽元
i += 1
if i != j: #将小记录元素交换到左侧
a[i], a[j] = a[j], a[i]
a[i], a[left] = a[left], a[i] #将枢纽元放到正确位置
q_sort(left, i-1)
q_sort(i+1, right)
q_sort(0, len(a)-1) #调用快速排序子函数
3. 原地快速排序(双向扫描1)
方法二:双向扫描划分数组。方法一中被交换的大记录元素不能一步到位交换到右侧的正确位置,导致出现重复交换。
我们可以通过从数组的两端交替向中间扫描,每扫描一次就把“不合群”的元素交换到另一侧的方法,确保每个位置的元素最多只被交换一次,这样效率要高于方法一。
为了保证小记录元素小于枢纽元,我们需要选取a[right]作为枢纽元,这样向右扫描时才不会越界。
函数名:quick_sort_2(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_2(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[right] #选取a[right]作为枢纽元
i, j = left, right
while i < j:
while a[i] < p: #确保小记录元素小于枢纽元
i += 1
while i < j and a[j] >= p: #确保大记录元素不小于枢纽元
j -= 1
if i < j: #将“不合群”的元素交换到另一侧
a[i], a[j] = a[j], a[i]
a[i], a[right] = a[right], a[i] #将枢纽元放到正确位置
q_sort(left, i-1)
q_sort(i+1, right)
q_sort(0, len(a)-1) #调用快速排序子函数
4. 原地快速排序(双向扫描2)
方法三:双向扫描划分数组。方法二中采取先扫描,再交换的方法,每次交换前都要先判断是否满足i < j,交换完毕后,又要再次判断是否满足i < j,以决定是否进入循环。这样重复判断,效率较低。我们将交换操作放到扫描之前,可以少做一次判断。我们开始选取a[left]作为枢纽元,经过第一次交换操作后,枢纽元到达right位置。
函数名:quick_sort_3(a)
参数表:a -- 待排序列表。
返回值:该方法没有返回值,但是会对列表的对象进行升序排序。
def quick_sort_3(a):
#嵌套定义快速排序子函数
def q_sort(left, right):
if left >= right: return
p = a[left] #选取a[left]作为枢纽元
i, j = left, right
while i < j:
a[i], a[j] = a[j], a[i] #第一次交换操作后,枢纽元到达right位置
while a[i] < p: #确保小记录元素小于枢纽元
i += 1
while i < j and a[j] >= p: #确保大记录元素不小于枢纽元
j -= 1
a[i],a[right] = a[right],a[i] #将枢纽元放到正确位置
q_sort(left, i-1)
q_sort(i+1, right)
q_sort(0, len(a)-1) #调用快速排序子函数
Python排序算法系列文章是我在阅读了大量算法专著以后,尝试用浅陋的语言把自己的理解表达出来。由于本人水平有限,表述中难免出现疏漏甚至错误之处,敬请谅解。
无论是赞同还是反对我的看法,都请你给我留言。如果你有新的想法,千万不要憋在心里,请发出来大家一起讨论。让我们相互学习,共同进步!
需要本文word版的,可以加入“选考VB算法解析”知识星球参与讨论和下载文件,“选考VB算法解析”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。
我们专注选考VB算法,感兴趣就一起来!
相关优秀文章:
以上是关于排序算法专题之快速排序的主要内容,如果未能解决你的问题,请参考以下文章