快排作为面试过程中的常考题,有必要好好整理以下。快排与我前面写的归并排序一样,都采用了分治策略。但是它不使用额外的存储空间,不过代价是,列表有可能不会一分为二(这个我们留到后面算法分析时具体说一下)。

快排原理:首先选定一个基准值,基准值的作用就是帮助列表进行切分。也就是将该基准值作为列表的分割点,分割点的左部分都小余基准值,右部分都大余基准值。

看代码比较来的实际:

def quicksort(alist, L, R):
    if L<R:#设置结束递归的条件
        mid=partition(alist, L, R)#该操作就是选择一个基准值,并对其进行左右排序
        quicksort(alist, 0, mid-1)#列表的左子部分
        quicksort(alist, mid+1, R)#列表的右子补分

def partition(alist, left, right):
    value=alist[left] #为了好说明情况,我们选择最左边作为基准值
    #定义两个表量分别指向列表的最左端和最右端
    l=left
    r=right

    while l<r:
        #当我们设置最左端为value时,应先从最右边开始扫描,反之亦然。
        while l<r and alist[r]>=value:#如果右部分的值小于基准值,往左移动
            r -=1
        # 当有值小于基准值时,直接覆盖到左部分的对应位置。因为左边第一个值作为基准值并赋给了变量value
        #就相当与左边空了一个位置出来,可以用来装填右部分小于基准值的元素。另外一边同理。
        alist[l]=alist[r]
        while l<r and alist[l]<=value:
            l +=1
        alist[r]=alist[l]
    # 最后我们将value填充到alist[r],此时,alist[r]左部分都小于value,有右分都大于等于value
    alist[r]=value
    #返回分割点,将左右两部分又继续进行分割。
    return r

#获取一个整形列表
nums = list(map(int, input().split()))
#进行快排
quicksort(nums, 0, len(nums)-1)
print(nums)

算法分析:快排的切分跟前面说到的归并排序一样,都需要进行

快排 python实现_算法分析

次切分,为了找到分割点,n个元素都要与基准值比较,所以时间复杂度为

快排 python实现_快排_02

.前面我们提到,快排不需要申请额外的空间,但是可能会出现列表不能一分为二的情况(排序之前列表已经排好序),这个时候就相当与做了一个选择排序,此时,时间复杂度就变成了

快排 python实现_快排 python实现_03


那有没有改进方法勒?必须的,作为一个想找工作的人,必须把这些安排的明明白白:

有人提出该随机获得一个low与high之间的数作为基准值,这样就不容易出现这样的情况,这被称为随机选取枢轴法。应该说,这在某种程度上,解决了对于基本有序的序列快速排序时的性能瓶颈。

import random
def quicksort(alist, L, R):
    if L<R:#设置结束递归的条件
        mid=partition(alist, L, R)#该操作就是选择一个基准值,并对其进行左右排序
        quicksort(alist, 0, mid-1)#列表的左子部分
        quicksort(alist, mid+1, R)#列表的右子补分

def partition(alist, left, right):
    temp=random.randint(left, right)#首先随机产生一个left到right的整数
    alist[temp],alist[left]=alist[left],alist[temp] #将随机产生的下标对应的值与alist[left]交换
    value=alist[left] #为了好说明情况,我们选择最左边作为基准值
    #定义两个表量分别指向列表的最左端和最右端
    l=left
    r=right

    while l<r:
        #当我们设置最左端为value时,应先从最右边开始扫描,反之亦然。
        while l<r and alist[r]>=value:#如果右部分的值小于基准值,往左移动
            r -=1
        # 当有值小于基准值时,直接覆盖到左部分的对应位置。因为左边第一个值作为基准值并赋给了变量value
        #就相当与左边空了一个位置出来,可以用来装填右部分小于基准值的元素。另外一边同理。
        alist[l]=alist[r]
        while l<r and alist[l]<=value:
            l +=1
        alist[r]=alist[l]
    # 最后我们将value填充到alist[r],此时,alist[r]左部分都小于value,有右分都大于等于value
    alist[r]=value
    #返回分割点,将左右两部分又继续进行分割。
    return r

#获取一个整形列表
nums = list(map(int, input().split()))
#进行快排
quicksort(nums, 0, len(nums)-1)
print(nums)

但是,随机数就是一个概率事件,要是我们产生的数对应的值很小或者很大了,一样也不能解决最坏复杂度的问题。

再改进,于是就有了三数取中(median-of-three)法。即取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数,也可以随机选取。这样至少这个中间数一定不会是最小或者最大的数,从概率来说,取三个数均为最小或最大数的可能性是微乎其微的,因此中间数位于较为中间的值的可能性就大大提高了。由于整个序列是无序状态,随机选取三个数和从左中右端取三个数其实是一回事,而且随机数生成器本身还会带来时间上的开销,因此随机生成不予考虑。

这个时候我们只需要将随机生成改为选取left、right、mid=(left+(right-left)>>1)中的中间值。将其对应的数组值作为分割值。

#将上面代码中
temp=random.randint(left, right)
alist[temp],alist[left]=alist[left],alist[temp]

修改为:
mid=left+((right-left)>>1)
#两个值进行比较,保证两者直接靠左边的值最小,这样就能保证alist[left]为中间值
if (alist[left]>alist[right]):
    alist[left],alist[right]=alist[right],alist[left]
if (alist[mid]>alist[right]):
    alist[mid],alist[right]=alist[right],alist[mid]
if (alist[mid]>alist[left]):
    alist[mid],alist[left]=alist[left],alist[mid]

其实,这个还可以优化,夜已深,留着下次吧。哈哈啊哈哈哈哈哈。