快排是在面试时一定会被问到的算法,但是笔者总是记不住,画个图来帮助自己记忆,希望也能帮到同样记性不好的小朋友。
1.快排的基本思想
是冒泡排序法的升级,属于交换排序
基本思想:通过一趟排序将待排序记录分割成独立的两部分(枢纽值的左边部分和右边部分),其中一部分的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序的目的
2.快排的图解
大家都知道,枢纽值的选取对快排的时间复杂度至关重要,如果枢纽值选的不好,最终就会生成一颗严重倾斜的二叉树,造成时间复杂度的下降,为了降低理解难度,我们先笨笨的选择每次递归进来的最左边的点作为枢纽值点。
先放代码,再进行讲解(先声明,代码并非原创,但是时间太久忘记当时是那个大佬的代码帮助笔者终于理解了快排算法,默默感谢一波)
def Partition(l, left, right):
#先笨笨的选择枢纽值为列表头位的值
pivot = l[left]
#将枢纽值记录下来,因为后面会被覆盖
#代码动态图解参见大话数据结构,优化不必要的交换部分,采用覆盖的形式
while left < right:
while left < right and l[right] >= pivot:
right = right - 1
l[left] = l[right]
while left < right and l[left] <= pivot:
left = left + 1
l[right] = l[left]
#最后right,left指针共同指向的地方的值已经去覆盖了上一个值,这个位置就是枢纽值的位置了
l[left] = pivot
return left
def QSort(l,left,right):
if left < right:
pivotindex = Partition(l,left,right)
QSort(l, left, pivotindex-1)
QSort(l, pivotindex+1, right)
def QuickSort(l):
QSort(l,0,len(l)-1)
Partition函数中最难理解的部分就是如何利用枢纽值作为经典交换中的tmp值,这样在交换两个数的时候就可以采取覆盖的形式,优化不必要的交换。
当left指针和right指针指向相同位置时,此处正好是枢纽值的位置,用枢纽值替换left,并返回left位置,由此可进入下一次递归。
采用三数取中法选择枢纽值可以很好的避免递归树的倾斜问题
还是先放代码:
def DealPivot(l,left,right):
mid = l[(left+right)//2]
# 三个数的排序方法是,先保证左边的数是最小的,再将后两个数比较后看要不要交换
if l[left] > l[mid]:
l[left],l[mid] = l[mid],l[left]
if l[left] > l[right]:
l[left],l[right] = l[right],l[left]
if l[mid] > l[right]:
l[mid],l[right] = l[right],l[mid]
# 将mid移动到左边第二位
l[left+1],l[mid] = l[mid],l[left+1]
return
def Partition2(l, left, right):
# 利用三数取中法选择枢纽值可以很好的避免递归数的倾斜问题
# 为了与Partition1中的方法保持一致
# 找出枢纽值后将枢纽值放置在左边+1的位置
DealPivot(l,left,right)
left = left + 1
pivot = l[left]
while left < right:
while left < right and l[right] >= pivot:
right = right - 1
l[left] = l[right]
while left < right and l[left] <= pivot:
left = left + 1
l[right] = l[left]
#最后right,left指针共同指向的地方的值已经去覆盖了上一个值,这个位置就是枢纽值的位置了
l[left] = pivot
return left
def QSort(l,left,right):
if left < right:
pivotindex = Partition2(l,left,right)
QSort(l, left, pivotindex-1)
QSort(l, pivotindex+1, right)
def QuickSort(l):
QSort(l,0,len(l)-1)
此图片参考于图解排序算法(五)之快速排序——三数取中法
先利用三数取中法找到枢纽值,并将枢纽值放置在左边第二个位置,此后将left指针指向枢纽值,就当当当~变成了上面简单的形式~!
3.Partiton函数的妙用
3.1 寻找第k小/大的数
因为每次Partition函数之后枢纽值的位置就确定了!!!,如果枢纽值的位置索引与K相等,说明他就是排序后应该在第K位置的数啊
def Partition(l, left, right):
pivot = l[left]
while left < right:
while left < right and l[right] >= pivot:
right -= 1
l[left] = l[right]
while left < right and l[left] <= pivot:
left += 1
l[right] = l[left]
l[left] = pivot
return left
def Recursion(l,left,right,k):
pivotindex = Partition(l,left,right)
if pivotindex == k:
return l[pivotindex]
elif pivotindex > k:
return Recursion(l,left,pivotindex-1,k)
else:
return Recursion(l,pivotindex+1,right,k)
def FindKth(l,k):
kth = Recursion(l,0,len(l)-1,k)
return kth
print(FindKth([2,3,4,5],0))