python快速排序的各种实现方法

总是觉得快排很奇妙,快排的思想无非就是划分.脑子里面也可以形成一张动态图,然后快排的实现却没有一个统一的说法,没事的时候练练快排也算是有收获.

1.常用方法
坐在马桶上看算法:快速排序
这篇文章的思路挺清晰的,但是其实步骤上存在一些小问题,用来帮助理解快排的原理即可,实际实现时逻辑上有一些小的差异.
步骤归纳:
* 排序数组为a,游标left从a的最左边开始,游标right从最右边开始
* 把pivot记为数组第一个数(即a[left]),作为数组左右的分界值
* 从右边开始向左移动游标(right = right - 1)直到找到第一个小于pivot的数,存储在a[left]当中
* 从左边开始向右移动游标(left = left + 1)直到找到第一个大于pivot的数,存储在a[right]当中
* 重复上述两步直到两个游标相遇
* 将pivot放置到a[left]位置上
* 随后递归地将pivot左右两部分分别排序

def quicksort(lists,low,high):
    if low < high:
        l = low
        r = high
        pivot = lists[l]
        while l < r:
            while l < r and lists[r] >= pivot:
                r -= 1
            lists[l] = lists[r]
            while l < r and lists[l] <= pivot:
                l += 1
            lists[r] = lists[l]
        lists[l] = pivot
        quicksort(lists, low, l-1)
        quicksort(lists, l + 1,high)
    return lists

2018.05.25 更新
随机挑选pivot元素 更符合实际场景中数组大部分已排好序的场景(不易退化为O(n^2)复杂度).但产生随机数的过程会增加时间消耗,有相应的解决方法(如,提前生成一张随机表,每次在表中选取实现伪随机)

def ranqsort(a, low, high):
    if low < high:
        l, r = low, high
        #random and swap
        rand = random.randint(low,high)
        a[rand], a[l] = a[l], a[rand]
        p = a[l]
        while l < r:
            while l < r and a[r] >= p:
                r -= 1
            a[l] = a[r]
            while l < r and a[l] <= p:
                l += 1
            a[r] = a[l]
        a[l] = p
        qsort(a, low, l-1)
        qsort(a, l+1, high)

知识关联
可变类型与不可变类型:
写递归最怕的是return的处理,处理得不好就会产生无限递归.在这里要理解一下python中的可变类型和不可变类型,可变类型包括list,dict和set等,不可变类型包括string,int,tuple等.数据类型的可变与不可变决定了他们在函数中传值方式的不同,在python中不必要再像c++中一样记忆各种引用和指针传值,只要记得不可变类型传值是在函数中又拷贝了一份,而可变类型传值在函数中的修改都将直接改变变量本身.
回到我们快排算法中来,因为list是可变类型,因此我们不必考虑返回值的问题,只需要按照下标将传入的list完成排序即可

2.更pythonic一点的写法

def pythonicqs(a):
    if len(a) <= 1:
        return a
    else:
        pivot = a[0]
        pivots = [n for n in a if n == pivot]
        return pythonicqs([n for n in a if n < pivot]) + pivots + pythonicqs([n for n in a if n > pivot])

这种方式应该更加直观便捷,几乎看代码就能直到排序原理,而且使用了python中列表生成式这个特性,但是从空间复杂度上,增加了额外开销存储数组.时间复杂度上应该是2倍于方法1,但是仍可视为是O(NlogN)

## 效率比较
import time
a = [random.randint(0,1000) for _ in range(100)]
import time
t = time.clock()
quicksort(a,0,len(a)-1)
print('Time consumed:',time.clock()-t)
Time consumed: 0.0004520551756286295
a = [random.randint(0,1000) for _ in range(100)]
t = time.clock()
pythonicqs(a)
print('Time consumed:',time.clock()-t)
Time consumed: 0.0005797025805804878

粗略地测试一个长度为100的数组,可以看到基本处于同一个数量级上,而方法2确实比方法1所用的时间略长.
方法2虽然更直观,但是逻辑步骤是有区别的,方法1应该更通用一些,只要稍作修改就是一个JAVA版本、C++版本,面试中遇到应该把方法1记为答案,方法2可作为python面试的加分项.

3.算法导论上对于快排的实现(2018.05.25更新)

def algqsort(a, l ,r):
    if l < r:
        p = a[r]
        i = l - 1
        for j in range(l,r):
            if a[j] <= p:
                i += 1
                a[i],a[j] = a[j], a[i] #未找到右边起第一个大于p的数之前,i==j 相当于不改变
        i += 1
        a[i],a[r] = a[r],a[i]
        algqsort(a,l,i-1)
        algqsort(a,i+1,r)

算是一个聪明又方便的方式. 主要区别在于取最左端做主元(pivot)改为取最右端做主元.

猜你喜欢

转载自blog.csdn.net/m0_37422289/article/details/79273560