本题要求实现一个用选择法对整数数组进行简单排序的函数。_通俗易懂讲 Python 算法:快速排序...

1c933af292771279aea23f99c98b37a6.png

原文:https://stackabuse.com/quicksort-in-python/

作者:Marcus Sanatan

译者:老齐

欢迎在 bilibili  搜索 freeCodeCamp 官方账号或者直接访问 https://space.bilibili.com/335505768 观看我们的技术视频

介绍

快速排序是一种流行的排序算法,经常与归并排序一起使用。快速排序是一个高效的排序算法,平均复杂度为 O(nlogn)。它受欢迎的部分原因还在于易于实施。

在本文中,我们将先使用整数集合演示快速排序算法,然后会用自定义对象深入探讨这种算法的实现方式。

在分治法、原地排序和非稳定排序中,都有快速排序算法。

- 在分治法中,对大数组使用递归进行排序之前,使用快速排序算法将数组拆分为更小的数组,直到最后得到一个空数组或只有一个元素的数组。

- 在原地排序中,快速排序不创建数组的任何副本,它在内存中完成所有的操作。

- 稳定排序指相同的值在数组中的顺序也是相同的,非稳定的排序算法不能保证这一点,只能说这样的顺序当然有可能出现,但不能保证。这在针对对象排序而不是对基本类型排序时变得很重要。例如,假设有几个自定义的 Person 类实例具有相同的 age,即 21 岁的 Dave 和 21 岁的 Mike。如果要对包含 Dave 和 Mike 的集合使用快速排序法按年龄排序,则无法保证每次运行算法时 Dave 都会出现在 Mike 之前,反之亦然。

快速排序

快速排序算法的执行流程如下:

- 将集合分成两个(大致)相等的部分,取一个伪随机元素并将其用作主元(注:“主元”,原文中 pivot,在多数介绍快速排序算法的中文资料中,并不对齐单独命名,只简单说成是“分割点”,即划分集合的元素,但本文作者使用了 pivot 这个词来表示“分割点”,译者认为很便于理解和表述,并将其翻译为“主元”)。

- 小于主元的元素将移动到其左侧,大于主元的元素将移动到其的右侧。

- 分别对于主元左侧和右侧的元素,重复此上述,直到对整个数组完成排序。

当我们将某个元素描述为比另一个元素“大”或“小”时,并不一定意味着这些元素是整数,我们可以自定义对象,并根据选择的任何属性进行排序。

假设有一个自定义类 Person,每个实例都有 name 和 age 属性,我们可以按姓名(词典顺序)或按年龄(升序或降序)进行排序。

faed600c03dd3c450a856581a19ed24b.png

快速排序算法的原理

对于快速排序算法,很多时候,数组是不能等分的,这是因为整个过程取决于如何选择主元。我们需要选择一个主元,使其比一半的元素大,比另一半的元素小。尽管这个过程看起来很直观,但很难做到。

想一想:如何为数组选择合适的主元?关于这个问题的解决方法,在快速排序算法的发展史上有过好多种。如果随机选择,是行不通的,因为这不能保障主元是合适的,随机选择的代价会非常“昂贵”。此外,还曾经有人提出从中间选择一个元元素,或者选择第一个、或者后半部分中间的,设置用更复杂的递归公式选择,等等。

最直接的最简单的方法是选择第一个(或最后一个)元素作为主元,具有讽刺意味的是,这会导致快速排序法对已经排序(或几乎排序了)的数组执行效果非常糟糕。

但是,大多数人还是用这种方法来实现快速排序算法,因为它很简单,而且这种选择主元的方式是一种非常有效的操作(我们需要重复执行),这也正是我们下面要做的。

既然我们选择了一个主元,接下来该用它做些什么?同样,有几种方法可以处理各个分区。我们要设置三个指针,有一个指向主元,另外两个分别指向“较小”元素和“较大”元素。

然后移动元素,使所有小于主元的元素都在左侧,而所有较大的元素都在右侧。更小或更大的元素不一定会被排序,我们只希望它们位于主元的适当的一侧。然后,用递归方法遍历主元的左侧和右侧。

一步一步来看看我们计划要做的事情,从而理解以上所说的快速排序算法的执行过程。使用下面显示的数组,我们选择了第一个元素作为主元(29),low 表示指向较小元素的指针,最右边的 high 表示指向较大元素的指针。

- 29 是第一主元,low 指向 99,high 指向 44

29 | 99 (low),27,41,66,28,44,78,87,19,31,76,58,88,83,97,12,21,44 (high)

- high 从右向左移动,直到找到一个小于主元的值

29 | 99 (low),27,41,66,28,44,78,87,19,31,76,58,88,83,97,12,21 (high),44

- 现在 high 指向了 21,它是一个比主元小的元素,我们想在数组的开头附近找到一个值,使之可以用于与 21 交换。用一个比主元还小的值交换是没有意义的,所以如果 low 指向一个更小的元素,我们试着找到一个更大的元素。

- 将 low 变量向右移动,直到找到一个大于主元的元素。幸运的是, low 已经定位在 99,它就比主元大

- 交换 high 和 low 指向的元素:

29 | 21 (low),27,41,66,28,44,78,87,19,31,76,58,88,83,97,12,99 (high),44

- 在我们这样做之后,就将 high 继续向左移动,将 low 向右边移动(21 和 99 现在所处的位置是正确的)。

- 再次,将 high 向左移动,下一个数字就是小于主元的 12。

- 现在我们通过将 low 向右移动来找一个大于主元的值,就是 41 了,它是第一个这样的值

不断重复上述过程,直到 low 指针和 high 指针最终在某个元素相遇:

29 | 21,27,12,19,28 (low/high),44,78,87,66,31,76,58,88,83,97,41,99,44

- 不再使用 29 作为主元了,所以剩下唯一要做的就是交换主元和 high 所指元素 28,然后我们就完成了这个递归步骤:

28,21,27,12,19,29,44,78,87,66,31,76,58,88,83,97,41,99,44

如你所见,我们已经让小于 29 的所有值现在都在 29 的左侧,大于 29 的所有值都在右侧。

然后,算法对 28、21、27、12、19(左侧)集合和44、78、87、66、31、76、58、88、83、97、41、99、44(右侧)集合执行相同的操作。

实现

对数组排序

快速排序是一种自然递归算法,将输入数组分成较小的数组,将元素移动到主元的适当一侧,然后重复。

让我们来看看几个递归调用:

- 当第一次调用算法时,我们考虑所有的元素——从索引 0 到 n-1,其中 n 是数组中的元素数量。

- 如果主元最终位于位置 k,那么我们将对从 0 到 k-1 和从 k+1 到 n-1 的元素重复该过程。

- 在将元素从 k+1 排到 n-1 时,当前主元将在某个位置 p 结束。然后我们将元素从 k+1 序到 p-1,从 p+1 排序到 n-1,依此类推。

也就是说,我们将使用两个函数:partition() 和 quick_sort()。quick_sort() 函数将首先用 partition() 函数对集合分组,然后在每组上递归调用自己。

下面从 partition() 函数开始:

def partition(array, start, end):
    pivot = array[start]
    low = start + 1
    high = end

    while True:
        # If the current value we're looking at is larger than the pivot
        # it's in the right place (right side of pivot) and we can move left,
        # to the next element.
        # We also need to make sure we haven't surpassed the low pointer, since that
        # indicates we have already moved all the elements to their correct side of the pivot
        while low <= high and array[high] >= pivot:
            high = high - 1

        # Opposite process of the one above
        while low <= high and array[low] <= pivot:
            low = low + 1

        # We either found a value for both high and low that is out of order
        # or low is higher than high, in which case we exit the loop
        if low <= high:
            array[low], array[high] = array[high], array[low]
            # The loop continues
        else:
            # We exit out of the loop
            break

    array[start], array[high] = array[high], array[start]

    return high

最后,让我们实现 quick_sort() 函数:

def quick_sort(array, start, end):
    if start >= end:
        return

    p = partition(array, start, end)
    quick_sort(array, start, p-1)
    quick_sort(array, p+1, end)

有了这两个函数,就可以对一个简单的数组执行 quick_sort():

array = [29,99,27,41,66,28,44,78,87,19,31,76,58,88,83,97,12,21,44]

quick_sort(array, 0, len(array) - 1)
print(array)

输出:

[12, 19, 21, 27, 28, 29, 31, 41, 44, 44, 58, 66, 76, 78, 83, 87, 88, 97, 99]

由于此算法是非稳定的,所以不能保证这两个 44 的顺序总是这样的,也许会交换 —— 虽然这在整数数组中意义不大。

对自定义对象进行排序

有几种方法可以重写此算法,以便在 Python 中对自定义对象进行排序。一种非常典型的 Python 方法是实现给定类的比较运算符,这意味着我们实际上不需要更改算法实现,因为 >、=、<= 等也可以应用于类对象。

另一个选择是以参数的方式给函数传入一个比较函数,用这个方法来执行对象的实际比较。用这种方式重写算法函数以用于自定义对象是相当简单的。但是请记住,算法并不稳定。

让我们从 Person 类开始:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return self.name

这是一个非常基本的类,只有两个属性,name 和 age。我们希望使用 age 作为排序键,这将通过为排序算法提供自定义 lambda 函数来实现。

但首先,让我们看看如何在函数中实现算法。我们没有直接使用 <= 或 >= 运算符进行比较,而是在函数中来判断哪个 Person 实例的年龄更高:

def partition(array, start, end, compare_func):
    pivot = array[start]
    low = start + 1
    high = end

    while True:
        while low <= high and compare_fun(array[high], pivot):
            high = high - 1

        while low <= high and not compare_fun(array[low], pivot):
            low = low + 1

        if low <= high:
            array[low], array[high] = array[high], array[low]
        else:
            break

    array[start], array[high] = array[high], array[start]

    return high
def quick_sort(array, start, end, compare_func):
    if start >= end:
        return

    p = partition(array, start, end, compare_func)
    quick_sort(array, start, p-1, compare_func)
    quick_sort(array, p+1, end, compare_func)

现在,让我们对这些实例对象的集合进行排序。你可以看到,在 quick_sort 函数的参数中,有 lambda 函数,它会对 age 属性的值进行实际的比较:

p1 = Person("Dave", 21)
p2 = Person("Jane", 58)
p3 = Person("Matthew", 43)
p4 = Person("Mike", 21)
p5 = Person("Tim", 10)

array = [p1,p2,p3,p4,p5]

quick_sort(array, 0, len(array) - 1, lambda x, y: x.age for person in array:
    print(person)

输出是:

Tim
Dave
Mike
Matthew
Jane

通过这种方式实现算法,只要提供适当的比较函数,它就可以用于我们选择的任何自定义对象。

优化快速排序算法

考虑到快速排序独立地对给定数组的“一半”进行排序,那么它就非常便于实现并行计算。我们可以用一个单独的线程对数组的每个“一半”进行排序,理想情况下,可以将排序所需的时间减半。

但是,如果我们在选择主元时特别不走运,快速排序法可能会递归太深,此时即使用并行计算,其效率也不如归并排序。

对小数组进行排序时,建议使用非递归算法。即使是像插入排序这样的简单操作,在小数组上也比快速排序更有效。因此,理想情况下,我们可以检查排序对象是否只有少量元素(大多数建议是 10 个或更少),如果是这样,就用插入排序代替。

快速排序的一个流行变体是多主元快速排序,它使用 n-1 个主元将原始数组分解为 n 个较小的数组。然而,大多数情况下只使用两个主元,而不是更多。

有趣的事实:Java 7 的排序实现中使用了双主元快速排序,针对较小数组的则是插入排序。

结论

正如我们前面提到的,快速排序的效率很大程度上取决于主元的选择——它可以成就或突破算法时间(和堆栈空间)的复杂度。在使用自定义对象时,算法的非稳定性也是一个潜在的问题。

然而,尽管如此,快速排序的平均时间复杂度 O(nlogn) 和相对低的内存使用、简单的实现方法,使它成为一种非常有效和流行的算法。


推荐阅读:

30 行 Python 代码实现 Twitch 主播上线实时通知

从软件工程专业思考到的大前端技术栈


edbdcd8bd378d1c009af0d2b68753778.png

非营利组织 freeCodeCamp.org 自 2014 年成立以来,以“帮助人们免费学习编程”为使命,创建了大量免费的编程教程,包括交互式课程、视频课程、文章等。线下开发者社区遍布 160 多个国家、2000 多个城市。我们正在帮助全球数百万人学习编程,希望让世界上每个人都有机会获得免费的优质的编程教育资源,成为开发者或者运用编程去解决问题。

你也想成为 freeCodeCamp 社区的贡献者吗?欢迎了解 招募丨freeCodeCamp 翻译计划

点击【在看】,与朋友交流?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/411629.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode标记_高效扩展工具让 VS Code 如虎添翼

Codelf 变量命名神器Star&#xff1a;10688https://github.com/unbug/codelf新建项目&#xff0c;变量&#xff0c;类&#xff0c;方法&#xff0c;接口都需要命名&#xff0c;一个好的命名可以一眼看出这个地方的功能&#xff0c;CodeIf 一键起名不再难&#xff0c;输入关键词…

CSS 有关Position = absolute (绝对定位 是相对于谁而言)

css中有绝对定位法&#xff0c;以前一直搞不懂绝对定位是相对于谁而言的绝对定位。 现在搞清楚了&#xff0c;不是相对于父元素&#xff0c;也不是相对于BODY。 而是相对于所属元素树中&#xff0c;相邻最近的那个显示标识了position属性的元素。 比如 Code<div id"a&q…

mysql gui vim_vim(一): 小技巧

1) 如何yank字符然后再查找 ( / Ctrl - R 0 ) The most recently yanked text will be stored in the 0 and registers (if no register was explicitly specified e.g. by xy ). Then you can paste the text of any that register in the last line (eith1) 如何yank字符然后…

LeetCode Smallest Range

数据范围是3500,3500也就是说n的平方是可以接受的。这里告诉你就是有序的,也就是在提醒你可能会是一个类似于二分的算法,所以的话其实基于这两个认识的话我们就可以利用一个枚举叫二分的算法来解决这道题。怎么做呢&#xff1f;就首先的话我们要枚举一端,一端的话我们可以把所有…

Web前端3.0时代,“程序猿”如何“渡劫升仙”

Web前端入行门槛低&#xff0c;很多人在成为前端工程师后很容易进入工作的舒适区&#xff0c;认为该熟悉的业务已熟悉了&#xff0c;然后就是重复用轮子&#xff0c;这样很容易让自己的成长处于原地打转以及低水平重复的状态。 想要不被行业抛弃&#xff0c;就要努力提升自己。…

mysql用 fifo 记录日志_MySQL一丢丢知识点的了解

1. MySQL体系结构从概念上讲&#xff0c;数据库是文件的集合&#xff0c;是依照某种数据模型组织起来并存放于二级存储器中的数据集合&#xff1b;数据库实例是程序&#xff0c;是位于用户与操作系统之间的一层数据管理软件&#xff0c;用户对数据库数据的任何操作&#xff0c;…

HttpHandler

概述HttpHandler是一个HTTP请求的真正处理中心&#xff0c;也正是在这个HttpHandler容器中&#xff0c;ASP.NET Framework才真正地对客户端请求的服务器页面做出编译和执行&#xff0c;并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。 IHttpHandler是什么 I…

小程序云开发搜索功能的实现正则_码code | 如何借助小程序云开发实现小程序支付功能...

转载来源&#xff1a;编程小石头我们在做小程序支付相关的开发时&#xff0c;总会遇到这些难题。小程序调用微信支付时&#xff0c;必须要有自己的服务器&#xff0c;有自己的备案域名&#xff0c;有自己的后台开发。这就导致我们做小程序支付时的成本很大。本节就来教大家如何…

python接口自动化(二十五)--unittest断言——下(详解)

简介 本篇还是回归到我们最初始的话题&#xff0c;想必大家都忘记了&#xff0c;没关系看这里&#xff1a;传送门 没错最初的话题就是登录&#xff0c;由于博客园的登录机制改变了&#xff0c;本篇以我找到的开源免费的登录API为案例&#xff0c;结合 unittest 框架写 2 个用例…

mysql-bin磁盘满数据库重启不_liunx磁盘空间满了,导致mysql数据库无法启动

如何启动/遏制/重启MySQLA、1、启动圆式1、哄骗 service 启动&#xff1a;service mysqld start2、哄骗 mysqld 脚本启动&#xff1a;/etc/inint.d/mysqld start3、哄骗 safe_mysqld 启动&#xff1a;safe_mysqld&二、遏制1、哄骗 service 启动&#xff1a;service mysqld …

[导入]C# Tips 2 右键单击listBox时弹出右键菜单并选中单击的行

C#,Listbox,右键,菜单,选中文章来源:http://blog.csdn.net/geyunfei_hit/archive/2008/07/16/2661153.aspx 转载于:https://www.cnblogs.com/geyunfei/archive/2008/10/08/1306335.html

python真的可以减少工作强度_用Python写几行代码,一分钟搞定一天工作量,同事直呼:好家伙!...

前几天有一个读者说最近要整理几千份文件&#xff0c;头都要整秃了&#xff0c;不知道能不能用Python解决&#xff0c;我们来看一下&#xff0c;你也可以思考一下。由于涉及文件私密所以具体内容已做脱敏处理。大概是这样&#xff0c;一个文件夹下有多份会议通知信息(本文以 7 …

Jmeter之逻辑控制器(Logic Controller)

一、简单控制器&#xff08;Simple Controller&#xff09;&#xff1a; 作用&#xff1a;这是Jmeter里最简单的一个控制器&#xff0c;它可以让我们组织我们的采样器和其它的逻辑控制器&#xff08;分组功能&#xff09;&#xff0c;提供一个块的结构和控制&#xff0c;并不具…

python需要掌握的词汇量_北大保安英语词汇量1.5万,会用Python编程,孟母三迁真有道理...

这就是良好学习环境的重要性啊&#xff0c;不然你以为“孟母三迁”是咋来的&#xff1f;人家孟母为了孩子有个好的学习环境&#xff0c;宁愿搬三次家。而这些保安小哥一步到位&#xff0c;直接就进入了无数人梦寐以求的最高学府&#xff0c;想不牛都不行啊。01、北大是最高学府…

Visual Studio Team System 2008 安装失败

微软的东西 看样子也没怎么测试就发布了。。。 [10/27/08,23:05:56] Microsoft .NET Framework 3.5: [2] Error code 1603 for this component means "安装时发生严重错误"[10/27/08,23:05:58] Microsoft .NET Framework 3.5: [2] Setup Failed on component Microso…

【题解】序列

题目描述 一个长度为k的整数序列b1&#xff0c;b2&#xff0c;...&#xff0c;bk&#xff08;1≤b1≤b2≤...≤bk≤N&#xff09;称为“好序列”当且仅当后一个数是前一个数的倍数&#xff0c;即bi1是bi的倍数对任意的i&#xff08;1≤i≤k-1&#xff09;成立。 给定N和k&#…

Struts2 文件上传

JSP界面&#xff1a; 必须把表单的enctype属性改为 multipart/form-data才能上传 Action&#xff1a; 程序如果多人使用&#xff0c;必须保证文件名是唯一&#xff0c;文件名相同会覆盖掉原来的文件&#xff0c;所以使用时间作为文件名。可以把生成的文件名直接存入数据库中&am…

看新闻的时间用的太多了

也不知从什么时候开始习惯每天看新闻,好像有五六年的习惯了,只是现在每天看新闻的时间用的太多了(每天都至少5-6小时),而且越来越严重了,除了看新闻,基本只剩下工作和睡觉时间了.cctv2,央视新闻频道,第一财经每天必看,新浪,搜狐,凤凰网,时寒冰,牛刀等等,从时事,到经济,到体育,到…

第五章学习小结

第5章学习树和二叉树 树 1.树的结构定义是一个递归定义&#xff1a;树的定义中又用到树的定义 2.结点的度即为结点的分支数&#xff0c;树的度是树内各结点度的最大值&#xff0c;二叉树每个结点至多只有两颗子树&#xff08;即二叉树中不存在度大于2的结点&#xff09; 二叉树…

[html] 浏览器内多个标签页之间的通信方式有哪些?

[html] 浏览器内多个标签页之间的通信方式有哪些&#xff1f; 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题