python快速排序算法集锦

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import random  
import time
  
''''' 
快速排序算法 ”在各种基于关键码比较的内排序算法中,快速排序是实践中平均速度最快的算法之一。
快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),
然后分别对这两个子序列分别快速排序,以达到整个序列有序。根据划分算法的不同,快速排序有许多不同的实现方法,本文将给出一些常见版本并分析其优化过程,抓住问题的本质。
说明:选取适当的枢纽元尽可能将数组划分为长度相当的两个子序列,是快速排序高效的关键。为了表达的方便,我们简单的选取a[left]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。
'''
#快速排序的驱动程序
def quick_sort(lst):
    qsort_7(lst, 0, len(lst)-1)

def quick_sort2(lst):
    qsort_8(lst, 0, len(lst)-1)
''''' 
方法一:单向扫描划分数组。使用两个下标i和j,分别指向最后一个小记录的下标和第一个未处理记录的下标。
优点:思路清晰,代码简洁易懂。
缺点:只向一个方向扫描,每遇到小记录元素就与下标i所指的大记录元素交换位置,不能将该大记录元素一步到位交换到右侧的正确位置,导致某些大记录元素需要交换多次才能到达正确位置。
一个极端的例子,a=[5,6,4,2,3,1],值为6的元素被交换了4次,才最终到达正确位置,都快赶上冒泡排序的低效了。
'''
def qsort_1(a,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] #将枢纽元放到正确位置

    qsort_1(a, left, i-1)
    qsort_1(a, i+1, right)
    

''''' 
方法二:双向扫描划分数组。方法一中被交换的大记录元素不能一步到位交换到右侧的正确位置,导致出现重复交换。
我们可以通过从数组的两端交替向中间扫描,每扫描一次就把“不合群”的元素交换到另一侧的方法,确保每个位置的元素最多只被交换一次,这样效率要高于方法一。
为了保证小记录元素小于枢纽元,我们需要选取a[right]作为枢纽元,这样向右扫描时才不会越界。
'''
def qsort_2(a,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] #将枢纽元放到正确位置

    qsort_2(a, left, i-1)
    qsort_2(a, i+1, right)
    

''''' 
方法三:双向扫描划分数组。方法二中采取先扫描,再交换的方法,每次交换前都要先判断是否满足i < j,交换完毕后,又要再次判断是否满足i < j,以决定是否进入循环。
这样重复判断,效率较低。我们将交换操作放到扫描之前,可以少做一次判断。我们开始选取a[left]作为枢纽元,经过第一次交换操作后,枢纽元到达right位置。
'''
def qsort_3(a,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] #将枢纽元放到正确位置

    qsort_3(a, left, i-1)
    qsort_3(a, i+1, right)
    

''''' 
方法四:双向扫描划分数组。方法三中小记录元素小于枢纽元,大记录元素大于等于枢纽元,这样两个子序列长度相差可能会很大,效率降低;
另外向右扫描时每遇到与枢纽元等值的元素就要停下来,等待交换,扫描效率不高。
我们在方法三的基础上对算法进行改进,使得小记录元素也可以与枢纽元等值,以提高划分的效率。
因为选取了a[right]作为枢纽元(虽然开始枢纽元是a[left],但是经过第一次交换后,枢纽元到达right位置),为了保证最终能将枢纽元放到正确位置,我们需要先向右扫描,再向左扫描。
一个例子,a=[5,1,4,2,6,3],经过第一次交换后变成a=[3,1,4,2,6,5],若先向左扫描,则一趟排序后变成错误序列a=[3,1,4,5,6,2];只有先向右扫描,才能得到正确序列a=[3,1,4,2,5,6]。
提问:如果是在方法二的基础上对算法进行改进,又该先向哪个方向扫描呢?
'''
def qsort_4(a,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 i < j and a[i] <= p: #先向右扫描
            i += 1
        while i < j and a[j] >= p: #再向左扫描
            j -= 1
                
    a[i],a[right] = a[right],a[i] #将枢纽元放到正确位置

    qsort_4(a, left, i-1)
    qsort_4(a, i+1, right)
    

''''' 
方法五:双向扫描划分数组。方法三和方法四都是使用先交换再扫描的技巧来减少一次对i < j的判断,提高了效率;但交换操作本身需要执行2个指令(用c语言实现的话需要3个指令),还可以改进。
我们采用填补空位的方法,用覆盖旧值代替交换操作,可进一步提高效率。一开始把最左侧的枢纽元提取出来,则最左侧出现空位。向左扫描时把第一次遇到的小记录元素放到左侧的空位上,则该小记录
处成为一个新的空位;然后向右扫描,把第一次遇到的大记录元素放到右侧的空位上,则该大记录所在处右成为一个新的空位。如此左右交替扫描,不断填补空位,并产生新的空位。
最后形成的空位即枢纽元的最终位置。
'''
def qsort_5(a,left,right):
    global sum
    sum += 1
    if left >= right: return

    p = a[left] #选取a[left]作为枢纽元,也即初始空位
    i,j = left,right
    while i < j:
        while i < j and a[j] >= p: #先向左扫描
            j -= 1
        if i < j:
            a[i] = a[j] #填补空位,a[j]变成新的空位
            i += 1  #i右移一位,以避免重复判断
            
        while i < j and a[i] <= p: #再向右扫描
            i += 1
        if i < j:
            a[j] = a[i] #填补空位,a[i]变成新的空位
            j -= 1  #j左移一位,以避免重复判断
                
    a[i] = p #枢纽元填补最后一个空位

    qsort_5(a, left, i-1)
    qsort_5(a, i+1, right)

''''' 
方法六:前面的方法都对枢纽元进行了定位,以枢纽元为界,将原序列分为左右两个子序列,递归执行的子序列中都不包含枢纽元。 
假设存在一种这样的特殊情况:序列中存在大量与枢纽元等值的元素,且这些元素连续分布在枢纽元两侧,则这些元素已经排好序,不应划入左右子序列中再次递归排序。 
此法在方法二的基础上进行了改进,扩大了i,j的扫描范围,使其能够跨过枢纽右侧与其等值的元素,减少下层递归的子序列长度,
即内层循环时i可以大于j,但外层循环检测到i>j时,循环结束。
'''  
def qsort_6(a,left,right):
    global sum
    sum += 1
    if left >= right: return

    p = a[left] #选取a[left]作为枢纽元 
    i,j = left,right
    while i <= j: #因为最后一次扫描时i和j可能指向同一位置,故等号不能少
        while i <= right and a[i] <= p: #先向右扫描,极端情况i>right,即不存在比p小的值
            i += 1
        while j > left and a[j] >= p: #再向左扫描,极端情况j=left,即不存在比p大的值
            j -= 1
        if i < j : #交换完毕,i,j均向前一步,是本算法的高效之处
            a[i],a[j] = a[j],a[i]  
            i += 1  
            j -= 1  
                
    a[left],a[j] = a[j],a[left] #将枢纽元放到正确位置 
    #因为a[j:i]的值均等于枢纽,无需再排序
    qsort_6(a, left, j-1)
    qsort_6(a, i, right)

'''
方法七:前面的方法都对枢纽元进行了定位,以枢纽元为界,将原序列分为左右两个子序列,递归执行的子序列中都不包含枢纽元。
方法六更是可以跳过出多个连续的枢纽元,以最大限度地减少子序列的长度。
有一种另类的快速排序算法,它只获取了枢纽元的值,但未对枢纽元进行定位,故排序结束后枢纽元位置不确定。
为避免当枢纽元为序列中最值时,会单向扫描整个数组而不做任何处理,出现无限递归,应将枢纽元作为“限位标志”,
扫描时遇到枢纽元就停下,并在做完交换操作后,i,j均向前一步。这样无需对下标进行越界检查,而且确保一趟排序后j<i,子序列长度至少比原序列少1。
优点:思路清晰,代码简洁易懂。直接将枢纽元作为“限位标志”,扫描时无需检查下标是否越界,扫描速度快,效率高。
缺点:扫描时每遇到枢纽元就停下来进行交换操作,导致大量的重复交换,当等值元素较多时效率偏低。
未对枢纽元进行定位,一轮扫描后无法得知枢纽元的最终位置,不适用于通过快速排序算法来确定第k大元素。
'''
def qsort_7(a,left,right):
    global sum
    sum += 1
    if left >= right: return

    p = a[left] #选取a[left]作为枢纽元
    i,j = left,right
    while i <= j: #注意下标相等时也要扫描,确保最终j<i
        while a[i] < p:
            i += 1
        while a[j] > p:
            j -= 1
        if i <= j:  #即使a[i]==a[j]==p也要进行交换操作,是本算法的一个缺陷
            a[i],a[j] = a[j],a[i]
            i += 1 #交换完毕,i,j均向前一步,是本算法的高效之处
            j -= 1
                
    qsort_7(a, left, j)
    qsort_7(a, i, right)

'''
方法八:前面七种方法的区别主要在于划分算法不一样,我们通过改变扫描方式和用填补空位代替交换等技巧,从理论上对划分算法进行了优化,但快速排序的框架基本不变。
在实际应用中,我们还可以对快速排序算法进行如下三个方面的优化:
1.选取更适合的枢纽元:前面我们为了算法表达的方便,基本上是选取a[left]作为枢纽元,这样很可能造成划分的不平衡,而导致时间复杂度变坏。
我们可以采用随机产生枢纽元位置,或使用数组左端,右端和中心位置上的三个元素的中值作为枢纽元的方法进行优化。
2.利用迭代消除尾递归:前面我们对左右两个子序列都进行了递归处理,这样做代码虽然简单,但是效率低下。为减少递归深度,我们选择较短的子序列递归,另一个则迭代处理。
3.对小数组直接插入排序:对于很小的数组(LIMIT<20),快速排序不如插入排序好。
所以我们可以采取一个这样的策略:若数组长度小于LIMIT,采用直接插入排序,否则采用快速排序。
前面七种方法均可以选择部分或全部上述三种优化方式,以提供排序的速度,我们以方法七为例,给出优化后的算法实现。
温馨提示:从实际测试效果来看,进行所谓算法优化后,速度也可能反而更慢,原因可能是有太多的选择语句,而且生成随机数本身也需要时间。
'''
def qsort_8(a,left,right):
    global sum
    sum += 1
    LIMIT = 20
    if right - left > LIMIT:
        while left < right: #用迭代代替递归
            pos = random.randint(left,right) #随机产生枢纽元位置
            a[left],a[pos] = a[pos],a[left] #将枢纽元交换到left位置
            p = a[left] #选取a[left]作为枢纽元
            i,j = left,right  
            while i <= j: #注意下标相等时也要扫描,确保最终j<i
                while a[i] < p:
                    i += 1
                while a[j] > p:
                    j -= 1
                if i <= j:  #即使a[i]==a[j]==p也要进行交换操作,是本算法的一个缺陷
                    a[i],a[j] = a[j],a[i]
                    i += 1 #交换完毕,i,j均向前一步,是本算法的高效之处
                    j -= 1
                    
            if j-left < right-i: #左子序列更短,则递归处理左子序列,迭代处理右子序列
                qsort_8(a, left, j) 
                left = i             
            else:                #否则递归处理较短的右子序列,迭代处理较长的左子序列
                qsort_8(a, i, right) 
                right = j             
    else: #直接插入排序
        for i in range(left+1,right+1):
            p = a[i]
            j = i
            while j > left and a[j-1] > p:
                a[j] = a[j-1]
                j -= 1
            a[j] = p

def qsort_9(a,left,right):
    global sum
    sum += 1
    LIMIT = 20
    if right - left > LIMIT:
        while left < right: #用迭代代替递归
            pos = random.randint(left,right) #随机产生枢纽元位置
            a[left],a[pos] = a[pos],a[left] #将枢纽元交换到left位置
            p = a[left] #选取a[left]作为枢纽元
            i,j = left,right
            while i <= j: #因为最后一次扫描时i和j可能指向同一位置,故等号不能少
                while i <= right and a[i] <= p: #先向右扫描,极端情况i>right,即不存在比p小的值
                    i += 1
                while j > left and a[j] >= p: #再向左扫描,极端情况j=left,即不存在比p大的值
                    j -= 1
                if i < j : #交换完毕,i,j均向前一步,是本算法的高效之处
                    a[i],a[j] = a[j],a[i]  
                    i += 1  
                    j -= 1  
                        
            a[left],a[j] = a[j],a[left] #将枢纽元放到正确位置
            
            if j-left < right-i: #左子序列更短,则递归处理左子序列,迭代处理右子序列
                qsort_9(a, left, j-1) 
                left = i             
            else:                #否则递归处理较短的右子序列,迭代处理较长的左子序列
                qsort_9(a, i, right) 
                right = j - 1  
    else: #直接插入排序
        for i in range(left+1,right+1):
            p = a[i]
            j = i
            while j > left and a[j-1] > p:
                a[j] = a[j-1]
                j -= 1
            a[j] = p

def qsort_10(a,left,right):
    global sum
    sum += 1
    LIMIT = 20
    if right - left > LIMIT:
        while left < right: #用迭代代替递归
            pos = random.randint(left,right) #随机产生枢纽元位置
            a[left],a[pos] = a[pos],a[left] #将枢纽元交换到left位置
            p = a[left] #选取a[left]作为枢纽元,也即初始空位
            i,j = left,right
            while i < j:
                while i < j and a[j] >= p: #先向左扫描
                    j -= 1
                if i < j:
                    a[i] = a[j] #填补空位,a[j]变成新的空位
                    i += 1  #i右移一位,以避免重复判断
                    
                while i < j and a[i] <= p: #再向右扫描
                    i += 1
                if i < j:
                    a[j] = a[i] #填补空位,a[i]变成新的空位
                    j -= 1  #j左移一位,以避免重复判断
                        
            a[i] = p #枢纽元填补最后一个空位
            
            if i-left < right-i: #左子序列更短,则递归处理左子序列,迭代处理右子序列
                qsort_10(a, left, i-1) 
                left = i + 1            
            else:                #否则递归处理较短的右子序列,迭代处理较长的左子序列
                qsort_10(a, i+1, right) 
                right = i - 1  
    else: #直接插入排序
        for i in range(left+1,right+1):
            p = a[i]
            j = i
            while j > left and a[j-1] > p:
                a[j] = a[j-1]
                j -= 1
            a[j] = p      
    
        
   
a = list(range(5))  
for i in range(9):  
    a.extend(a)  
a = list(range(30000))  
print(len(a))  
  
random.shuffle(a)
b1 = a[:]
b2 = a[:]
b3 = a[:]

#a.sort()  
t0 = time.clock()  
b1.sort()
t1 = time.clock()
print(t1 - t0)

sum = 0
t0 = time.clock()  
quick_sort(b2)
t1 = time.clock()
print(t1 - t0)
if b1 == b2:print('ggg')
else: print('fff')
print sum

sum = 0
t0 = time.clock()  
quick_sort2(b3)
t1 = time.clock()
print(t1 - t0)
if b1 == b3:print('ggg')
else: print('fff')
print sum

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值