一、算法基本介绍
1. 算法的含义
-
什么是算法?准确描述的问题求解的步骤
-
什么是计算机算法?从特殊到一般的追求(实例->算法)
2. 算法的描述
-
将问题求解过程,以操作步为单位,清晰准确的写下来
-
why描述:
- 让目标读者看得懂
- 便于分析算法的性质
- 如果需要的话,有助于用程序实现
-
描述方式包含:自然语言描述、流程图和伪代码
-
三种控制结构:**顺序、条件、循环 **
3. 算法的分析
-
正确性问题,能否达到目的?
- 能否满足要求的结果解的类型:精确解、近似解、可行解:比如路线、最优解
- 能否结束
-
效果如何?
- 时间效率和空间效率
- 算法的时间效率(时间复杂性):操作步骤数与输入数据规模(值)之间的关系。
- 大O计数法
- 与最优解之间的差距(如果存在的话)
- 时间效率和空间效率
4. 算法的类型
-
从复杂度分:多项式/指数算法
主要在于效率和性能
- 从应用场合分:数值计算/非数值计算
- 数值计算方法:主要在于模拟和仿真,强调收敛与误差等概念
- 非数值计算:不是计算过程不涉及到数值,二是主要目的在搜索、推理、决策、规划等需求的满足。
- 从应用场合分:数值计算/非数值计算
-
从设计方法论分:枚举、贪心、分治、动态规划、启发式、回溯,这种划分有利于算法技巧的系统化学习。
-
从数据的存储介质分:内存算法、外存算法
- 主要区别在于随机访问数据的时间相等的假设成立与否。
二、欧几里得算法
1. 量水问题
- 最大公约数,数学公式
G C D ( x , y ) = a x + b y GCD(x,y) =ax+by GCD(x,y)=ax+by
2. 基本欧几里得算法
辗转相除法
if a > b,
G
C
D
(
a
,
b
)
=
G
C
D
(
a
,
a
−
b
)
\text{if a > b, }GCD(a,b) = GCD(a,a-b)
if a > b, GCD(a,b)=GCD(a,a−b)
算法实现
Input:非负非全零整数 a,b
Output:
G
C
D
(
a
,
b
)
GCD(a,b)
GCD(a,b)
While b<>0:
a
,
b
=
b
,
a
%
b
a,b = b, a \% b
a,b=b,a%b
print (“GCD(a,b)=”,a)
循环一定会变为0么?
特殊情况:a < b; a = 0; b = 0
3. 扩展欧几里得算法
Q:已知 G C D ( a , b ) = d GCD(a, b)= d GCD(a,b)=d,找出一组 x , y x,y x,y 满足 a x + b y = d ax+by=d ax+by=d
A:递归方法
数学公式
G
C
D
(
a
,
b
)
=
d
⟺
a
x
+
b
y
=
d
,
a
x
1
+
b
y
2
=
d
,
a
%
b
=
a
−
(
a
/
/
b
)
b
,
b
x
2
+
(
a
%
b
)
y
2
=
d
,
=
>
x
1
=
y
2
;
y
1
=
x
2
−
(
a
/
/
b
)
y
2
GCD(a,b) = d \iff ax+by = d,\\ ax_1+by_2 = d, \\ a\%b = a-(a//b)b,\\ bx_2+(a\%b)y_2 = d,\\ =>x1= y2;y_1=x_2-(a//b)y_2
GCD(a,b)=d⟺ax+by=d,ax1+by2=d,a%b=a−(a//b)b,bx2+(a%b)y2=d,=>x1=y2;y1=x2−(a//b)y2
辗转相除、终止条件、逐层计算
- 算法的正确性
- 算法效率分析:k
- 欧几里得算法的应用
- RSA加密算法:质数 ab,求 ax % b = 1
三、二分法
1. 二分搜索
二分搜索原理
- “>, <, =” 满足传递特性
- 二分搜索仅适用于有序排列的搜索对象
伪代码
输入:a[]升序数组,key,查找对象
输出:找到,返回序号;否则查找失败。
- low = 0; high = Len(a) - 1
- while(low <= high):
- mid = (low+high) //2; – 向下取整
- if(key == a[mid]) return mid
- if(key < a[mid]) high = mid - 1
- if(key > a[mid]) low = mid + 1
- return not found
算法的正确性讨论
- 是否一定会计算结束
- 是否一定查找到?
算法的效率
-
逐次比较: O ( n ) O(n) O(n)
-
二分搜索: O ( log n ) O(\log n) O(logn),可以使用树型结构将这个结果表示出来
2. 二分法求奇次方程组的一个实根
求解二次方程的解
因式分解 或者 同解公式
?高阶方程
二分法的思想:
f
(
a
)
f
(
b
)
<
0
,
则存在
x
∗
∈
(
a
,
b
)
,满足
f
(
x
∗
)
=
0
⟶
分而治之的二分法求解思想
f(a)f(b)<0,则存在x^*\in (a,b),满足f(x^*)=0\longrightarrow 分而治之的二分法求解思想
f(a)f(b)<0,则存在x∗∈(a,b),满足f(x∗)=0⟶分而治之的二分法求解思想
什么时候停止计算
- 基于函数值 f ( x ) f(x) f(x)
- 基于区间长度,满足一定的阈值则停止计算
哪些方程可以使用二分法计算?
函数的连续性、单调性【不单调可能会漏掉某些实根】
伪代码
算法的正确性
如何确定初值?使用分割法
算法的效率
- 使用区间长度作为精确要求,则收敛速度 l o g ( d / ϵ ) log(d/\epsilon) log(d/ϵ)
- 函数值确认:无法确认,陡峭则缓慢、平缓则快速
其他近似求解方法
牛顿法等等,用切线计算
四、最优编码树
1. 信息编码
编码:字符映射到二进制序列
解码:二进制序列还原成对应的字符
压缩(有损/无损):用较少的01序列描述原始信息
几种常用的常用的编码方式:
- ASCII编码:将数字、字母和一些常用符号用一个字节编码
- GBK/GB2312:针对简体字的编码,每个汉字用两个字节编码
- Unicode:
- UTF-8:
定长和可变长编码:
- 定长编码:容易得到对应的解码
- 不定长编码:
- 前缀码问题:某些字符编码是其他字符编码的前缀,则容易引发混淆
- 编码长度:
如何提升编码效率?
- 编码效率:编码总长度/字符数= ∑ f i × L i \sum{f_i}\times L_i ∑fi×Li, f i f_i fi 为频率, L i L_i Li为编码长度
- 出现频率高的字符用较短的编码、出现频率较低的用较长的编码,如何设计最优编码呢?
2. 哈夫曼编码
一种信息的无损压缩方法
编码数
-
二叉树:每个节点至多只有两个子节点【0 1 编码】
长度不同具有唯一性,但是会导致前缀码问题,无法正确解码
-
叶节点:
编码效率与码源频次的关系:码位均值最低,编码效率最高
- 构建码位均值最低的编码树:哈夫曼编码树,哈夫曼编码也被称为最优编码
3. 哈夫曼编码算法
构建哈夫曼编码——自下向上/自上向下?
- 自下而上的方法避免了已经形成的叶节点变成子节点、需要再调整的过程。
- Huffman 算法:自下而上构建的最优二叉编码树。
构建最优编码树——1个子节点/2个子节点?
- 优化编码树:满二叉树(否则可以将唯一子节点提升代替其父节点、码位减少)
Huffman编码算法描述:
-
构建Huffman树节点:
从频率集合中选取最小的两个节点 i j、创建其父节点 k, f k = f i + f j f_k=f_i+f_j fk=fi+fj
-
调整频率集合:
添加新建父节点的频率 f_k,删除两个子节点频率 f_i、f_j
两个数据结构
- 一位数组 f[],存放频率
- 二维数组 node[][],huffman树节点
- 符号、频率、左右子节点
Huffman编码伪代码:
遍历哈夫曼树形成编码(深度优先搜索):
算法的正确性:
- 哈夫曼编码是最优编码、但不是唯一最优编码
- 算法运行的正确性:
- 创建哈夫曼树
- 遍历哈夫曼树
算法效率:
- 创建哈夫曼树 O ( n 2 ) O(n^2) O(n2)
- 遍历哈夫曼树 O ( n ) O(n) O(n)
总的时间效率 O ( n 2 ) O(n^2) O(n2),优化的数据结构 O ( n log n ) O(n\log n) O(nlogn)
贪心策略:
每一步都选择当前最好的选择,从而达到全局最优的结果
贪心算法是否能拿到最优结果?不一定,需要看局部最优和全局最优之间的关系
五、优化互联互通的成本(最小生成树)
1. 连通图与生成树
节点、边、图|生成树、支撑树【无环的连通图】
无环连通图:树,如果有n个节点、那么将他们联通起来需要的最小边数为n-1
图的数据表示:边列表
- 可以表示任意规模的图
- 节点集合隐含在边的信息中
算法思路、算法描述与算法分析
算法的效率(假设输入图有m条边): O ( m × n ) O(m \times n) O(m×n)
2. 最小生成树算法
加权图:每条边上关联有一个数值,用来表示在两个节点直接连通的的代价,如修路的成本、铺设管线的难度。
最小生成树:
基于一种生成树算法改造而得的最小生成树的算法
算法的效率(假设输入图有m条边): O ( m × n ) O(m \times n) O(m×n)
3. 算法的正确性
算法分析:为什么它给出的是最小生成树?
树:添一条边,生成一个环、去掉一条边,则会又长出一棵树。
算法的正确性:
- 是否包含权值最小的边?反证法,成环
- 是否存在在E中的一条两端点恰好有一个在V中且权值不是最小的边,生成的树是最小生成树?反证法,假设前i步都是最优结果,第i+1步骤出现问题,反证算法失败。
算法讨论:为什么说它是“贪心算法”?
- 局部最优vs全局最优
六、斐波那契的三种求解算法
斐波那契数列:
来源:
上月成兔+上月幼兔 = 上月总对数
上月幼兔 = 再上月成兔
上月成兔 = 再上月成兔+再上月幼兔=再上月总
当月兔子 = 当月成兔+当月幼兔=上月总+上月成兔= 上月总+再上月总
then F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1)+F(n-2) F(n)=F(n−1)+F(n−2)
1. 递归方法
自上而下,算法效率为 O ( 2 n ) O(2^n) O(2n).适用于其他方法难以解决的问题
缺点在于状态的保存与恢复以及重复计算时间,需要额外的时间消耗
可以提高算法效率嘛?
2. 动态规划(记忆法)
是一种自下而上的计算方法,记录中间过程的解,避免大量的重复计算。
算法复杂度为 O ( n ) O(n) O(n),是一种空间换时间的计算方法。
适用于存在大量重复计算的问题。
3. 矩阵解法
快速幂运算
- 求解 a n a^n an,将a自乘n-1次,时间效率为 O ( n ) O(n) O(n)
- 任意整数n 可以表述为 2的k次幂序列之和,效率为 O ( log 2 ( n ) ) O(\log_2(n)) O(log2(n))
快速幂伪代码:
矩阵解法效率分析:
- 矩阵算法效率 O ( log 2 ( n ) ) O(\log_2(n)) O(log2(n))
- 记忆法 O ( n ) O(n) O(n)
七、最大收益的投资组合
1. 背景
问题的一般描述:将n元钱分成m份,每份>= 0
2. 动态规划
从特殊到一般:在i个项目上的最佳投资的回报能通过在i-1个项目上的回报进行表达
算法思路:
这种计算思路是有最后的兜底的,即
F
1
(
x
)
=
f
1
(
x
)
F_1(x)=f_1(x)
F1(x)=f1(x)
动态规划工作表:
3. 最佳投资组合
在知道最优回报时,如何获取对应的最优回报组合呢?
既得到最优回报,也得到最优回报组合
动态规划既记录 F i ( x ) F_i(x) Fi(x),同时也记录此时在第 i i i 个项目上的投入。
算法描述
伪代码(memo对应工作表,payoff对应各项回报)
小结
如果项目的回报函数不是单调非减的,则算法还有效么?在算法计算时需要加深对数据的理解以及表达式的抽象。
八、路径规划(节点之间的最短路径)
1. 问题引入以及图论基础知识
给出一个图结构,如何得出两个节点之间的最短距离呢?
图的基本概念
单源:求某个源点到其他点间的最短路径——Dijkstra
多源:给定连通图,求任意节点对间的最短路径——Floyd算法
2. 单源最短路径 Dijkstra 算法
依次找到最短路径,下一个最短路径取决于已得到的最短路径+边值。
依次找到最短路径的方法
前驱节点:逻辑类似于最优组合的第i项目
算法伪代码
算法的效率分析:更适用于边数较少的图计算。
3. 多源最短路径 Floyd 算法
适合于连接边比较多的稠密网络
构建子问题:i ->j节点的最短路径
- 节点 1~n,矩阵D[i,j] 表示i-j的距离
- $D^0[i,j] $:初始路径矩阵
- $D^1[i,j] $:经过中转节点“1”转发
- $D^2[i,j] $:经过中转节点“2”转发
- …
算法公式
D
k
[
i
,
j
]
=
m
i
n
{
D
k
−
1
[
i
,
j
]
,
D
k
−
1
[
i
,
k
]
+
D
k
−
1
[
k
,
j
]
}
D^k[i,j] = min\{D^{k-1}[i,j], D^{k-1}[i,k]+ D^{k-1}[k,j]\}
Dk[i,j]=min{Dk−1[i,j],Dk−1[i,k]+Dk−1[k,j]}
动态规划的基本思想:
- 问题分解成相关联的子问题
- 当前子问题需要借助前面子问题的解(不发生重复计算)
如何得到最短路径经过的节点?在发生路径值更新时,更新前驱节点:
伪代码实现:
算法的正确性分析:
严格的证明要使用数学归纳法:
单源路径算法和多源最短路径的算法比较: