#!/usr/bin/python3
# -*- coding: UTF-8 -*-
'''''
快速排序算法
快速排序的基本思想是通过一趟排序将待排序列分割成独立的两个子序列,然后分别对这两个子序列分别快速排序,以达到整个序列有序。
假设待排序序列为a[low:high+1],从中任选一个元素值作为枢纽,再分别从左右两端向中间扫描,通过交换操作将不大于和不小于枢纽的元素分别置于左右两侧,
使得一趟排序后左子序列的元素均不大于右子序列的元素。
为防止数组下标越界或重复扫描或出现死循环,需要下标进行越界检查或设置“限位标志”,
根据检查方法的不同,出现了各种不同的表达方式,本文拟对此进行分析,抓住问题的本质。
'''
import random
import time
'''''
方法一:此法只获取了枢纽的值,但未对枢纽进行定位,故排序结束后枢纽位置不确定。
为避免当枢纽为最大或最小元素值时,会单向扫描整个数组而不做任何处理,出现无限递归。
应将枢纽设为“限位标志”,扫描时遇到枢纽就停下,并在做完交换操作后,i,j均向前一步。
这样无需对下标进行越界检查,而且确保一趟排序后j<i,子序列长度至少比原序列少1。
虽然此法在扫描时遇到枢纽也停下,使得交换操作增加,但由于无需检查下标越界,扫描效率很高。
极限情况分析:
t为最小值:1.i不动,j向左扫描到pos处停下;交换a[i],a[j],i,j各向前一步。
2.i不动,j向左扫描到low处停下,因为i>j,外层循环结束。
3.此时j==low,i==low+1,递归执行qsort_1(a,i,high)。
t为最大值:1.i向右扫描到pos处停下,j不动;交换a[i],a[j],i,j各向前一步。
2.i向右扫描到high处停下,j不动,因为i>j,外层循环结束。
3.此时j==high-1,i==high,递归执行qsort_1(a,low,j)。
'''
def qsort_1(a,low,high):
global sum
sum += 1
if low < high:
pos = random.randint(low,high)
i,j,t = low,high,a[pos]
while i <= j: #注意下标相等时也要扫描,确保最终j<i
while a[i]<t: i += 1
while a[j]>t: j -= 1
if i<=j: #即使a[i]==a[j]==t也要进行交换操作,是本算法的一个缺陷
a[i],a[j] = a[j],a[i]
i += 1 #交换完毕,i,j均向前一步,是本算法的高效之处
j -= 1
if low<j: qsort_1(a,low,j)
if i<high: qsort_1(a,i,high)
'''
对qsort_1采取消除尾递归优化
'''
def qsort_12(a,low,high):
global sum
sum += 1
while low < high:
pos = random.randint(low,high)
i,j,t = low,high,a[pos]
while i <= j: #注意下标相等时也要扫描,确保最终j<i
while a[i]<t: i += 1
while a[j]>t: j -= 1
if i<=j: #即使a[i]==a[j]==t也要进行交换操作,是本算法的一个缺陷
a[i],a[j] = a[j],a[i]
i += 1 #交换完毕,i,j均向前一步,是本算法的高效之处
j -= 1
if low<j: qsort_12(a,low,j)
low = i
'''''
方法二:方法一未对枢纽进行定位,当枢纽为最值时,会出现无限递归,若事先将枢纽定位到a[low],则可避免无限递归。
因为扫描时不交换与枢纽等值的元素,故需对下标进行越界检查,确保一趟排序后i==j,
然后将枢纽交换到a[j]处,这样可以确保子序列长度至少比原序列少1。
由于扫描时需要检查下标越界,使得扫描速度变慢,虽然交换操作减少,但总体效率还是下降了。
极限情况分析:
t为最小值:1.j向左扫描到low处停下,i不动,因为i==j,外层循环结束。
2.交换a[j],a[low],此时j==low,i==low,递归执行qsort_2(a,i+1,high)。
t为最大值:1.j不动,i向右扫描到high处停下,因为i==j,外层循环结束。
2.交换a[j],a[low],此时j==high,i==high,递归执行qsort_2(a,low,j-1)。
'''
def qsort_2(a,low,high):
global sum
sum += 1
if low < high:
pos = random.randint(low,high)
a[low],a[pos] = a[pos],a[low]
i,j,t = low,high,a[low]
while i < j: #循环结束时i==j
#因为枢纽元素在最左侧,应先向左扫描,确保最终a[j]<t
while i<j and a[j]>=t: j -= 1
while i<j and a[i]<=t: i += 1
if i<j:
a[i],a[j] = a[j],a[i]
a[j],a[low] = a[low],a[j]
#枢纽元素a[j]已经排好,无需再处理
if low<j-1: qsort_2(a,low,j-1)
if i+1<high: qsort_2(a,i+1,high)
'''
对qsort_2采取消除尾递归优化
'''
def qsort_22(a,low,high):
global sum
sum += 1
while low < high:
pos = random.randint(low,high)
a[low],a[pos] = a[pos],a[low]
i,j,t = low,high,a[low]
while i < j: #循环结束时i==j
#因为枢纽元素在最左侧,应先向左扫描,确保最终a[j]<t
while i<j and a[j]>=t: j -= 1
while i<j and a[i]<=t: i += 1
if i<j:
a[i],a[j] = a[j],a[i]
a[j],a[low] = a[low],a[j]
#枢纽元素a[j]已经排好,无需再处理
if low<j-1: qsort_22(a,low,j-1)
low = i+1
'''''
方法三:方法二中向左右扫描时都需要检查下标越界,当i==j时,外层循环结束,为了确保最终a[j]<t,必须先向左扫描。
因为我们已经将枢纽放到了a[low]处,故向左扫描时,可以将枢纽设为“限位标志”,j到达low处时必须停下,
这样就无需检查下标越界,而且扫描方向次序可变。
此外,方法二中交换操作前后都要判断if i<j,有些重复,我们可以将交换操作提到前面,少做一次判断。
为了将交换操作提到前面,我们要先取a[high]为枢纽,然后循环之初就交换左右两端元素,那么在后面的过程中枢纽值仍然是a[low]。
此法还有一个好处,就是确保最终a[i+1,high+1]的值均大于枢纽(a[i]>=t),这样在进行消除尾递归优化后,递归深度大大减少。
但是由于向右扫描时要检查下标,向左扫描时要在枢纽值处停下,总体来说效率不高,速度与方法二接近。
极限情况分析:
t为最小值:1.交换a[i],a[j];i向前一步,j向左扫描到low处停下,因为i>j,外层循环结束。
2.交换a[j],a[low],此时j==low,i==low+1,递归执行qsort_3(a,i,high)。
t为最大值:1.交换a[i],a[j];i向右扫描到high处停下,j不动,因为i==j,外层循环结束。
2.交换a[j],a[low],此时j==high,i==high,递归执行qsort_3(a,low,j-1)。
'''
def qsort_3(a,low,high):
global sum
sum += 1
if low < high:
pos = random.randint(low,high)
a[high],a[pos] = a[pos],a[high]
i,j,t = low,high,a[high]#先取a[high]为枢纽,然后循环之初就交换左右两端元素
while i < j:
a[i],a[j] = a[j],a[i] #将交换操作提到前面,可以少判断一次i < j
while i<j and a[i]<=t: i += 1
while a[j]>t: j -= 1
a[j],a[low] = a[low],a[j]
#枢纽元素a[j]已经排好,无需再处理
if low<j-1: qsort_3(a,low,j-1)
if i<high: qsort_3(a,i,high)
'''
对qsort_3采取消除尾递归优化
'''
def qsort_32(a,low,high):
global sum
sum += 1
while low < high:
pos = random.randint(low,high)
a[high],a[pos] = a[pos],a[high]
i,j,t = low,high,a[high]#先取a[high]为枢纽,然后循环之初就交换左右两端元素
while i < j:
a[i],a[j] = a[j],a[i] #将交换操作提到前面,可以少判断一次i < j
while i<j and a[i]<=t: i += 1
while a[j]>t: j -= 1
a[j],a[low] = a[low],a[j]
#枢纽元素a[j]已经排好,无需再处理
if i<high: qsort_32(a,i,high)
high = j-1
'''''
方法四:方法二和方法三都对枢纽进行了定位,以枢纽元素为界,将原序列分为左右两个子序列,递归执行的子序列中都不包含枢纽元素。
假设存在一种这样的特殊情况:序列中存在大量与枢纽等值的元素,且这些元素连续分布在枢纽两侧,则这些元素已经排好序,不应划入左右子序列中再次递归排序。
此法在方法二的基础上进行了改进,扩大了i,j的扫描范围,使其能够跨过枢纽右侧与其等值的元素,减少下层递归的子序列长度,
所以要分别以high和low为扫描边界,即内层循环时i可以大于j,但外层循环检测到i>j时,循环结束。
此法特别适合等值元素多的数组,与方法一互为补充。
极限情况分析:
t为最小值:1.i向前一步,j向左扫描到low处停下,因为i>j,外层循环结束。
2.交换a[j],a[low],此时j==low,i==low+1,递归执行qsort_4(a,i,high)。
t为最大值:1.i向右扫描到high+1处停下,j不动,因为i>j,外层循环结束。
2.交换a[j],a[low],此时j==high,i==high+1,递归执行qsort_4(a,low,j-1)。
'''
def qsort_4(a,low,high):
global sum
sum += 1
if low < high:
pos = random.randint(low,high)
a[low],a[pos] = a[pos],a[low]
i,j,t = low,high,a[low]
while i<=j:
while i<=high and a[i]<=t: i += 1
while low<j and a[j]>=t: j -= 1
if i<j : #不可能出现i==j的情况
a[i],a[j] = a[j],a[i]
i += 1
j -= 1
a[low],a[j] = a[j],a[low]
#因为a[j:i]的值均等于枢纽,无需再排序
if low<j-1: qsort_4(a,low,j-1)
if i<high: qsort_4(a,i,high)
'''
对qsort_4采取消除尾递归优化
'''
def qsort_42(a,low,high):
global sum
sum += 1
while low < high:
pos = random.randint(low,high)
a[low],a[pos] = a[pos],a[low]
i,j,t = low,high,a[low]
while i<=j:
while i<=high and a[i]<=t: i += 1
while low<j and a[j]>=t: j -= 1
if i<j : #不可能出现i==j的情况
a[i],a[j] = a[j],a[i]
i += 1
j -= 1
a[low],a[j] = a[j],a[low]
#因为a[j:i]的值均等于枢纽,无需再排序
if low<j-1: qsort_42(a,low,j-1)
low = i
a = list(range(3))
for i in range(9):
a.extend(a)
a = list(range(20000))
print(len(a))
random.shuffle(a)
b1 = a[:]
b = a[:]
#a.sort()
t0 = time.clock()
b.sort()
t1 = time.clock()
print(t1 - t0)
sum=0
t0 = time.clock()
qsort_2(b1,0,len(a)-1)
t1 = time.clock()
print(t1 - t0)
print('s = ',sum)
if b == b1:print('ggg')
else: print('fff')
快速排序算法(python)
最新推荐文章于 2024-04-29 03:05:26 发布