目录
1. 为什么要学算法
2. 整数乘法问题
2.1 问题描述
2.2 小学算法
2.3 小学算法操作数量分析
2.4 还能做的更好吗
3. Karatsuba乘法
3.1 一个例子
3.2 一种递归算法
3.3 Karatsuba乘法
4. MergeSort算法
4.1 为什么要学MergeSort(Motivation)
4.2 排序问题描述
4.3 一个例子
4.4 伪代码
4.5 Merge子程序伪代码
5. MergeSort算法分析
5.1 Merge的运行时间
5.2 MergeSort运行时间
6. 算法分析的指导原则
6.1 原则1: 最坏情况分析
6.2 原则2: 全局分析
6.3 原则3: 渐进分析
6.4 什么是最快算法
1. 为什么要学算法
![eddc1e8c146ceb0dddd9bdbd75c4961a.png](https://img-blog.csdnimg.cn/img_convert/eddc1e8c146ceb0dddd9bdbd75c4961a.png)
有以下好处:
- 算法对计算机科学的所有分支都重要: 想要完成实质性工作,理解算法的基础知识并掌握算法密切相关的数据结构知识是必不可少的。
- 算法是技术革新的推动力: 一个例子是搜索引擎使用一系列算法高效的计算与特定搜索项相关联的各个web页面出现的频率。
- 算法会对其它学科产生影射。
- 学习算法有益于思维: 作者还是学生的时候,喜欢上那些具有挑战性的课程,如果他艰苦的征服这些课程,会感到自己智商比刚开始学的时候提高几个点 (我也有这样的感觉)。作者希望也能给读者带来类似的体验。
- 算法很有趣: 希望学完后给大家带来的感觉是简单而愉快的事情。
2. 整数乘法问题
2.1 问题描述
![3e4cb11e6ceea0a6bbde3e6d7bbe06dc.png](https://img-blog.csdnimg.cn/img_convert/3e4cb11e6ceea0a6bbde3e6d7bbe06dc.png)
乘法问题的描述是,它输入是两个n位数字的整数,分别称为x和y。鼓励把n看作一个非常巨大的数据,几千甚至更大,输出是x和y的乘积。
2.2 小学算法
性能定义
我们通过测量它所执行的基本操作数量来评估这种算法的性能。基本操作看做下列的操作之一:
- 两个个位数相加
- 两个个位数相乘
- 在一个数之前或之后添加一个0
举个例子
![13c5e84a454eac2b3919618010db41e5.png](https://img-blog.csdnimg.cn/img_convert/13c5e84a454eac2b3919618010db41e5.png)
x是5678,y是1234,其中n=4,这两个整数相乘详细过程如上图:
- 这种算法首先计算第一个数与第二个数最后一个数字的部分乘积:4*5678=227122
- 计算这个部分乘积的过程中又可以细分为把第一个数字的每一位数字与4相乘,并在必要时产生进位
- 在计算下一部分乘积5678*3=17034时我们执行相同的操作,并把结果左移一位,相当于在末尾添了一个0
- 对于剩下的两个部分,乘积也是执行相同的操作
- 最后把所有四部分乘积相加
2.3 小学算法操作数量分析
我们把4依次与第一个数的5,6,7,8相乘,这样就产生了4个基本操作,由于进位的原因,我们还需要执行一些加法操作。
一般而言,计算一个部分乘积涉及n个乘法(每位数字1个)以及最多n个加法(每位数字最多1个),总共最多就有2n个基本操作。
第一个部分乘积和其他部分乘积相比,并没有任何特别之处,每个部分乘积都最多需要2n个基本操作。
由于一共有n个部分乘积,第二个整数的每一位都会产生一个乘积,因此,所有部分乘积最多需要个基本操作。 我们还需要把所有的部分乘积相加得到最终答案,这个过程中仍需要相当数量的操作,于是该算法的基本操作数量总结如下:2.4 还能做的更好吗
或许成为优秀算法设计师的最重要原则就是 拒绝满足 ,每个算法设计师都应该坚守下面的信条, 我还能做得更好吗 ?![bb1b6dc2d24446b320131d38f9f9cbe9.png](https://img-blog.csdnimg.cn/img_convert/bb1b6dc2d24446b320131d38f9f9cbe9.png)
3. Karatsuba乘法
3.1 一个例子
我们将执行一系列与之前小学算法与众不同的步骤,我们需要领悟一个关键的要点,就是我们可以通过许多眼花缭乱的方法来解决诸如整数乘法这样的计算问题。![c464ebc70492990756b9a06a24dce6e6.png](https://img-blog.csdnimg.cn/img_convert/c464ebc70492990756b9a06a24dce6e6.png)
![ef1e7cf733fb3bed0d6d3ad8bc3ac46d.png](https://img-blog.csdnimg.cn/img_convert/ef1e7cf733fb3bed0d6d3ad8bc3ac46d.png)
- step1: 计算,结果是
- step2:
- step3:
- step4: 把步骤3减去前两个步骤结果: 6164-672-2562=2840
- step5: 计算
3.2 一种递归算法
我们先来看看另一个比较简单的整数乘法的递归算法。 一般而言,位数为偶数n的数x,可以表示为两个位的数,他的前半部分a和后半部分b,于是: 于是: 它表示用一种递归方法进行两个整数的相乘,为了计算xy这个乘积,我们对最后一个表达式进行计算,四个相关的乘积(ac,ad,bc,bd)所涉及的位数都是小于n的,所以我们可以用递归的方式计算,当这4个递归调用带着各自的答案返回时,我们就可以简单的计算表达式得值:ac后面加n个0,ad和bc相加,并得出的结果后面加上个零,最终把两个表达式与bd相加。伪代码如下:![bdbbbadc0cc5a3fc853ad90a1e92f1ae.png](https://img-blog.csdnimg.cn/img_convert/bdbbbadc0cc5a3fc853ad90a1e92f1ae.png)
3.3 Karatsuba乘法
还能改进吗 上面的算法使用了4个递归调用,我们事实上并不真正的关心ad或bc的值,只是关注他们的和ad+bc,所以我们真正关心的只有三个值: ac, ad+bc,bd,那是不是只用三个递归调用就可以了呢?![51b19010dee1e8ebff402963db4bb201.png](https://img-blog.csdnimg.cn/img_convert/51b19010dee1e8ebff402963db4bb201.png)
- step1: 递归计算ac
- step2: 递归计算bd
- step3: 计算 a+b, c+d,递归计算 (a+b)*(c+d)
- step4: 步骤3减去步骤1,步骤2,获得ad+bc的值
- step5: 步骤1结果后面加n个0,步骤4后面加个0,然后与步骤2的结果相加就得到答案
![dfc21da95482d10752057ef354722d54.png](https://img-blog.csdnimg.cn/img_convert/dfc21da95482d10752057ef354722d54.png)
4. MergeSort算法
4.1 为什么要学MergeSort(Motivation)
![864f3dd1464b46bc31222e3b09ac566d.png](https://img-blog.csdnimg.cn/img_convert/864f3dd1464b46bc31222e3b09ac566d.png)
姜还是老的辣:它虽然年龄很大了,但是在实践中一直被沿用,仍然是很多程序库中的标准排序算法之一。
经典的分治算法: 分治算法设计范式是一种通用解决问题的方法,它的基本思路是把原始问题分解为多个更小的子问题,并以递归的方式解决子问题,最终通过组合子问题的解决方案得到原始问题的答案,MergeSort可以作为良好的起点帮助我们理解分治算法,它的优点以及面临的分析挑战。
校准预备条件: 本节对面的MergeSort的讨论,可以让读者明白自己当前的技术水平是否适合上这门课,假设读者已经具有一定的编程和数学背景,能够把MergeSort的高级思路转换为自己喜欢的编程语言,能够看懂我们对算法进行的是运行时间分析,如果读者能够适应本内容,那么对于本书的剩余部分也不会有什么问题。
推动算法分析的指导原则: 本节对MergeSort运行时间的分析展示了一些更加基本的指导原则。
为主方法热身: 我们将使用递归树方法对MergeSort进行分析,这是一种对递归算法所执行的操作进行累加的方法,后面将结合这些思路生成一个“主方法”,主方法是一种功能强大且容易使用的工具,用于界定许多不同的分治算法的运行时间。
4.2 排序问题描述
输入一个数组,数组里面的每个数字是不重复的,输出是已经排序好的数组。![416c64c8e11bfd3617d0fd0597194a1f.png](https://img-blog.csdnimg.cn/img_convert/416c64c8e11bfd3617d0fd0597194a1f.png)
- SelectionSort: 扫描全数组找到最小元素,把它放到输出数组的第一位,接着扫描复制次小的元素,以此类推;
- InsertionSort: 这是同一个思路的一种更灵巧的实现方法,它把输入数组中的每一个元素依次插入到有序的输出数组中的适当位置;
- BubbleSort: 对相邻无序的元素进行比较,执行反复的交换,直到最后数组完成排序。
4.3 一个例子
想要理解MergeSort算法是如何运行的,一个最简单的方法就是看看具体的例子。![a53a2bab6ab5a677fb033618c6dbd308.png](https://img-blog.csdnimg.cn/img_convert/a53a2bab6ab5a677fb033618c6dbd308.png)
4.4 伪代码
将上面的图换成伪代码就是这样的过程![4526ee71e814eeadc39dd34ae9fd97a4.png](https://img-blog.csdnimg.cn/img_convert/4526ee71e814eeadc39dd34ae9fd97a4.png)
- 作为一种递归算法,它必须有一个或多个基本条件,如果不再有进一步的递归就直接返回答案,因此,如果输入数组a只包含0或1个元素就返回该数组。
- 这段伪代码并没有详细说明n是奇数时,前半部分和后半部分是怎么划分的,但是这种划分是显而易见的,比如说一半比另一半多一个元素,这种也是可行的。
- 最后这段伪代码忽略了怎么把两个子数组实际传递给它们各自递归调用的实现细节,这个细节取决于编程语言,高级伪代码的要点就是忽略这些细节,把注意力集中在超越特定编程语言的概念上。
4.5 Merge子程序伪代码
由上面的图我们可以知道,Merge的时候,其实输入两个已经排序好的数组C, D,再把它们排序输出到B。![4d0663c36c682eff290ac941ac989b31.png](https://img-blog.csdnimg.cn/img_convert/4d0663c36c682eff290ac941ac989b31.png)
5. MergeSort算法分析
我们对算法进行运行时间分析,分析的是什么呢?“运行时间”表示算法的一个具体实现所执行的代码的行数,我们可以把它看成是在一个具体的实现中用调试器进行逐行追踪,每次追踪一个“基本操作”,我们所感兴趣的是程序结束之前调试器所执行的步数。5.1 Merge的运行时间
![eb57b05141fb479b042f29791eb84538.png](https://img-blog.csdnimg.cn/img_convert/eb57b05141fb479b042f29791eb84538.png)
5.2 MergeSort运行时间
递归算法运行时间分析的难点 我们怎样才能从Merge程序的简明分析转到MergeSort的分析呢?要知道递归算法会产生更多的自身调用,更为可怕的是递归调用数量快速增长,随着递归深度的加深,递归调用的数量以指数级的速度增长,我们必须记住一个事实,传递给每个递归调用的输入又要明显小于上一级递归调用的输入。 相互制衡的竞争因素 一方面是需要解决的子问题的数量呈爆炸性增长,另一方面是这些子问题的输入越来越小,协调好两个竞争因素,有助于我们对MergeSort进行分析。 运行时间计算 为了简单起见,假如输入数组的长度是n的2次方(如果没有这个假设只需要额外工作就能消除这个假设),我们用递归树的方法来分析运行时间的上界,每一个节点就表示一次递归调用。![f137704f6c6ae5d35a49e486ccfa2317.png](https://img-blog.csdnimg.cn/img_convert/f137704f6c6ae5d35a49e486ccfa2317.png)
![5740bd346a3690d6801303a6d7b712cb.png](https://img-blog.csdnimg.cn/img_convert/5740bd346a3690d6801303a6d7b712cb.png)
![086763a7436721340d24825558a48d58.png](https://img-blog.csdnimg.cn/img_convert/086763a7436721340d24825558a48d58.png)
6. 算法分析的指导原则
现在回过头来明确与运行时间的分析解析有关的三个假设,我们将这三个假设作为合法合理分析算法的指导原则。
如果我们想要进行准确的运行时间分析,只有那些最简单的算法才有可能,在更多情况下,算法都是很复杂的,所以我们需要进行妥协,找到正确的平衡点,可以为数十种基本算法提供良好的运行时间保证。这一保证可以让我们知道哪种算法更为快捷。
6.1 原则1: 最坏情况分析
![715f76ee5cb798c0a18abde93f7c1828.png](https://img-blog.csdnimg.cn/img_convert/715f76ee5cb798c0a18abde93f7c1828.png)
我们上面的MergeSort算法里面的运行时间上就是,如果有一个充满恶念的人,他的生活目标就是编造一个恶意的数组,使目的是MergeSort运行尽可能的慢,但是这个上界仍然是被满足的,这种类型的分析称为最坏情况分析,因为它给出了运行时间的上界,即使遇到最坏的输入,上界仍然是有效的。
如果了解了问题领域知识,并理解哪些输入更具有代表性后,平均情况分析和基准实例分析都是非常实用的。最坏情况分析并不需要考虑输入,它更适用于通用目的的子程序设计,目标就是范围很广的应用程序。为了使算法的适用性更广,他们更专注于通用目的子程序,因此一般使用最坏情况来判断算法的性能。
最坏情况分析还有一个额外的优点,相比其他分析,他通常更容易用数学方式实现。
![c3d9c093ef5e8cd21c45c218b7742acc.png](https://img-blog.csdnimg.cn/img_convert/c3d9c093ef5e8cd21c45c218b7742acc.png)
便于数学处理: 进行全局分析的第一个理由是它比那些需要精确的常数因子和低阶项的方法更容易进行数学处理。
常数因子往往依赖于环境: 第二个理由,它是非常重要的,我们在描述算法的粒度层中很容易被误导,从而过于重视常数因子的准确性,我们的目的是常数因子不可避免的是依赖于具体所用的编程语言,特定实现以及编译器和处理器的细节,我们的目的是把注意力集中在那些与编程语言计算机体系结构细节无关的算法属性上,并且这些属性并不受运行时间上界中的较小常数因子的变化而影响。
预测能力的损失有限: 第三个理由,也是我们决定忽略常数因子的根本原因,可能很多人可能会担心忽略常数因子会导致我们错误判断,误以为自己的算法很快,但实际上使用中速度却慢很多。告诉大家一个愉快的消息,我们忽略了低阶项和常数因子,我们所进行的数学分析定性预测能力仍然是高精度的,当算法分析告诉我们一种算法运行较快的时候,它实际使用中也是比较快速的,反过来也是如此。
全局分析忽略了一些信息,但它保留了我们真正需要的东西,关于哪些算法较之其他算法运算速度更快的指导方针。
![ee850642d180fe3c25431a1d7e2ca50a.png](https://img-blog.csdnimg.cn/img_convert/ee850642d180fe3c25431a1d7e2ca50a.png)
渐进性分析,是指把注意力集中在当输入长度n增长时算法的运行时间增长率上。
比如和,如果n<90的时候,是大,当n>90的时候,是大。
我们需要关注大量数据而不是小量数据,因为大量数据才是算法的用武之地。
6.4 什么是最快算法
![459d09ed2be6a0e1b17d65846ec5a6f0.png](https://img-blog.csdnimg.cn/img_convert/459d09ed2be6a0e1b17d65846ec5a6f0.png)
所以快速算法就是指算法的最坏情况运行时间随着输入长度的增加而较慢增长。
对于第一个知道原则,我们想要保证运行时间并不需要任何领域知识为前提,这也是为什么我们把注意力集中在算法的最坏运行时间的原因。
第二和第三个指导原则表示常数因子往往依赖于编程语言和计算机,并且我们感兴趣的是大型的问题,这是我们把注意力集中在算法的运行时间增长率的原因。
推荐一下小姐姐的公众号吧,想学算法的朋友,可以关注一下!![4d681e9367088ea8d746d1d1cb621637.png](https://img-blog.csdnimg.cn/img_convert/4d681e9367088ea8d746d1d1cb621637.png)