python3实现快速排序算法图文详解

快速排序做为一种比较经典的排序思路,经常在面试中被提及。这一节就分解下快排的思路,最后用代码实现一下。

快速排序

假设有下面一个数组需要排序

[38,67,11,26,20,99,45,54]

思路就是从第一个元素38开始,找到一个中间位置,将比38小的元素都放在左边,比38大的元素都放在右边。然后再分别对左右两边的子序列进行同样的操作,最后达到整体有序。

为了达到这个目的,我们只要弄清楚了单次排序的实现方法,后面的子序列只需要采用递归的方式就可以依法炮制了。

下面来看看单次排序的思路。

首先需要两个游标,一个low游标指向第一个元素,也就是待定位元素,另一个high游标指向最后一个元素。如下图所示

1-start.png

首先让high游标向左移动,一直到找到比38小(先不考虑等于)的元素,赋值给low游标。如下图所示,就是将20赋值给第一个元素

2-1st.png

然后让low游标向右移动,一直找到比38大的元素,赋值给high游标。如下图所示,就是将67赋值给high所在的元素

3-2nd.png

如此循环,在移动high游标,将26赋值给low游标的元素

4-3rd.png

直到low和high两个游标重合,将38放到该处

5-finish.png

这样就实现了38左边都比38小,右边都比38大的效果。

之后采用递归地思路,将左边的[20,26,11]对20采用该方法,对右边的[67,99,45,54]的67也采用该方法。然后再分为4个子序列重复。

代码实现

下面的代码基于python3,首先实现单次排序。

单次排序

根据上面的理论,可以写出单次排序的代码如下

def fast_sort(list_):
    mid_value = list_[0]
    low = 0
    high = len(list_) - 1

    while low < high:
        while low < high and list_[high] >= mid_value:
            high -= 1
        list_[low] = list_[high]
        while low < high and list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    list_[low] = mid_value

验证下

if __name__ == '__main__':
    list_ = [38, 67, 11, 26, 20, 99, 45, 54]
    print(list_)
    fast_sort(list_)
    print(list_)

打印结果为

[38, 67, 11, 26, 20, 99, 45, 54]
[20, 26, 11, 38, 67, 99, 45, 54]

内层循环的low<high的判断是否多余?

不多余,因为有可能在内层的某个循环中就出现low>high的情况,例如

def fast_sort(list_):
    mid_value = list_[0]
    low = 0
    high = len(list_) - 1

    while low < high:
        while list_[high] > mid_value:
            high -= 1
        list_[low] = list_[high]
        while list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    print(low, high)  # 这里打印下low和high
    list_[low] = mid_value


if __name__ == '__main__':
    list_ = [38, 67, 11, 26, 20, 99, 45, 54]
    print(list_)
    fast_sort(list_)
    print(list_)

此时的打印结果为

[38, 67, 11, 26, 20, 99, 45, 54]
4 3
[20, 26, 11, 67, 38, 99, 45, 54]

可以看到最后low为4而high为3,并且最后的结果也不对。是因为在最后一步中,low一直从26的地方经过11,又经过了high指向的26。所以有必要在内部的两个循环也分别加上限制条件。

这也给我们提了个醒,在循环内对循环退出变量做除了简单+1操作以外的复杂操作时要格外小心,记得做判断退出循环。

等于待定位元素的情况怎么处理?

这里我分别用了list_[high] >= mid_valuelist_[low] < mid_value做为循环条件,也就是说high游标遇到相等的值会直接跳过继续往左,而low游标则会等待直到交换。最终的结果就是和待定位元素相等的值都去了中间位置的右边。当然不加这个等号或是上下都加等号也是没问题的,因为反正最终几个值得顺序都会被打乱,肯定是不稳定的,所以没有区别,这里只是为了实现都到一边的目的才加了一个等号。

递归

下面要开始处理递归,如下,利用开头的if语句退出递归,最后的两个函数调用自身分别对左右两个子序列进行操作

def fast_sort(list_, first, last):
    if first >= last:
        return
    mid_value = list_[first]
    low = first
    high = last

    while low < high:
        while low < high and list_[high] >= mid_value:
            high -= 1
        list_[low] = list_[high]
        while low < high and list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    list_[low] = mid_value

    fast_sort(list_, first, low - 1)
    fast_sort(list_, low + 1, last)

验证下

if __name__ == '__main__':
    list_ = [38, 67, 11, 26, 20, 99, 45, 54]
    print(list_)
    fast_sort(list_, 0, len(list_) - 1)
    print(list_)

结果正确

[38, 67, 11, 26, 20, 99, 45, 54]
[11, 20, 26, 38, 45, 54, 67, 99]

为什么多加两个参数而不是直接传递切片?

因为这里是对原list直接进行操作,切片之后返回的是新list,和原list的地址不一致,所以不能直接操作,只能间接赋值,如下也能达到目的

def fast_sort(list_):
    mid_value = list_[0]
    low = 0
    high = len(list_) - 1

    while low < high:
        while low < high and list_[high] >= mid_value:
            high -= 1
        list_[low] = list_[high]
        while low < high and list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    list_[low] = mid_value

    if low > 0:
        list_[0:low] = fast_sort(list_[0:low])
    if low < len(list_) - 1:
        list_[low + 1:] = fast_sort(list_[low + 1:])

    return list_

注意这里最后要进行返回。同时这里要考虑特殊情况,就是单边的子序列只有一个元素的时候,这时候最后的两个if都不满足,直接原样返回退出递归。

同样去验证下

if __name__ == '__main__':
    list_ = [38, 67, 11, 26, 20, 99, 45, 54]
    print(list_)
    # fast_sort(list_, 0, len(list_) - 1)
    fast_sort(list_)
    print(list_)

返回的结果是一样的

[38, 67, 11, 26, 20, 99, 45, 54]
[11, 20, 26, 38, 45, 54, 67, 99]

退出递归的大于号有必要吗?

有必要,因为当最后low和high都在最边上的元素时,会出现类似first为0而last为-1的情况,或者是first为len(list_)而last为len(list_)-1的情况。要对这些情况直接退出递归不做任何操作。

时间复杂度

最好情况

最好情况就是每次从中点对半分,每次子序列是原先的一般,一直到最后长度为1。所以总的遍历次数m满足

n*(1/2)^m = 1,最后m=log2n。而每次都遍历n次,所以时间复杂度为O(nlogn)

最坏情况

最坏情况就是每次都在最边上的元素,一共遍历n遍,每次n个元素。时间复杂度为O(n^2)

稳定性

从前面的分析可以看出来,无论代码如何去设定相等值的处理,原先的顺序都会被打乱,所以快速排列一定是不稳定的。

完整代码

这里我将第二种传切片的方式注释起来,以供参考

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time: 2020-07-10
# @Author: xiaofu

# def fast_sort(list_):
#     # if len(list_) == 1:
#     #     return list_
#     mid_value = list_[0]
#     low = 0
#     high = len(list_) - 1
#
#     while low < high:
#         while low < high and list_[high] >= mid_value:
#             high -= 1
#         list_[low] = list_[high]
#         while low < high and list_[low] < mid_value:
#             low += 1
#         list_[high] = list_[low]
#     # print(low, high)
#     list_[low] = mid_value
#
#     if low > 0:
#         list_[0:low] = fast_sort(list_[0:low])
#     if low < len(list_) - 1:
#         list_[low + 1:] = fast_sort(list_[low + 1:])
#
#     return list_


def fast_sort(list_, first, last):
    if first >= last:
        return
    mid_value = list_[first]
    low = first
    high = last

    while low < high:
        while low < high and list_[high] >= mid_value:
            high -= 1
        list_[low] = list_[high]
        while low < high and list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    # print(low, high)
    list_[low] = mid_value

    fast_sort(list_, first, low - 1)
    fast_sort(list_, low + 1, last)


if __name__ == '__main__':
    list_ = [38, 67, 11, 26, 20, 99, 45, 54]
    print(list_)
    fast_sort(list_, 0, len(list_) - 1)
    # fast_sort(list_)
    print(list_)

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值