算法精解 c语言描述 豆瓣,斯坦福大学教授亲授,这本美亚4.7星的算法书,新手程序员都看得懂!...
原标题:斯坦福大学教授亲授,这本美亚4.7星的算法书,新手程序员都看得懂!
“算法会扩展并提高大家的编程技巧,而学习基本的算法设计范式,可以和许多不同领域的不同问题密切相关,还能作为预测算法性能的工具帮助大家为自己碰到的问题设计新算法。”
——斯坦福大学教授Tim Roughgarden
这句话表达了Tim Roughgarden对算法学习的高度重视,期望算法能帮助程序员更好地编程。为了让更多人学会算法,从2012年开始,Tim Roughgarden就在斯坦福大学教授多年的本科课程的基础之上,定期在YouTobe上更新算法课程,用视频课程这种大家容易接受的方式来介绍算法的基础知识!
没想到,视频课程刚开始不久,就吸引了成千上万的学习者。他们有着不同的年龄、背景、生活方式,但同样的是对算法学习的坚持。对Tim Roughgarden来说,最开心的事情莫过于,他独特的教学方式受到了这些来自全世界各个角落的学生(包括高中生、大学生等)、软件工程师 (包括现在的和未来的)、科学家和专业人员的高度认同。
于是,在粉丝们的强烈建议下,Tim Roughgarden终于决定将程序员必学的基本算法知识汇总成一本书,做一份通俗易懂的算法指南。
虽然计算机算法相关的图书多如牛毛,但Tim 这本《算法详解 卷1》却名列前茅,在美亚获得4.7的星评,豆瓣评分8.6!
今天异步君就是要给大家分享《算法详解(卷1)——算法基础》中的一个经典算法——QuickSort,希望对你们有所帮助!
快速排序
如果询问专业的计算机科学家或者程序员,他们心目中排名前十的算法是哪些,你会发现QuickSort将出现在许多人的答案中。为什么会这样?
从实用的角度看,QuickSort的竞争力较之MergeSort也毫不逊色,在某些方面甚至更加优越。基于这个原因,它是许多程序库的默认排序方法。和MergeSort比较,QuickSort的一个巨大优点是它是原地排序的,它反复交换元素,直接在输入数组中进行操作。由于这个原因,它只需要分配极少的额外内存用于中间计算。从美学的角度看,QuickSort是一种引人注目的优雅算法,并且具有同等优雅的运行时间分析。
排序
QuickSort算法解决了对数组进行排序的问题。
问题:排序
输入:包含n个以任意顺序出现的数的数组。
输出:包含相同数的数组,它们已经按照从小到大的顺序排序。
因此,如果输入数组是
则正确的输出数组是
和我们对MergeSort的讨论一样,简单起见,我们假设输入数组中的元素都是不同的,没有重复的值。
根据基准元素进行划分
QuickSort是围绕一种用于“部分排序”的快速子程序而建立的,这个子程序的任务就是根据一个“基准(pivot)元素”对数组进行划分。
步骤1:选择一个基准元素。首先,从数组中选择一个元素,把它作为基准元素。现在,我们就不假思索地直接使用数组的第一个元素(也就是上面的“3”)作为基准元素。
步骤2:根据这个基准元素重新排列数组。选择了基准元素p之后,下一个任务就是对数组中的元素进行排列,使数组中p之前的所有元素都小于p,在p之后的所有元素都大于p。例如,根据上面的输入数组,下面是一种合理的重新排列元素的方式:
这个例子清楚地说明了基准元素之前的元素并不需要按照正确的顺序放置(图中“1”和“2”的位置放反了),基准元素之后的元素也是如此。这个划分子程序把数组的(非基准)元素放在两个桶里,一个是放置小于基准元素的桶,另一个是放置大于基准元素的桶。
下面是这个划分子程序的两个关键特性。
快速。这种划分子程序具有非常炫目的实现速度,它的运行时间是线性的O(n)。更妙的是这个子程序能够在原地实现,除输入数组所占用的内存之外,不需要再为它分配内存,这也是QuickSort能够成为实用工具的关键原因。
明确的进展。围绕一个基准元素对数组进行划分对数组的排序起到了很大的帮助。首先,基准元素本身处于正确的位置,意味着它在排序之后的输入数组中也是处于相同的位置(所有小于它的元素在它之前,所有大于它的元素在它之后)。其次,这种划分把排序问题分割成两个更小的子问题:对小于基准元素的元素进行排序(它们很方便地在自己的子数组中原地排序)以及对大于基准元素的元素进行排序(也是在它们自己的子数组中原地排序)。
在递归地对这两个子数组中的元素进行排序之后,这个算法就完成了任务!
高级描述
在QuickSort算法的高级描述中,数组的“第一部分”和“第二部分”分别表示小于基准的元素和大于基准的元素:
QuickSort(高级描述)
输入:包含n个不同整数的数组A。
处理结果:A的元素从小到大排序。
if n ≤ 1 then //基本条件—已经排序
return
选择一个基准元素 p // 有待实现
围绕p对A进行划分 // 有待实现
递归地对A的第一部分进行排序
递归地对A的第二部分进行排序
虽然MergeSort和QuickSort都是分冶算法,但它们的操作顺序是不同的。在MergeSort中,首先执行的是递归调用,然后是组合步骤Merge。在QuickSort中,递归调用出现在划分之后,它们的结果并不需要进行组合!
围绕基准元素进行划分
接下来,我们详细讨论怎样围绕基准元素对数组进行划分,也就是对数组进行重新排列,使之看上去像下面这样:
简易方法
如果我们并不介意分配额外的内存,实现线性的划分子程序是相当简单的。一种方法是对输入数组A进行一遍扫描,并把它的非基准元素逐个复制到一个相同长度的新数组B中,小于p的元素复制到数组B的前面,大于p的元素复制到数组B的后面。在处理完了所有的非基准元素之后,就可以把基准元素复制到数组B中剩下的那个位置。对于5.1节的这个输入数组例子,计算过程中间的一个快照如下:
由于这个子程序对于输入数组中n个元素中的每一个元素都只执行O(1)的工作,所以它的运行时间是O(n)。
原地实现:高级计划
围绕一个基准元素划分数组时,怎么才能做到几乎不需要分配额外的内存呢?我们的高级方法就是对数组进行一遍扫描,根据需要交换元素,使数组在这遍扫描之后就正确地完成了划分。
假设基准元素是数组的第一个元素,我们可以在一个预处理步骤中把基准元素与数组的第一个元素进行交换,这个操作可以在O(1)时间内完成。当我们扫描并转换输入数组时,要小心谨慎,确保它具有下面的形式:
也就是说,这个子程序维持下面这个不变性[6]:第一个元素是基准元素,接着是所有已经处理的非基准元素,所有小于基准的元素都在大于基准的所有元素的前面,然后是按任意顺序出现的尚未处理的非基准元素。
如果我们能够成功完成这个计划,那么在线性扫描结束时就完成了对数组的转换,其形式如下:
最后,为了完成划分,我们把基准元素与小于它的最后一个元素进行交换:
例子
接下来,我们通过一个具体的例子来详细讨论原地划分子程序。在看到它的代码之前就在一个程序例子中观察它的执行步骤,这看上去有点奇怪,但是请相信我,这是理解这个子程序的最快捷径。
在我们的高级计划的基础之上,我们希望记录两个边界。其中一个是已经观察的非基准元素和尚未观察的非基准元素之间的边界,另一个是第一组中小于基准的元素和大于基准的元素之间的边界。我们将使用索引j和i来记录这两个边界。我们所期望的不变性可以重新作以下描述。
不变性:基准元素和i之间的所有元素都小于基准元素,i和j之间的所有元素都大于基准元素。
i和j都被初始化为基准元素和剩余元素之间的边界。基准元素和j之间没有元素,此时这个不变性就简单地成立:
在每次迭代时,这个子程序观察一个新元素,并把j的值加1。为了维持这个不变性,可能需要其他工作,也可能不需要。在这个例子中,当我们第一次把j的值加1时,得到下面的结果:
基准元素和i之间没有任何元素,i和j之间的唯一元素(“8”)大于基准元素,所以这个不变性仍然成立。
现在情况变得复杂了。再次把j的值加1之后,i和j之间就又有了一个元素,它(“2”)小于基准元素,这就违反了这个不变性。为了恢复这个不变性,我们把“8”与“2”进行交换,同时把i的值加1,这样i就位于“2”和“8”之间,我们再次明确了已处理元素中小于基准的元素和大于基准的元素之间的边界:
第三次迭代与第一次迭代相似。我们处理下一个元素(“5”)并把j的值加1。由于这个新元素大于基准元素,所以这个不变性仍然成立,不需要再做其他事情:
第四次迭代与第二次迭代相似。把j的值加1就在i和j之间插入了一个小于基准的元素(“1”),这就违背了不变性。但是恢复这个不变性也是非常简单的,只要把“1”与大于基准的第一个元素(“8”)进行交换并把i的值加1,这样就更新了已处理元素中小于基准的元素和大于基准的元素之间的边界:
最后3个迭代过程所处理的元素都大于基准元素,所以在增加j的值之外不需要做其他操作。在所有的元素都被处理并且基准元素之后的所有元素都完成划分之后,我们就可以完成最后一个步骤,把基准元素与小于它的最后一个元素进行交换:
正如要求的那样,在最终的数组中,所有小于基准的元素出现在基准元素之前,所有大于基准的元素出现在基准元素之后。至于“1”和“2”符合顺序则纯属巧合。基准元素之后的元素很显然并不是有序排列的。
Partition子程序的伪码
观察了这个例子之后,Partition子程序的伪码正如我们所期望的那样。
Partition
输入:包含n个不同整数的数组A,左、右端点,且。
处理结果:子数组的元素已经围绕完成了划分。
输出:基准元素的最终位置。
p := A[l]
i := + 1
for j := + 1 to r do
if A[j] < p then // 如果A[j] > p,不需要操作
交换A[j]和A[i]
i := i + 1 // 恢复不变性
交换A[l]和A[i – 1] // 正确放置基准元素
return i – 1 // 报告最终的基准位置
Partition子程序接受输入数组A,但它只对包含元素
的子数组进行操作,其中和r都是特定的参数。后面将会看到,QuickSort的每个递归调用将负责原始输入数组的一个特定的连续子集,而参数和r指定了对应的端点。
例如在这个例子中,索引j记录哪些元素已经被处理,索引i记录已处理元素中小于基准的元素和大于基准的元素之间的边界(使A[i]成为大于基准的元素中最左边的那个,前提是已处理元素中存在大于基准的元素)。for循环的每次迭代处理一个新的元素。类似的,当新元素A[j]大于基准元素时,这个不变性就自动成立,不需要做其他操作。
否则,这个子程序就通过把新元素A[j]与大于基准元素的最左边元素A[i]进行交换以维持这个不变性,然后把i的值加1,更新小于基准的元素和大于基准的元素之间的边界。正如之前所介绍的那样,最后一个步骤就是把基准元素交换到它的正确位置,与小于它的最右边元素进行交换。Partition子程序的最后一步就是把这个位置报告给调用它的QuickSort。
这种实现具有令人炫目的速度。对于相关子数组的每个元素
,它只执行常数级的操作,因此它在子数组上的运行时间是线性的。
重要的是,这个子程序是在原地对子数组进行操作的,除了像i和j这种用于记录位置的变量的O(1)级内存之外,它不需要分配额外的内存。
QuickSort的伪码
现在我们已经完整地描述了QuickSort算法,暂时不理会用于选择基准元素的子程序ChoosePivot的细节。
QuickSort
输入:包含n个不同整数的数组A,左、右端点,且。
处理结果:子数组的元素已经按照从小到大的顺序完成了排序。
if 1≥r then // 0个或1个元素的子数组
return
i := ChoosePivot(A,l , r) // 有待实现
swap A[l] and A[i] // 首先确定基准元素
j := Partition(A;l; r) // j 为新的基准位置
QuickSort(A, l, j – 1) // 对第一部分进行递归操作
QuickSort(A, j + 1; r) // 对第二部分进行递归操作
为了对一个包含n个元素的数组A进行排序,只要调用QuickSort(A, 1, n)就可以了。
算法详解
算法详解(卷1)——算法基础
作者:Tim Roughgarden
译者: 徐波
算法详解 卷1 算法基础
内容简介:
算法详解系列图书共有4卷,本书是第一卷——基础算法。本书共有6章,主要介绍了4个主题,它们分别是渐进性分析和大O表示法、分冶算法和主方法、随机化算法以及排序和选择。附录A和附录B简单介绍了数据归纳法和离散概率的相关知识。本书的每一章均有小测验、章末习题和编程题,这为读者的自我检查以及进一步学习提供了较多的便利。
适合人群:
本书为对算法感兴趣的广大读者提供了丰富而实用的资料,能够帮助读者提升算法思维能力。本书适合计算机专业的高校教师和学生,想要培养和训练算法思维以及计算思维的IT专业人士,以及在准备面试的应聘者和面试官阅读参考。
通过本书你讲得到:
● 成为更优秀的程序员
读者将学习一些令人炫目的用于处理数据的高速子程序以及一些实用的数据结构,它们用于组织数据,并可以直接部署到自己的程序中。实现和使用这些算法将会扩展并提高读者的编程技巧。读者还将学习基本的算法设计范式,它们与许多不同领域的不同问题密切相关,并且可以作为预测算法性能的工具。这些“算法设计模式”可以帮助读者为自己碰到的问题设计新算法。
● 加强分析技巧
读者将会获得大量的实践以对算法进行描述和推导。通过数学分析,读者将对《算法详解》系列图书所涵盖的特定算法和数据结构产生深刻的理解。读者还将掌握一些广泛用于算法分析的实用数学技巧。
● 形成算法思维
在学习了算法之后,很难发现有什么地方没有它们的踪影。不管是坐电梯、观察鸟群,还是管理自己的投资组合,甚至是观察婴儿的认知,算法思维都如影随行。算法思维在计算机科学之外的领域,包括生物学、统计学和经济学越来越实用。返回搜狐,查看更多
责任编辑:
算法精解 c语言描述 豆瓣,斯坦福大学教授亲授,这本美亚4.7星的算法书,新手程序员都看得懂!...相关推荐
- 资料 | O‘Reilly精品图书系列:算法精解 C 语言描述 (简体中文)
下载地址:资料 | O'Reilly精品图书系列:算法精解 C 语言描述 (简体中文) 内容简介 · · · · · · 本书是数据结构和算法领域的经典之作,十余年来,畅销不衰! 全书共分为三部分:第 ...
- 算法精解_C语言 链表_单链表(接口定义+类型实现)
链表可以说是一种最为基础的数据结构.链表由一组元素以一种特定的顺序组合或链接而成,在维护数据的集合时很有用.这一点同我们常用的数组很相似.然而,链表在很多情况下比数组更有优势.特别是在执行插入和删除操 ...
- JVM内存管理------GC算法精解(五分钟让你彻底明白标记/清除算法)
转载自 JVM内存管理------GC算法精解(五分钟让你彻底明白标记/清除算法) 相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑 ...
- JVM内存管理------GC算法精解(复制算法与标记/整理算法)
转载自 JVM内存管理------GC算法精解(复制算法与标记/整理算法) 本次LZ和各位分享GC最后两种算法,复制算法以及标记/整理算法.上一章在讲解标记/清除算法时已经提到过,这两种算法都是在此 ...
- 算法与数据结构java语言描述 英文版_CVPR2020 |室内设计师失业?针对语言描述的自动三维场景设计算法...
近日,计算机视觉顶会CVPR 2020接收论文结果公布,从6656篇有效投稿中录取了1470篇论文,录取率约为22%.在<Intelligent Home 3D: Automatic 3D-Ho ...
- 《数据结构、算法与应用 —— C++语言描述》学习笔记 — 回溯法
<数据结构.算法与应用 -- C++语言描述>学习笔记 - 回溯法 一.算法思想 二.货箱装载 1.问题描述 2.回溯算法 3.实现 4.测试代码 一.算法思想 回溯法是搜索问题解的一种系 ...
- 数据结构习题精解 C语言实现+微课视频(习题解答、研考试题、微课视频)
数据结构习题精解 C语言实现+微课视频(习题解答.研考试题.微课视频) 配套 数据结构.数据结构C语言实现等经典教材的课后习题解答,著名高校典型考研试题详解.微课视频
- JVM内存管理------GC算法精解(五分钟教你终极算法---分代搜集算法)
转载自 JVM内存管理------GC算法精解(五分钟教你终极算法---分代搜集算法) 引言 何为终极算法? 其实就是现在的JVM采用的算法,并非真正的终极.说不定若干年以后,还会有新的终极算法, ...
- 《数据结构、算法与应用 —— C++语言描述》学习笔记 — 优先级队列 — 左高树
<数据结构.算法与应用 -- C++语言描述>学习笔记 - 优先级队列 - 左高树 一.左高树 1.外部节点 2.高度优先左高树 (1)定义 (2)特性 (3)HBLT 与 大小根树 3. ...
最新文章
- pcb结构链表_第2章 2-1进程与PCB
- 《Python 学习手册4th》 第十二章 if测试和语法规则
- 使用C#检验.NET FrameWork版本
- 用计算机探索规律反思,《用计算器探索规律》教学反思
- 全志h3linux移植教程,全志H3启动分析,移植主线UBOOT
- 北京活动:4月20号《科技媒体、SEO与PM》主题活动
- Android加速度传感器
- 并不是所有SAP产品的UX,都得遵循Fiori UX风格
- python3.7知识点汇总
- (二)AS给button添加点击事件
- java中servlet知识_jsp_Servlet常用知识总结
- 1:1 人脸比对 开源_在开源周宣布青年:1月13日至17日
- Java 中的参数传递和引用类型
- 洛谷P2770 航空路线问题(费用流)
- [5-24]绿色精品软件每天更新[uc23整理]
- 【Java基础】Java集合、泛型和枚举
- html5添加音波波形图,wavesurfer.js绘制音频波形图的实现
- 工作8年的普通专科生程序员的一些感悟
- 基于Python的MACD顶底背离形态的实现
- Pitfall of OO Programming