算法设计与分析¶
第一部分:算法设计和分析的数学和定理基础¶
算法设计的两个例子¶
调度问题¶

贪心法的解:按照加工时间从小到达安排,即3,5,8,10,15

贪心算法的反例:

按照贪心算法:单位重量价值最大的优先,按照 价值/重量 从大到小排序

然而存在更优的解:

==算法设计:==

投资问题¶

对问题进行建模:

蛮力算法(枚举):将每一种可能的分配方案计算出结果,选择最优的解
蛮力算法的效率:
数学的求解过程:(可只关注结论)


结果的推导过程参考:stirling公式

结论:蛮力算法的效率为指数函数,效率极低.==因此有些问题容易找到好的算法,有些问题需要设计更好的算法==

问题计算复杂度的界定:以排序问题为例¶

以上算法的具体过程见数据结构
问题计算复杂度的分析:同一个问题的不同种算法同时考虑分析
哪个排序算法的效率最高?
排序问题的难度?
货郎问题与计算复杂性理论¶
货郎问题:

问题的建模:

0-1背包问题:

问题的建模:

双机调度问题:

问题的分析:

==以上问题都没有找到有效的算法可以解决,被称为NP-Hard问题==
NP-Hard问题:
存在大量的这类问题
至今没有找到有效算法:现有算法的运行时间是输入的指数或更高级别
至今没有人能证明不存在这类问题的多项式时间的算法
从多项式时间的算法角度来看,这类问题等价,这类问题的难度处于可有效计算的边界

课程内容:

算法及其时间复杂度¶
问题及实例的概念¶

算法的概念¶

基本运算与输入规模¶

输入规模:

基本运算:

算法的两种时间复杂度¶

平均时间复杂度的计算¶

算法的伪码表示¶
算法的伪码描述

求最大公约数:欧几里得算法


小结

函数渐进的界¶
大O符号:可以接触到的上界,fn阶数不高于gn


大Ω符号:可以接触到的下界,fn阶数不低于gn


小o符号:接触不到的上界,fn阶数低于gn


小Ω符号:接触不到的下界,fn阶数高于gn


Θ符号:既是可接触的上界又是可接触的下界,fn和gn阶数相同


时间复杂度:O(n^1/2^) //可接触的上界
有关函数渐进界的定理(计算时间复杂度时使用)¶

用于判断fn和gn的阶数高低
推论:


用于判断时间复杂度为对数,幂函数,指数函数时的关系大小

用于判断各函数的阶数大小关系

用于判断一个算法中不同基本运算次数在时间复杂度上取哪个(算法的时间复杂度是各步操作之和,在常数步的情况下取最高阶的函数即可)
几类重要函数¶
基本函数类¶

==指数>多项式>对数多项式==,每一级都相对于上一级爆炸增长
指数级的算法是不可运算的!
对数函数¶
简写说明:

性质:

1.在考虑算法的复杂度时不需要考虑对数底的大小
2.对数要小于幂函数
3.此情况下对数指数函数和幂函数相等
指数函数与阶乘¶

阶乘要小于n^n^,要大于2^n^,阶乘的对数和nlogn同阶
取整函数¶

性质:

按照阶排序的实例¶

从左到右阶数依次变小,从上往下量级依次变小
序列求和的方法(求迭代过程的算法的时间复杂度)¶
求和公式¶

一个例子:

应用:二分检索法的平均时间复杂度¶



估计和式上界的放大法¶

一个例子:

估计和式渐进的界:


结论:调和级数和logn同阶
递推方程与算法分析(递归的算法的时间复杂度求解)¶
递推方程的定义¶

几个例子¶
fibonacci数列:

最下面的红框即为递推方程的解
汉诺塔问题:


利用迭代求递推公式的解
即时间复杂度为指数级!
==这是一个难解问题,不存在多项式时间的算法==
插入排序

W(n-1)为上一个元素插入排序的时间复杂度,n-1为这一个元素最坏情况下需要比较的次数
利用迭代求递推公式的解
迭代法求解递推方程¶
直接迭代¶

以汉诺塔为例做迭代求递推公式的解:

换元迭代¶

以二分归并排序为例做换元迭代:

W(n/2)为后面两次递归的排序的时间复杂度,n-1为这次排序需要对比的次数

可以用数学归纳法验证正确性

差消法化简高阶递推方程(以快速排序的时间复杂度分析为例)¶
快速排序时间复杂度分析:



平均工作量=总工作量/输入情况个数n

差消化简:利用两个方程相减,将右边的项尽可能消去,以达到降阶目的

下面两式相减,可消去∑处的大部分项


递归树(是迭代的模型)¶
递归树的概念¶

迭代在递归中的表示:

递归树的生成规则:

==递归树往往使用在需要迭代的项数大于1的情况==
实例一:以二分归并排序的时间复杂度分析为例做递归树的应用¶


写出递归树前几层后,==分析每一层的总和,根据每一层的规律推导出总的时间复杂度==

实例二¶

==注意==:这棵迭代数左边递减速度大于右边,所以树右边的层数会大于左边,因此最后一层的总和不超过n,即O(n)

这里的求和只看最右边这一路:每一次迭代乘以2/3,总共迭代k次,即最后一层的表达式为(2/3)^k^n,等于最后的初值为1,从而求出k
最后得到:

logn为层数k,n为每层的总和
主定理及其应用(用于求解递推方程的解,得到时间复杂度)¶
主定理的应用背景

主定理的概念:

主定理的应用:





不可以使用的例子:

使用递归树求解:


第二部分:分支算法的设计思想与分析方法¶
分治策略的设计思想¶
分治策略的基本思想¶

几个例子¶
二分检索:


二分归并排序:



汉诺塔问题:


分治算法的一般描述和分析方法¶
分治算法的一般描述¶

1.当P问题足够小到量为c时,可以直接求解
分治算法的设计要点¶

分治算法的时间复杂度分析¶

fn为在划分时需要消耗的工作量以及综合问题时的工作量
Wc为划分到最小时的工作量
==分治算法两类常见递推方程及其时间复杂度分析==¶


==方程2常用以下结论直接得出时间复杂度:(由主定理推导得到)==

典型分治策略算法1:芯片测试¶

首先考虑两个芯片之间相互测试可能存在的情况:


接下来分析用其他所有芯片判断一颗芯片好坏的可能性
奇数情况:

偶数情况:

最后考虑整个问题的算法:
一 蛮力算法

最坏情况时间复杂度为On^2^量级
==二 分治算法==

==注意==:该算法在递归调用一次后,仍然满足好芯片比坏芯片多一颗的条件,接下来证明这个结论:

n为偶数时显然满足

n为奇数时,若按照原方案,则存在轮空芯片,导致算法出错;
因此需要单独对轮空芯片与其他芯片比较,判断其好或坏(见前文如何判断一颗芯片的好坏)
分治算法的伪码表示:

分治算法的时间复杂度分析:

结论:分治算法要优于蛮力算法
典型分治策略算法2:快速排序¶
基本思想:

伪码表示:


实例:

==注意==:与之前学习的稍有区别,体现在之前有一个辅助空间储存中间值,从而使得发现一个需要交换的数直接进行交换;结束条件为前指针位置大于后指针位置时
此处为先从后向前找再从前向后找,都找到需要交换的值时再进行调换;结束条件为找到需要交换的第一组前指针大于后指针的情况
时间复杂度分析:

最坏情况:中间值为数组中最大的数/最小的数
当每次划分比例保持不变时,时间复杂度仍为O(nlogn)

推导过程:递归树(类似于递归树中的示例二)

平均时间复杂度分析:


==注意==:红框中的式子使用差消法求得递推方程的解
结论:快速排序的的时间复杂度:
最坏:O(n^2^) 平均:O(nlogn)
典型分治策略算法3:幂乘算法及其应用¶

使用分治算法划分:

分治算法的时间复杂度分析:

幂乘算法的应用:fibonacci数列的矩阵运算
传统算法:

fibonacci数列的矩阵相关性质:

利用幂乘计算fiboncci数列的算法以及时间复杂度分析:

改进分治算法的途径1:减少子问题数¶

实例一:整数位乘问题

此处的四条红线分别是需要计算的四个子问题
发现分治算法并没有降低时间复杂度
减少子问题的个数从而降低分治算法的时间复杂度:

这次子问题被缩小到了三个,有效的降低了时间复杂度
实例二:矩阵相乘问题

采用简单分治算法:

此处有八个子问题需要计算,与传统算法的时间复杂度相同
通过Strassen矩阵乘法,将子问题缩小到7个:


矩阵乘法的应用:

改进分治算法的途径2:增加预处理,减少fn¶
以平面点对问题为例:

算法伪码:

跨边界情况的处理:

==注意==:δ为左半边和右半边最小长度中的最小值,而每个方格中最长的长度(对角线)为5δ/6,因此每个方格最多有一个点
(因为如果一个方格里有两个点说明他们的距离小于δ,δ就不再是左半边和右半边最小长度中的最小值)
算法时间分析:

增加预处理,减少排序的时间:

拆分的时间复杂度为On
拆分过程的实例:

对XY都直接排序
X:直接从中间拆分 Y:将左半边的移到右侧,右半边的移到左侧
改进算法的时间复杂度分析:

时间减少了一个logn
典型分治策略算法4:选择问题¶

选最大与最小¶
只选最大/最小:

同时选择最大和最小:
普通算法(顺序比较):

分组算法:先两两分组,将大的分为一组小的分为一组,在大的组中找最大值,小的组中找最小值

最坏时间复杂度分析:

分治算法:将数组从中间划分,递归的求左右两个子数组的最小值和最大值,最后比较最外面一层的左右最大值和最小值

最坏时间复杂度分析:

结论:

只选最大/最小:顺序比较已经是最好的算法了
同时选最大和最小:顺序比较不够优秀,分组和分治是最好的算法
选第二大¶

提高效率的算法:锦标赛算法

==重点:以时间换空间==


实例:

分析时间复杂度:


说明最大值所淘汰的数总共有logn上取整那么多,从而得到第二阶段的时间复杂度

一般选择问题的算法设计¶

应用该问题的实例:管道位置问题

实例分析:

结论:最优解为y坐标的中位数之间修管道
简单算法:

分治算法:

为了保证效率,需要设计找到合适的m^*^的算法:
将整个数组划分成n/5下取整个组,每组从大到小排列,从而通过选择算法确定中位数组中的中位数作为m^*^

A,D两个区中数字的大小和m^*^的大小关系无法确定,因此需要一一对比,将大的数划分进B,小的数划分进C
实例:


算法伪码:

一般选择问题的算法分析¶
算法的时间消耗主要在三次递归调用上消耗
子问题的规模分析:

因此算法的时间复杂度为:

递推方程为:

通过递归树求解得:

==思考==:之前讨论的都是五个元素一组,若修改每组元素个数,如3/7个,算法时间复杂度会改变吗?

以3分组时为例分析时间复杂度:


当3分组时,递归树的每一行不构成公比小于1的等比数列,因此复杂度增加
结论:当3分组时时间复杂度增加,为O(nlogn),而5,7分组时为O(n)
经典分治策略算法应用1:卷积¶
卷积及其应用¶
向量计算

卷积:下标之和为k的相同的每组数相乘再相加,得到c~k~的值
卷积计算用矩阵表示:

实例:

卷积与多项式乘法的关系:卷积的每个分量的值和多项式的每一项对应
卷积的应用:信号的平滑处理


蓝框为信号向量a 红框为权向量b 权向量的长度=2*步长+1
==注意==:信号向量两头不能完全取到所有权向量的值会产生误差
实例:

使用矩阵表示:

卷积计算¶
蛮力算法¶

时间复杂度为O(n^2^)
快速傅里叶变换FFT算法¶
基本原理:

问题:如何选择x~0~到x~2n-1~,使得多项式求值算法高效
高斯滤波的权值函数:

2n个数(x)的选择:1的2n次根

实例:

快速傅里叶变换FFT算法思路:

最后利用以下等式关系:

算法的关键:

接下来研究,对多项式快速求值的算法
法1:蛮力算法

法二:改进的求值算法

法三:分治算法(奇系数和偶系数多项式)
实例:

一般公式:


从而分解出两个子问题递归调用的工作
分治算法的时间复杂度分析:

FFT算法伪代码

算法分析:

经典分治策略算法应用2:平面点集的凸包¶

分支的算法:


算法时间复杂度分析:

分治算法小结¶



第三部分:动态规划¶
动态规划算法的例子¶


实例:

算法设计:

动态规划求解:

从终点往起点的顺序考虑,每次标记最短路径以及是往上走还是往下走
动态规划算法特点分析:


==关键==:每一步决策与上一步决策的结果存在依赖关系

一个反例:

解释: 模10的最小路径:路径长度%10以后结果最小
无法使用动态规划的原因:不满足优化原则,如第一个子问题(最后一个点到终点的路径选择)的正确结果并不是该子问题的最优解
小结:

动态规划算法设计¶

矩阵链相乘问题:

分析矩阵相乘基本运算次数:

实例:

蛮力算法:时间复杂度极高
动态规划算法设计:

递推方程:

小结:

动态规划算法的递归实现¶

通过证明得到:动态规划的递归实现的算法时间复杂度仍旧为很高,达到2^n^
原因:

相同颜色的为相同的子问题

==子问题有大量的重复运算!==

动态规划算法的迭代实现¶


伪码:

时间复杂度:

实例:



两种实现的比较:

动态规划算法的要素:

投资问题¶

实例:

子问题的界定和计算顺序:

注意:不同于矩阵链相乘问题,投资问题的两个参数类型不同
先考虑前k个问题的最大值,每个问题都要考虑x的所有可能
优化函数的递推方程:

计算实例:



时间复杂度分析:


小结:

背包问题¶

问题建模




实例:




推广:

小结:
