快速排序的python实现和常见的几种优化

基本思想:

快速排序是一种对包含n个数的输入数组进行排序的算法,最坏情况运行时间为O(n^2)。虽然这个最坏运行时间比较差,但快速排序通常是用于排序的最佳使用选择,这是因为其平均性能相当好,期望的运行时间为O(nlgn),而且其中隐含的常数因子很小,另外,它还能够进行就地排序,在虚存环境中也能很好的工作。
在这里插入图片描述

像合并排序一样,快速排序也是基于分治模式的,下面是对一个典型子数组A[p…r]排序的分治过程的三个步骤:

分解:数组A[p…r]被划分成两个(可能空)子数组A[p…q-1]和A[q+1…r],使得A[p…q-1]中的每一个元素都小于A(q),而且小于A[q+1…r]中的元素,下标q也在这个划分过程中进行计算。

解决:通过递归调用快速排序,子数组A[p…q-1]和A[q+1…r]排序。

合并:因为两个子数组是就地排序,所以他们的合并不需要操作。

python代码

def quicksort(A,p,r):    
	if r>p :        
		q=partition(A,p,r)        
		quicksort(A,p,q-1)        
		quicksort(A,q+1,r)

为排一个完整的数组A,应调用quicksort(A,0,len(A)-1)
而该算法关键是partition(A,p,r)过程,也就是上述的“分解”过程。
其python代码为

def partition(A,p,r):
 j=p
 i=p
 x=A[r]
 while j!=r:
  if A[j]<x :
   A[i],A[j]=A[j],A[i]
   j+=1
   i+=1
  else:
   j+=1
 A[i],A[r]=A[r],A[i]
 return i

其运行过程可参考如图:

完整代码可参考如下

def quicksort(A,p,r):
 if r>p :
  q=partition(A,p,r)
  quicksort(A,p,q-1)
  quicksort(A,q+1,r)
 
def partition(A,p,r):
 j=p
 i=p
 x=A[r]
 while j!=r:
  if A[j]<x :
   A[i],A[j]=A[j],A[i]
   j+=1
   i+=1
  else:
   j+=1
 A[i],A[r]=A[r],A[i]
 return i
 
 A=input().split()
 A=[int(A[i]) for i in range(len(A))]
 quicksort(A,0,len(A)-1)
 print(A)

以上可以说应该实现了快排的基本python实现,接下来可以看看他的常用优化

1.随机化

代码

def partition(A,p,r):
 i=random.randint(p,r)
 A[r],A[i]=A[i],A[r]    
 j=p
 i=p
 x=A[r]
 while j!=r:
  if A[j]<x :
   A[i],A[j]=A[j],A[i]
   j+=1
   i+=1
  else:
   j+=1
 A[i],A[r]=A[r],A[i]
 return i

在partition(A,p,r)加上下面这两行即可

 i=random.randint(p,r)
A[r],A[i]=A[i],A[r] 

分析:

快排的最坏情况划分行为产生的两个区域,分别包含n-1个元素和1个元素,这样很明显快排的这个分区排序的优势是一点也没有发挥出来,笨拙的和插入排序的比较次数是一样的,即运行时间也为O(n^2)。而且当输入的数已经完全排好序时,快排的运行时间为最坏但插入排序却是O(n)。

所以当次次随机选取一个数作为‘x’来划分这两个区域就基本可以避免这种最坏情况的发生,从而使运行时间更逼近其期望运行时间O(nlgn)来提高效率。

2.尾递归

代码:
如下改变quicksort(A,p,r)即可

def quicksort(A,p,r):
 while r>p :
  q=partition(A,p,r)
  quicksort(A,p,q-1)
  p=q+1

分析:
在之前的quicksort(A,p,r)中包含有两个对其自身的递归调用,左右子数组分别被递归调用。然而实际上快排是一直往前就地排序,不需要回头的,当它一直往下算到底时数组就已经被排好了。所以快排的第二次递归调用并不是必须的,可以用迭代控制机构来替代它,这种技术就称作尾递归

有关它的详细原理和具体优化步骤可参考‘Tyler_Zx’博主的《尾递归及快排尾递归优化》—链接:https://blog.csdn.net/qq_38289815/article/details/105487879

3.聚集元素

代码

import random
def quicksort(A,p,r):
 while r>p :
  q,j=partition(A,p,r)
  quicksort(A,p,q-1)
  p=j+1
 
def partition(A,p,r):
 i=random.randint(p,r)
 A[r],A[i]=A[i],A[r]
 j=p
 i=p
 sum=1
 x=A[r]
 while i!=r:
  if A[i]<x :
   A[i],A[j]=A[j],A[i]
   j+=1
   i+=1
  elif A[i]==x :
   A.remove(A[i])
   sum+=1
   r=r-1
  else:
   i+=1
 A[j],A[r]=A[r],A[j]
 q=j
 while sum>1 :
  A.insert(j+1,x)
  j+=1
  sum-=1
 return q,j
 
 A=input().split()
 A=[int(A[i]) for i in range(len(A))]
 quicksort(A,0,len(A)-1)
 print(A)
 #以上代码也结合了前述的两个优化

分析:
聚集元素的思想:在一次分割结束后,将与本次基准相等的元素聚集在一起,再分割时,不再对聚集过的元素进行分割。

即在快排时多考虑一个与本次基准(‘x’)相等的情况,把与‘x’相等的元素最后都放在‘x’旁边和‘x’分为一类,不必再分到子数组再行运算了,显然当数组中有大量相等元素时,可极大的减少运算,提高效率。

代码中的具体实现采用了与‘x’相等就删除,数组随之缩小,最后再将其插入的方法。(如有更好方法,望告之)

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值