【算法日积月累】6-快速排序

要学习快速排序,首先要理解的一个重要操作是 Partition。

理解 Partition

Partition 其实并不难理解,它的基本思想如下所述。

Partition 每一次排定一个元素,这个元素不像选择排序或者冒泡排序那样排在数组的开头或者末尾,可以在数组的任何位置,既然是排定它,那么它就要在数组最终确定的位置上,所以它具有这样的性质:位于它前面的元素都小于它,位于它后面的元素都大于等于它。

例如数组是 [4,6,2,3,1,5,7,8]。我们首先选择数组的第 1 个元素 4 。经过 Partition 以后,我们要达到的是这样一个效果:[2,3,1,4,6,5,7,8],这个数组有什么特点呢?

快速排序-1

1、我们刚开始选定的元素 4 就放在了最终它应该放在的位置上;

2、元素 4 之前的所有元素都比它小,严格说应该是“不大于” 4,并且它们的相对顺序和 Partition 以前一致;

3、对称地,元素 4 之后的所有元素都比它大,严格说应该是“不小于” 4,并且它们的相对顺序和 Partition 以前一致。

这是怎么做到的呢?其实很简单,我们就拿 4 作为“基准元素”,在剩下的数组元素中扫一遍,比 4 的“依次”拿出来形成“列表1”,比 4 大的“依次”拿出来形成“列表2”,那么 [列表1, 元素 4, 列表2 ] 就是上面的结果。在 Python 中,你可以很轻松地实现这个操作。

Python 代码:

def partition(nums):
    pivot = nums[0]
    return [i for i in nums[1:] if i < pivot] + [pivot] + [i for i in nums[1:] if i >= pivot]


if __name__ == '__main__':
    nums = [4, 3, 1, 2, 7, 8, 5]
    result = partition(nums)
    print(result)

而快速排序是这样的:

Python 代码:

def quick_sort(nums):
    if len(nums) <= 1:
        return nums
    pivot = nums[0]
    left = [num for num in nums[1:] if num <= pivot]
    right = [num for num in nums[1:] if num > pivot]
    return quick_sort(left) + [p] + quick_sort(right)

如果你没有看懂这里 Python 的语法,也没有关系。Python 就是这么神奇,看起来像伪代码一样,但它真的可以运行。快速排序的逻辑在上面这段代码中也很清晰了:每次排定一个元素,因为排定以后满足上面提到的性质2和性质3,然后对这个元素的左边和右边再递归进行下去。是不是看到了“分治”的影子?只不过他不像归并排序那样,最后要“合”起来。因为一开始,我们不是“无脑地”一分为二,我们做了上面的性质2和性质3,这一点要仔细体会。

当然,我们不会这么做,因为这样做其实是使用了辅助的空间。实际上,我们可以通过交换数组的元素达到同样的效果。这个操作是非常经典的,是需要在理解的基础上记忆的方法。

快速排序-2

Python 代码:

def partition(nums, left, right):
    pivot = nums[left]
    j = left
    for i in range(left + 1, right + 1):
        if nums[i] < pivot:
            j += 1
            nums[j], nums[i] = nums[i], nums[j]
    nums[left], nums[j] = nums[j], nums[left]
    return j

在编写这个方法的过程中,我们使用了两个“指针”变量:i 和 j。i 用于遍历,表示我要把除了 pivot 的剩下所有数组元素全部看完。而 j 维护了一个循环不变的性质,就是区间 [left,j) 内的元素都严格小于 pivot,这是代码编写的关键之处,注意这里 j 并不包含,所以初始值设置为 left,区间 [left,left) 内的元素为空,这也是合理的。接下来,只要遇到比 pivot 小的元素,就交换到 j 这个位置,因此 j + 1 就表示下一个要交换到这里的元素的位置。把数组中剩下的元素全部看过一遍以后,再把 j 和 left 交换就可以了。

最后,我们要把 j 返回回去,供快速排序后序调用。

刚开始接触的时候,可能会比较 (๑•ᴗ•๑) ,要反复看,用几个小数组手写一遍程序运行的过程。并且在自己编写的代码中添加一些打印输出,观察自己写的代码是不是按照自己认为的那个逻辑在运行

例如,我在编写代码的时候,就会以注释的形式写上我理解的 partition 过程。

快速排序-3

我们再画一个简单的图来加深对“大放过,小操作”这个过程的理解。

快速排序-4

下面是我以前用 Java 写的时候的笔记:

快速排序-5

在理解了 Partition 的基础,我们不难写出第 1 版快速排序的代码。

编写第 1 版快速排序

Python 代码:

def partition(nums, left, right):
    pivot = nums[left]
    j = left
    for i in range(left + 1, right + 1):
        if nums[i] < pivot:
            j += 1
            nums[j], nums[i] = nums[i], nums[j]
    nums[left], nums[j] = nums[j], nums[left]
    return j


def __quick_sort(nums, left, right):
    if left >= right:
        return
    p_idx = partition(nums, left, right)
    __quick_sort(nums, left, p_idx - 1)
    __quick_sort(nums, p_idx + 1, right)


def quick_sort(nums):
    __quick_sort(nums, 0, len(nums) - 1)

写好以后,不妨用前面我们介绍的方法,自己写一个生成随机数组的方法,再写一个判断数组是否有序的方法,来检验我们自己编写的排序算法是否正确。

我可以,你也一定可以!

快速排序-6

看到这样的输出,心里很欣慰,居然写对了!

下面是我自己编写的测试代码,供参考,你们完全可以写出比我写的更好的代码。

快速排序-7
快速排序-8
快速排序-9

下面这个方法看起来有点“傻”。

快速排序-10

本文源代码

Python:代码文件夹,Java:代码文件夹

(本节完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • “算法”的入门,我们从“排序算法”开始,希望通过“排序算法”这一部分的学习,能够让我们认识到“算法”的威力。“算法...
    李威威阅读 919评论 0 0
  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an 输...
    code武阅读 775评论 0 0
  • 前面,我们看到了“选择排序”虽然可以完成排序任务,但是有点死板。而我们在这一节介绍的“插入排序”是一个比“选择排序...
    李威威阅读 383评论 0 0
  • 十大经典排序算法 来源:https://github.com/wangguanfu/-Sorting-algori...
    星丶雲阅读 851评论 0 7
  • 原味姑苏群讨论地名,施晓平说,被人诟病的是,有一条路起名叫“春耀路”,许多人读来读去感觉是“春药路”。 有人说,最...
    黄叶村人阅读 563评论 2 2

友情链接更多精彩内容