总是觉得快排很奇妙,快排的思想无非就是划分.脑子里面也可以形成一张动态图,然后快排的实现却没有一个统一的说法,没事的时候练练快排也算是有收获.
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)改为取最右端做主元.