文章目录
作为非计算机专业学生,开始学习数据结构,这里主要是学习清华大学的:数据结构-学堂在线慕课(MOOC)平台
并作出相应笔记。
计算
研究对象:找出其规律,总结出技巧。
目标:实现有效和高效的计算,在资源消耗方面足够低廉。
绳索计算机
输入:任给直线
l
l
l及其上一点A
输出:经过A作
l
l
l的一条垂线
(构成3,4,5勾股定理)
这里的计算机是什么?是长度为12的绳索。
计算就是可以重复机械完成的任务。
尺规计算机
这里的计算机是理想的直尺和理想的圆规。
这里的子程序(解决一个更小的问题)是:过直线外一点,做平行线。
算法
计算=信息处理:借助某种工具,遵照一定规则,以明确而机械的形式进行
计算模型=计算机=信息处理工具
有穷性
这里注意:C++中的0是false,应该选择后者。
对于任意n,这个序列的长度是有限的么?有穷才可行。
如果无穷,那么程序未必是算法。
比如:死循环或栈溢出。
好算法
计算模型
性能测试
数据结构与算法(DSA, Data Structures and Programs )
1)引入理想、统一、分层次的尺度
2)运用该尺度,以测量DSA的性能
问题规模
算法分析
主要看运行时间
T
A
(
P
)
{T_A}(P)
TA(P)=算法A求解问题实例P的计算成本
实例太多,所以用划分等价类进行归纳概括
问题实例的规模,往往是决定计算成本的主要因素。
通常:规模越近,计算成本也接近;规模扩大,计算成本亦上升。
最坏情况
特定算法+不同实例
令
T
A
(
P
)
{T_A}(P)
TA(P)=用算法A求解某一问题规模为n的实例,所需的计算成本讨论特定算法A(及其对应的问题)时,简记作
T
(
n
)
{T}(n)
T(n)
然而,这一定义仍有问题。
同一问题等规模的不同实例,计算成本不尽相同,甚至有实质差别。
既然如此,如何定义
T
(
n
)
{T}(n)
T(n)?
稳妥起见,取
T
(
n
)
=
m
a
x
(
T
(
P
)
∣
∣
P
∣
=
n
)
{T}(n)=max(T(P)| |P|=n)
T(n)=max(T(P)∣∣P∣=n),即在规模同为n的所有实例中,只关注最坏(成本最高)者。
理想模型
特定问题+不同算法
同一问题通常有多种算法,如何评判其优劣?
实验统计是最直接的方法,但不足以准确反映算法的真正效率。
不同的算法,可能更适应于不同规模、不同类型的输入。
同一算法,可能由不同程序猿、用不同程序语言、经不同编译器实现,可能实现并运行不同的体系结构、操作系统。(CPU速度)
为给出客观评价,需要抽象出一个理想的平台或模型,不再依赖于上述种种具体的因素,从而直接而准确地描述、测量并评价算法。
图灵机
TM:Turing Machine
-
纸袋Tape 依次均匀地划分为单元格,各注有某一字符,默认为‘#’,每一个小格子是cell
-
字符来自Alphabet(字母表),Alphabet字符的种类有限
-
读写头Head
总是对准某一单元格,并可读取和改写其中的字符,每经过一个节拍,可转向左侧或右侧的邻格。 -
State TM总是处于有限状态中的某一种,每经过一个节拍,可(按照规则)转向另一种状态。
-
Transition Function:(q, c; d, L/R, p)
若当前状态为q且当前指格子内容字符为c; 则将当前字符改写成的一个新的字符d; L/R表示接下来转向左侧/右侧的邻格;转向p状态,一旦转入特定的状态‘h’,则停机。
图灵机实例
功能:将二进制非负整数加一
后四位反转为0,前一位翻转为1
把读写头复位到最初的位置,因为这个可能会成为算法的部分,所以要规范。
规范 :大部分 以接口的形式表现出来
RAM模型
Random Access Machine
和图灵机很像,但也有区别
-
寄存器顺序编号,总数没有限制 R[0],R[1],R[2],R[3],…
-
与TM模型一样,RAM模型也是一般计算工具的简化与抽象
使我们可以独立于具体的平台,虽算法的效率做出可信的比较与评判。 -
在这些模型中,算法的运行时间 转化为 算法需要执行的基本操作次数
T ( n ) T(n) T(n)=算法为求解规模为n的问题,所需执行的基本操作次数,这样可以不用考虑硬件环境。
RAM实例
R[2]是次数
执行过程可以记录为一张表
表的行数即是所执行基本指令的总条数
能够客观度量算法的执行时间
图灵机、RAM等模型为度量算法性能提供了准确的尺度
大O记号
主流长远
大O记号相当于一把尺上的刻度,但又不是很精细,在定性与定量间打到一个适度的折中。用于复杂度分析。
随着问题规模的增长,计算成本如何增长?
注:这里更关心足够大的问题,注重考察成本的总体增长趋势。
渐进分析:在问题足够大后,计算成本如何增长?
Asmptotic analysis:当
n
≫
2
n \gg 2
n≫2后,对于规模为n输入,算法
需执行的基本操作次数:
T
(
n
)
T(n)
T(n)=?
需占用的存储单元数:
S
(
n
)
S(n)
S(n)=? //通常可不考虑
大O记号(big-O notation)
big-O是
T
(
n
)
T(n)
T(n)的悲观估计。只要大略高于
T
(
n
)
T(n)
T(n)即可。
其它记号
Ω
\Omega
Ω 表示small-
Ω
\Omega
Ω, 乐观估计
Θ
\Theta
Θ介于两者之间
高效解
不含转向(循环、调用、递归等),必然顺序执行,即是
O
(
1
)
O(1)
O(1)
但如下几种也可以被表示为
O
(
1
)
O(1)
O(1)
对数多项式复杂度
O
(
log
c
n
)
O({\log ^c}n)
O(logcn)
往往不标明底数,因为在底数为常数的情况下,具体为多少,是没有多大影响的。常数次幂也是没有什么影响的。
O
(
log
c
n
)
O({\log ^c}n)
O(logcn)低于任何一个多项式的复杂度
O
(
n
c
)
O({n ^c})
O(nc),为啥无限接近于常数?
有效解
拒绝访问。 幂函数
多项式中只看最高次项
这类算法的效率通常认为已可令人满意,然而这个标准是否太低了?
n
c
{n^{\rm{c}}}
nc拒绝访问。 即属于多项式。
凡多项式复杂度,即可视作可解。可解,至少还不是难解。
难解
O
(
2
n
)
O({2^n})
O(2n)指数函数
幂函数比指数函数好
举例2-SUBSET
问题描述
S包含n个整数集,
∑
S
=
2
m
\sum {S = 2m}
∑S=2m
S是否有子集T,满足
∑
T
=
m
\sum {T = m}
∑T=m?
∑
S
\
T
=
m
\sum {S\backslash T=m}
∑S\T=m
相当于 原来的n个整数是否可以分为两部分,各部分之和为m?
举“选举人制”为例,美国,获胜的州的选举人数之和
但是,若共有两位候选人,是否可能恰好各得269票?
其中拒绝访问。
除非加上一些限制条件,比如n=?票数的分布符合什么规律?
增长速度
尺度太小,不能表现出增长趋势
所以把尺度变大
算法分析
绪论
“去粗存精”式的估算
两个主要任务=正确性(不变性×单调性)+复杂度
为确定后者,真的需要将算法描述为RAM地基本指令,再统计累计的执行次数?不必!
级数
右上角有个通用公式
有必要讨论这类级数么?
难道,基本操作次数、存储单位数可能式分数?某种意义上是的。
举例如下:
投硬币,投正面的概率为
λ
\lambda
λ,投反面的概率即为
1
−
λ
1-\lambda
1−λ,投多次硬币,投到反面的概率分布为
具体内容,可以看看书《Concrete Mathematics》(具体数学)
循环
可以用面积法来看大小。
i
≪
=
1
i \ll = 1
i≪=1表示想做左移动一格,即乘以2。
几何级数的和与末项同阶
实例:非极端元素+起泡排序
非极端元素
起泡排序
}
正确性的证明
起泡排序
封底估算-1
Back-of-The-Envelope Calculation
除了大O这种方法,还有其他估算方法,封底估算几乎不需要笔纸,就可以得到近似估计。
封底估算-2
在时间伤建立一定的概念
三生三世=10^10sec
三生三世的一天相当于一天中的一秒
宇宙大爆炸的三生三世相当于三生三世当中的0.1秒
Bubblesort起泡排序
蓝色方块硬件横坐标表示1s中执行的次数
紫色方块纵坐标表示执行的总次数
绿色方块表示执行的总秒数
G,M,k.
Mergesort归并算法
迭代与递归
迭代与递归
迭代乃人工,递归方神通
To iterate is human,to recuse,divine.
迭代更加高效
注:循环里的不算复杂度
空间复杂度:
n个输入+累加器+内部循环的控制变量:
O
(
n
)
O(n)
O(n)
累加器+内部循环的控制变量
O
(
2
)
O(2)
O(2)√
我们这里是主要考量除了输入所占的空间外我们需要的另加的用于计算的所必须的总量
减而治之
(Decrease-and-conquer)
递归跟踪
(几何)
数组求和:线性递归
递归跟踪的应用范围:直观形象,仅适用于简明的递归模式
递归方程
(代数)
间接抽象,更适用于复杂的递归模式
实例
求解过程
数组倒置
代码注释中“需要两个递归基”的含义是:在问题规模缩减为0或1时,停止递归
因为“递归基”是递归函数的一种平凡情况,只有有递归基,递归才不会一直进行下去。
分而治之
数组求和:二分递归
int mi=(lo+hi)>>1;
表示取lo和hi的中间(lo+hi的和除以2)
递归基的形式:lo==hi
如何看复杂度?
第一种(按递归跟踪)
(按几何级数)
几何级数的和与末项同阶
第二种(按递归方程)
二分递归:MAX2
Max2:迭代1
如何改进?
没有实质改进,最坏情况时的时间复杂度是一样的,最好的不一样。
Max2:迭代1
递归+分治
算法复杂度有确定的下降
动态规划
动态规划
fib( ):递归
Project库
project右击属性——调试——命令行参数可以改数字
二分递归会很慢
递推方程
1是两者相加得出来的
第n项的通项公式,
O
(
Φ
n
)
O({\Phi ^n}{\rm{) }}
O(Φn)
封底估算
记住**
Φ
36
=
2
25
{\Phi ^{36}} = {2^{25}}
Φ36=225**
Φ
43
=
2
30
=
1
0
9
f
l
o
=
1
sec
{\Phi ^{43}} = {2^{30}} = {10^9}flo = 1\sec
Φ43=230=109flo=1sec,
其中
(
2
10
)
3
=
(
1
0
3
)
3
{({2^{10}})^3} = {({10^3})^3}
(210)3=(103)3,
1
0
9
{10^9}
109是现在主流计算机一个gigahertz主频的CPU,所以用二分递推方法时,从43开始延时。
座椅这个算法不好。
递推跟踪
先后出现的递归实例,共计
O
(
Φ
)
O(\Phi )
O(Φ)个,而去除重复之后,总共不过
O
(
n
)
O(n)
O(n)种。
迭代
T
(
n
)
=
O
(
n
)
T(n) = O(n)
T(n)=O(n)。而且仅需
O
(
1
)
O(1)
O(1)空间。而之前那个方法需要
O
(
n
)
O(n)
O(n)空间。
最长公共子序列
LCS:递推
计算最长公共子序列的长度
递归
0)那边对应着递归基的作用
1)如果序列A和序列B的最后一个字符是相同的
##LCS:理解
LCS:复杂度
为了求出序列A(长度为m)和序列B(长度为n)的解,我们要唤醒(a,b)多少次呢
表示在n-a+m-b中挑选n-a种水平路径的总数
这个和“=”后面的式子是等效的。
LCS:迭代(动态规划)
总结
递归:设计出可行且正确的解
动态规划:消除重复计算,提高效率。