算法浅谈——分治算法与归并、快速排序(附代码和动图演示)
off999 2024-10-04 00:36 21 浏览 0 评论
在之前的文章当中,我们通过海盗分金币问题详细讲解了递归方法。
我们可以认为在递归的过程当中,我们通过函数自己调用自己,将大问题转化成了小问题,因此简化了编码以及建模。今天这篇文章呢,就正式和大家聊一聊将大问题简化成小问题的分治算法的经典使用场景——排序。
排序算法
排序算法有很多,很多博文都有总结,号称有十大经典的排序算法。我们信手拈来就可以说上来很多,比如插入排序、选择排序、桶排序、希尔排序、快速排序、归并排序等等。老实讲这么多排序算法,但我们实际工作中并不会用到那么多,凡是高级语言都有自带的排序工具,我们直接调用就好。为了应付面试以及提升自己算法能力呢,用到的也就那么几种。今天我们来介绍一下利用分治思想实现的两种经典排序算法——归并排序与快速排序。
归并排序
我们先来讲归并排序,归并排序的思路其实很简单,说白了只有一句话:两个有序数组归并的复杂度是 O(n) 。
我们举个例子:
a = [1, 4, 6]
b = [2, 4, 5]
c = []
我们用i和j分别表示a和b两个数组的下标,c表示归并之后的数组,显然一开始的时候i, j = 0, 0。我们不停地比较a和b数组i和j位置大小关系,将小的那个数填入c。
填入一个数之后:
i = 1
j = 0
a = [1, 4, 6]
b = [2, 4, 5]
c = [1]
填入两个数之后:
i = 1
j = 1
a = [1, 4, 6]
b = [2, 4, 5]
c = [1, 2]
我们重复以上步骤,直到a和b数组当中所有的数都填入c数组为止,我们可以很方便地写出以上操作的代码:
从上面的代码我们也能看出来,这个过程虽然简单,但是写成代码非常麻烦,因为我们需要判断数组是否已经全部填入的情况。这里有一个简化代码的优化,就是在a和b两个数组当中插入一个”标兵“,这个标兵设置成正无穷大的数,这样当a数组当中其他元素都弹出之后。由于标兵大于b数组当中除了标兵之外其他所有的数,就可以保证a数组永远不会越界,如此就可以简化很多代码了(前提,a和b数组当中不存在和标兵一样大的数)。
我们来看代码:
这里应该都没有问题,接下来的问题是我们怎么利用归并数组的操作来排序呢?
其实很简单,这也是归并排序的精髓。
我们每次将一个数组一分为二,显然,这个划分出来的数组不一定是有序的。但如果我们继续切分呢?直到数组当中只有一个元素的时候,是不是就天然有序了呢?
我们举个例子:
[4, 1, 3, 2]
/ \
[4, 1] [3, 2]
/ \ / \
[4] [1] [3] [2]
\ / \ /
[1, 4] [2, 3]
\ /
[1, 2, 3, 4]
通过上面的这个过程我们可以发现,在归并排序的时候,我们先一直往下递归切分数组,直到所有的切片当中只有一个元素天然有序。接着一层一层地归并回来,当所有元素归并结束的时候,数组就完成了排序。这也就是归并排序的全部过程。
如果还不理解,还可以参考一下下面的动图。
我们来试着用代码来实现。之前我曾经在面试的时候被要求在白板上写过归并排序,当时我用的C++觉得编码还有一定的难度。现在,当我用习惯了Python之后,我感觉编码难度降低了很多。因为Python支持许多数组相关的高级操作,比如切片,变长等等。整个归并排序的代码不超过20行,我们一起来看下代码:
你看,无论是思想还是代码实现,归并排序并不难,就算一开始不熟悉,写个两遍也一定没问题了。
理解了归并排序之后,再来学快速排序就不难了,我们一起来看快速排序的算法原理。
快速排序
快速排序同样利用了分治的思想,我们每次做一个小的操作,让数组的一部分变得有序,之后我们通过递归,将这些有序的部分组合在一起,达到整体有序。
在归并排序当中,我们划分问题的方法是横向切分,我们直接将数组一分为二,针对这两个部分分别排序。快排稍稍不同,它并不是针对数组的横向切分,而是从问题本身出发的”纵向“切分。在快速排序当中,我们解决的子问题不是对数组的一部分排序,而是提升数组的有序程度。怎么提升呢?
我们在数组当中寻找一个数,作为标杆,我们利用这个标杆调整数组当中元素的顺序。将小于它的放到它的左侧,大于它的放到它的右侧。这么一个操作结束之后,可以肯定的是,这个标杆所在的位置就是排序完成之后,它应该在的位置。
我们来看个例子:
a = [8, 4, 3, 9, 10, 2, 7]
我们选择7作为标杆,一轮操作之后可以得到:
a = [2, 4, 3, 7, 9, 10, 8]
接着我们怎么做呢?很简单,我们只需要对标杆前面以及标杆后面的部分递归调用重复上述操作即可。如果还不明白的同学可以看一下下面这张动图:
如果用C++写过快排的同学肯定对于快排的代码印象深刻,它是属于典型的原理不难,但是写起来很麻烦的算法。因为快速排序需要用到两个下标,写的时候一不小心很容易写出bug。同样,由于Python当中动态数组的支持非常好,我们可以避免使用下标来实现快排,这样代码的可读性以及编码难度都要降低很多。
多说无益,我们来看代码:
整个代码除去注释,不到15行,我想大家应该都非常容易理解。
最后,我们来分析一下这两个算法的复杂度,为什么说这两个算法都是 O(nlogn) 的算法呢?(不考虑快速排序最差情况)这个证明非常简单,我们放一张图大家一看就明白了:
我们在递归的过程当中,我们只遍历了一遍数组,虽然我们每一层都会讲数组拆分。但是在递归树上同一层的递归函数遍历的总数加起来应该是等于数组的总长也就是n的。
而且递归的层数是有限制的,因为我们每次都将数组一分为二。而一个数组的最小长度是1,也就是说极端情况下我们一共能有 log2n 层,每一层的复杂度总和是n,所以整体的复杂度是 nlogn。
当然对于快速排序算法来说,如果数组是倒序的,我们默认取最后一个元素作为标杆的话,我们是无法切分数组的,因为除它之外所有的元素都比它大。在这种情况下算法的复杂度会退化到 n^2 。所以我们说快速排序算法最差复杂度是 n^2 。
到这里,关于归并排序与快速排序的算法就讲完了。这两个算法并不难,我想学过算法和数据结构的同学应该都有印象,但是在实际面试当中,真正能把代码写出来并且没有明显bug的实在是不多。我想,不论之前是否已经学会了,回顾一下都是很有必要的吧。
今天的文章就到这里,希望大家有所收获。如果喜欢本文,请顺手点个关注吧。
相关推荐
- 使用 python-fire 快速构建 CLI_如何搭建python项目架构
-
命令行应用程序是开发人员最好的朋友。想快速完成某事?只需敲击几下键盘,您就已经拥有了想要的东西。Python是许多开发人员在需要快速组合某些东西时选择的第一语言。但是我们拼凑起来的东西在大多数时候并...
- Python 闭包:从底层逻辑到实战避坑,附安全防护指南
-
一、闭包到底是什么?你可以把闭包理解成一个"带记忆的函数"。它诞生时会悄悄记下自己周围的变量,哪怕跑到别的地方执行,这些"记忆"也不会丢失。就像有人出门时总会带上...
- 使用Python实现九九乘法表的打印_用python打印一个九九乘法表
-
任务要求九九乘法表的结构如下:1×1=11×2=22×2=41×3=32×3=63×3=9...1×9=92×9=18...9×9=81使用Python编写程序,按照上述格式打印出完整的九...
- 吊打面试官(四)--Java语法基础运算符一文全掌握
-
简介本文介绍了Java运算符相关知识,包含运算规则,运算符使用经验,特殊运算符注意事项等,全文5400字。熟悉了这些内容,在运算符这块就可以吊打面试官了。Java运算符的规则与特性1.贪心规则(Ma...
- Python三目运算基础与进阶_python三目运算符判断三个变量
-
#头条创作挑战赛#Python中你学会了三步运算,你将会省去很多无用的代码,我接下来由基础到进阶的方式讲解Python三目运算基础在Python中,三目运算符也称为条件表达式。它可以通过一行代码实现条...
- Python 中 必须掌握的 20 个核心函数——set()详解
-
set()是Python中用于创建集合的核心函数,集合是一种无序、不重复元素的容器,非常适合用于成员检测、去重和数学集合运算。一、set()的基本用法1.1创建空集合#创建空集合empty_se...
- 15个让Python编码效率翻倍的实用技巧
-
在软件开发领域,代码质量往往比代码数量更重要。本文整理的15个Python编码技巧,源自开发者在真实项目中验证过的工作方法,能够帮助您用更简洁的代码实现更清晰的逻辑。这些技巧覆盖基础语法优化到高级特性...
- 《Python从小白到入门》自学课程目录汇总(和猫妹学Python)
-
小朋友们好,大朋友们好!不知不觉,这套猫妹自学Python基础课程已经结束了,猫妹体会到了水滴石穿的力量。水一直向下滴,时间长了能把石头滴穿。只要坚持不懈,细微之力也能做出很难办的事。就比如咱们的学习...
- 8÷2(2+2) 等于1还是16?国外网友为这道小学数学题吵疯了……
-
近日,国外网友因为一道小学数学题在推特上争得热火朝天。事情的起因是一个推特网友@pjmdoll发布了一条推文,让他的关注者解答一道数学题:Viralmathequationshavebeen...
- Python学不会来打我(21)python表达式知识点汇总
-
在Python中,表达式是由变量、运算符、函数调用等组合而成的语句,用于产生值或执行特定操作。以下是对Python中常见表达式的详细讲解:1.1算术表达式涉及数学运算的表达式。例如:a=5b...
- Python运算符:数学助手,轻松拿咧
-
Python中的运算符就像是生活中的数学助手,帮助我们快速准确地完成这些计算。比如购物时计算总价、做家务时分配任务等。这篇文章就来详细聊聊Python中的各种运算符,并通过实际代码示例帮助你更好地理解...
- Python学不会来打我(17)逻辑运算符的使用方法与使用场景
-
在Python编程中,逻辑运算符(LogicalOperators)是用于组合多个条件表达式的关键工具。它们可以将多个布尔表达式连接起来,形成更复杂的判断逻辑,并返回一个布尔值(True或Fa...
- Python编程基础:运算符的优先级_python中的运算符优先级问题
-
多个运算符同时出现在一个表达式中时,先执行哪个,后执行哪个,这就涉及运算符的优先级。如数学表达式,有+、-、×、÷、()等,优先级顺序是()、×、÷、+、-,如5+(5-3)×4÷2,先计算(5-3)...
- Python运算符与表达式_python中运算符&的功能
-
一、运算符分类总览1.Python运算符全景图2.运算符优先级表表1.3.1Python运算符优先级(从高到低)优先级运算符描述结合性1**指数右→左2~+-位非/一元加减右→左3*//...
- Python操作Excel:从基础到高级的深度实践
-
Python凭借其丰富的库生态系统,已成为自动化处理Excel数据的强大工具。本文将深入探讨五个关键领域,通过实际代码示例展示如何利用Python进行高效的Excel操作,涵盖数据处理、格式控制、可视...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 使用 python-fire 快速构建 CLI_如何搭建python项目架构
- Python 闭包:从底层逻辑到实战避坑,附安全防护指南
- 使用Python实现九九乘法表的打印_用python打印一个九九乘法表
- 吊打面试官(四)--Java语法基础运算符一文全掌握
- Python三目运算基础与进阶_python三目运算符判断三个变量
- Python 中 必须掌握的 20 个核心函数——set()详解
- 15个让Python编码效率翻倍的实用技巧
- 《Python从小白到入门》自学课程目录汇总(和猫妹学Python)
- 8÷2(2+2) 等于1还是16?国外网友为这道小学数学题吵疯了……
- Python学不会来打我(21)python表达式知识点汇总
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)