python快速排序递归与非递归

写在前面

众所周知,快速排序相对于选择排序,插入排序,冒泡排序等初级排序有着天然的优势。这是因为快排在交换元素的过程中,两个发生交换的元素,距离较远。比如插入排序,新的元素要在已经有序的序列中,一次又一次地找到它应该处于的位置,交换的次数远远高于快排。但是,使用快排时,要特别的小心,尤其是它的边界条件设置,还有就是重复元素比较多的情况。

快速排序的递归函数

这里我们使用快排的最常见的递归函数,这里要注意一下,要先写退出递归的条件,一些小细节还是要注意一下的。

def QuickSort(lo,hi):
    if lo >= hi:
        return
    j = partition(lo,hi)
    QuickSort(lo,j-1)
    QuickSort(j+1,hi)

快排的切分函数

切分函数很重要,是快速排序的精髓。以下是python实现。
由于python里没有java,C++中a[++i]这种骚操作,因此不能照着Algorithm那本书那么写,要稍微做一些改动,其实主要就是边界条件。特殊情况下,如果切分元素是数组中最大或者最小的那个元素,就要小心别让扫描指针跑出数组的边界。实际上,第二个内循环边界条件(j>lo)是多余的,因为j递减到lo+1,若继续进入循环,再减到lo时,已经不满足进入循环的条件了,此时a[j]=v,循环自动退出,因此这个边界条件冗余。这都是在后期发现的,刚开始学习的话以防万一可以加上,熟练之后就可以去掉了。Algorithm中的java实现请点击链接

def partition(lo,hi):
    i = lo+1
    j = hi
    v = a[lo]
    while 1:
        while (i<hi)and(a[i]<=v):#使用i++必须要及时使用哨兵,使用<=跳过重复元素,防止重复元素之间交换形成死循环                              
            i += 1
        while (j>lo)and(a[j]>v):
            j -= 1   
        if i >= j:
            break
        
        a[i],a[j] = a[j],a[i]
    a[lo],a[j]=a[j],a[lo]
    return j

快排的非递归函数

这里手动维护一个下压栈来存储待排序数组的切分指针以及头尾指针

def QuickSort(lo,hi):
    stack = []
    stack.insert(0,lo)#头指针入栈
    stack.insert(0,hi)#尾指针入栈
    while(len(stack)!= 0):#若栈不为空
        hi= stack.pop(0)#头指针出栈
        lo = stack.pop(0)#尾指针出栈
        j = partition(lo,hi)#当返回切分指针,发现子数组长度为1,则无法入栈
        if lo<j-1:
            stack.insert(0,lo)
            stack.insert(0,j-1)
        elif j+1<hi:
            stack.insert(0,j+1)
            stack.insert(0,hi)

以下是非递归的入栈出栈图
在这里插入图片描述

  • 图1中lo和hi指针入栈,然后出栈
    在这里插入图片描述
  • 图2中切分指针两边的左右子数组分别入栈,然后释放右子数组的头尾指针

在这里插入图片描述

  • 图3中接着入栈4个指针,j’是下一次切分的指针。持续的出栈,入栈过程,意味着右子数组不断细化,不断被切小,而切分指针不断入栈。当子数组长度为1时,栈容纳的指针数量达到了最大,partition函数返回的切分指针j已经不能入栈了,所以下压栈开始了释放过程,一旦子数组长度不是1,那么意味着还有切分指针入栈的余地,于是切分指针继续入栈。不断重复这个过程。
  • 总的来说,栈从空到满溢的过程中,拿别人的多,给别人的少。在满溢到空的过程中,下压栈给别人的多,拿别人的少。我们可以把这种过程看做是股市的跌涨。在牛市的时候,虽然个别股是下跌的,但是整个股市形势一片大好啊,人们不停的注资,撤资的少。但是到达了股市的顶点,整个形势就不好了。虽然还是有人往里注资,但撤资的人数高于注资的人。

完整的源代码

import random
import datetime
#切分函数
def partition(lo,hi):
    i = lo+1
    j = hi
    v = a[lo]
    while 1:
        while (i<hi)and(a[i]<=v):#使用i++必须要及时使用哨兵,使用<=跳过重复元素,防止重复元素之间交换形成死循环                              
            i += 1
        while (j>lo)and(a[j]>=v):
            j -= 1   
        if i >= j:
            break
        
        a[i],a[j] = a[j],a[i]
    a[lo],a[j]=a[j],a[lo]
    return j
    
#递归
def QuickSort(lo,hi):
    if lo >= hi:
        return
    j = partition(lo,hi)
    QuickSort(lo,j-1)
    QuickSort(j+1,hi)
#非递归   
def QuickSort(lo,hi):
    stack = []
    stack.insert(0,lo)#头指针入栈
    stack.insert(0,hi)#尾指针入栈
    while(len(stack)!= 0):#若栈不为空
        hi= stack.pop(0)#头指针出栈
        lo = stack.pop(0)#尾指针出栈
        j = partition(lo,hi)#当返回切分指针,发现子数组长度为1,则无法入栈
        if lo<j-1:
            stack.insert(0,lo)
            stack.insert(0,j-1)
        elif j+1<hi:
            stack.insert(0,j+1)
            stack.insert(0,hi)
            
if __name__ =='__main__':
    a = []
#    random.seed(12345)
    for i in range(10):
        a.append(random.randint(0,10))
    random.shuffle(a)
    lo = 0
    hi = len(a)-1
    print(a)
    time1 = datetime.datetime.now()
    QuickSort(lo,hi)
    time2 = datetime.datetime.now()
    duration = time2-time1
    print(duration)
    print(a)            

快排很容易出错,在单元测试的时候,大家不妨用random函数多试几次,来验证自己写的快排是否正确,尤其是切分元素在数组中还有重复的元素,以及一些边界条件,有没有等于之类的情况

猜你喜欢

转载自blog.csdn.net/qq_34859243/article/details/83146941