基本思想:
快速排序是一种对包含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’相等就删除,数组随之缩小,最后再将其插入的方法。(如有更好方法,望告之)