算法学习之:Lomuto 分区算法 + 快速排序算法

算法原理

  • 分区就是通过确定一个基准值,把一组数分成两个部分。所有比基准值小的数据放在一边,另外的数据放在另外一边。
  • Lomuto 算法将第一个数据设为基准值,然后向后比对,如果有比基准值小的数据,就交换到前面,当遍历完成后,把基准值插入到分界的位置。

代码

def LomutoPartition(A, lo, hi):
    p = A[lo]
    s = lo
    for i in range(lo + 1, hi + 1):
        if A[i] < p:
            s = s + 1
            A[s], A[i] = A[i], A[s]
    A[s], A[lo] = A[lo], A[s]
  • 代码中,p 是基准值,s 是一个索引位置。一开始 s 和 lo 都指向第一个位置,然后如果数组中有出现比 基准值小的数据,就让它和 s + 1 的位置的数据进行交换,
    在这里插入图片描述
  • 当然如果觉得每次都放在 s+1 的位置很不习惯的话,还可以略作调整,写成下面的样子:
def LomutoPartition(A, lo, hi):

    p = A[lo]
    s = lo + 1
    for i in range(lo + 1, hi + 1):
        if A[i] < p:
            A[s], A[i] = A[i], A[s]
            s = s + 1
    A[s-1], A[lo] = A[lo], A[s-1]
  • 让 s 从 lo+1 的位置开始,当 i 对应的位置有比基准小的值,就交换 A[s] 和 A[i] 的值,然后再把 s 后移一位,但是这种情况在遍历完 i 的时候,将基准值插入数组合适位置的时候需要执行 A[s-1] 和 A[lo] 交换

基于 lomuto 的快速排序算法

由于快速分区算法进行一次,就可以把一个数组分成两个区,基准值用来分隔这两个区,因此,我们使用快排算法来完成序列的排序的时候,只需要递归调用lomuto算法,即可,因此,我们只需要确定递归的出口和每次递归缩小的规模即可:

  • 递归出口就是 lo >= hi,因为一个序列最终不断地规模缩小到只有一个元素的时候,我们默认他是有序的,因此,当一个只有一个元素的序列存在时,lo == hi
  • 递归每次缩小的规模,每次递归要完成对一个序列左右两边都进行 lomuto,所以,我们可以在 lomuto 算法的基础上稍作修改,得到快排算法:
def quicksort(A,lo,hi):
    if lo < hi:
        p = A[lo]
        s = lo
        for i in range(lo + 1, hi + 1):
            if A[i] < p:
                s = s + 1
                A[s], A[i] = A[i], A[s]
        A[s], A[lo] = A[lo], A[s]
        quicksort(A,lo,s-1)
        quicksort(A,s+1,hi)
    return A

def test():
    array = [3, 1, 4, 5, 9, 2, 6, 8]
    print(quicksort(array,0,len(array)-1))
    
if __name__ == "__main__":
    test()

拓展:基于左右两边指针的快速排序算法

这种快速排序算法,基于左右两个指针进行:

  • 当左右两个指针不相遇的时候,左指针向右移动,右指针向左移动,右指针经过的每一个数都与基准值进行比较,如果发现比基准值小的数值,那么右指针就停在这个值上,同样的,左指针遇到比基准值大的数值,也停止移动

  • 这个时候,左右指针都停下的时候,判断他们是否停在了同一个位置,如果不是同一个位置,那么就交换左右指针对应值的位置,即把比基准小的数据放在左边,比基准大的放在右边;然后继续重复进行上面的操作。

  • 不太好理解的是下面这段代码中的比较符号:
    在这里插入图片描述

  • 先来说第三行的 a[left] <= pivot 这条限制,是当有和基准相等的值的时候,统一放在左边

  • 那第一行的 right 为什么要 >= left 呢?在 left 和 right 相遇之前,假设现在 left 已经停住了,那么代表现在 left 停的位置的数值一定是大于基准值的(因为left 指针只有遇到大于基准的数才会停下来)而且下一个 right 进行循环之后一定会完成对整个序列的遍历,那么这个时候如果 right 不遍历等于 left 的时候,就会出现如下情况:夹在他们中间的这个大于基准的值没有被移到右侧。因此,当 right 的下标等于 left 下标的时候仍然需要向左移动一位,此时 交换 left 和 right 中的值,可以保证把最后一个大于 基准的值移动到右侧,

  • 而此时,由于 right 在 left 左侧,因此把基准的值和 right 指针位置的值进行交换

  • L < R 的时候一直进行递归操作,因此递归返回的出口就是 L >= R 的时候,把整个序列进行返回


def quicksort_(a,L,R):
    if L < R:
        left = L
        right = R
        pivot = a[L]
        while left < right:
            while right >= left and a[right] > pivot:
                right -= 1
            while left < right and a[left] <= pivot:
                left += 1
            a[left], a[right] = a[right], a[left]
        a[left],a[right] = a[right], a[left]
        a[right],a[L] = a[L], a[right]
        quicksort_(a,L,right-1)
        quicksort_(a,right+1,R)
    return a
if __name__ == '__main__':

    a = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]
    print(quicksort_(a,0,len(a)-1))

在这里插入图片描述

建议

还是使用 lomuto 的方法比较容易理解也不容易犯错误。

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖仔会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值