算法与算法分析
- 初识算法
- 算法的定义
- 算法的描述
- 算法与程序
- 算法的特性
- 算法设计的要求
- 算法时间效率的度量
- 事前估计法
- 算法时间复杂度的渐进表示法
- 算法时间复杂度案例分析
- 最好、最坏和平均时间复杂度
- 算法的空间复杂度
初识算法
算法的定义
算法,即是对特定问题求解方法和步骤的一种描述。它是指令的有限序列。其中每个指令表示一个或多个操作。
简而言之,算法就是解决问题的方法和步骤。
step1 ……
step2 ……
step 3……
……
算法的描述
- 自然语言
- 流程图:传统流程图、N-S流程图
- 伪代码:类语言、类C语言
- 程序代码:C语言程序……
算法与程序
算法
是解决问题的一个方法或者一个步骤,考虑如何将输入转化为输出,一个程序可以有多种算法。程序
是某种程序设计语言对算法的具体实现。- 程序=数据结构+算法;数据结构通过算法实现操作,算法根据数据结构设计程序。
算法的特性
- 有穷性
- 确定性
- 可行性
- 输入
- 输出
算法设计的要求
- 正确性
- 可读性
- 健壮性
当输入的数据非法时,好的算法能适当地做出正确反应或进行相应处理。 - 高效性
高效性包括时间
和空间
两个方面。
时间高效是指算法设计合理,执行效率高,可以用时间复杂度
来度量。
空间高效是指算法占用存储容量合理,可以用空间复杂度
来度量。
算法时间效率的度量
算法时间效率可以用依据该算法编制的程序在计算机上所消耗的时间
来度量。
两种度量方法:
- 事后统计法
- 事前估计法
事后统计法的缺点很明显,需要将算法实现,需要花费较多的精力,然后测算其时间和空间开销。所以一般是事前估计。
事前估计法
算法运行时间=一个简单操作所需要的时间
×
\times
×简单操作次数
语句频度,即每条语句的执行次数
算法运行的总时间=
∑
\displaystyle \sum
∑每条语句的语句频度
×
\times
×执行该语句所需哟的时间。
每条语句执行一次的时间一般是由机器而异的,与算法本身无关。我们设每条语句执行一次所需的时间均是单位时间。
算法运行的时间就可以转化成频度之和来计算了。
例:两个n
×
\times
×n矩阵相乘
for (i=i;i<=n;i++) //n+1次(最后一次判断,退出循环)
for (j=1;n<=n;j++) //n(n+1)次
{
a[i][j]=0;//n*n次
for(k=1;k<=n;k++) //n*n*(n+1)次
a[i][j]=a[i][k]*a[k][j];//n*n*n次
}
T(n)= 2 n 3 + 3 n 2 + 2 n + 1 2n^3+3n^2+2n+1 2n3+3n2+2n+1
算法时间复杂度的渐进表示法
为了便于比较不同算法的效率,我们仅仅比较它们的数量级。
若有某个辅助函数f(n),当n趋于无穷大时,T(n)/f(n)的极限值为不等于0的常数,则称f(n)是T(n)的同数量级函数,记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度。(O是数量级的符号
),简称时间复杂度。
在上述n阶矩阵相乘的例子中,当n趋于无穷大时,T(n)=O(
n
3
n^3
n3),这就是上述问题的渐进时间复杂度,简称时间复杂度
。
一般情况下,我们只需要考虑算法中基本操作
执行的次数。
当n趋于无穷大时,两者的增长率相同。
T(n)=O(f(n))表示存在正的常数C和
n
0
n_{0}
n0,使得当n≥
n
0
n_{0}
n0时都满足0≤T(n)≤Cf(n)。
算法时间复杂度案例分析
定理
f(n)=
a
m
n
m
+
a
m
−
1
n
m
−
1
+
…
+
a
1
n
+
a
0
a_{m}n^m+a_{m-1}n^{m-1}+…+a_1n+a_0
amnm+am−1nm−1+…+a1n+a0是一个m次多项式,则T(n)=O(
n
m
n^m
nm)。
在计算算法时间复杂度时,可以忽略所有低次幂项和最高次幂的系数,这样可以简化算法分析,也体现出了增长率的含义。
分析算法时间复杂度的基本方法
- 找出
语句频度最大
的那条语句作为基本语句; - 计算
基本语句的频度
得到问题规模n的某个函数f(n); - 取数量级用符号
'O'
表示;
x=0; //1次
y=0; //1次
for (int k=0;k<n;k++) //n+1次
x++; //n次
for (int i=0;i<n;i++) //n+1次
for (int j=0;j<n;j++) //n(n+1)次
y++;//n*n次
T(n)=O(
n
2
n^2
n2)
时间复杂度是由嵌套最深层语句的频度决定的。
- 常量阶示例
x++;
s=0;
两条语句的频度均为1,算法的执行时间是一个与问题规模n无关的常数,所以算法的时间复杂度为T(n)=O(1)。
再如:
for (i=0;i<1000;i++) //1001次
{
x++; //1000次
s=0;
}
上面算法的执行次数虽然是1000次,但是它不会随着问题规模n的增长而增长,即使这个常数再大,算法的时间复杂度都是O(1)
- 线性阶示例
for (i=0;i<n;i++) //n+1次
{
x++; //n次
s=0;
}
T(n)=O( n n n)
- 平方阶示例
x=0;y=0;
for (k=1;k<=n;k++)
x++; //n次
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
y++; //n*n次
当有若干个循环语句时,算法的时间复杂度是由最深层循环内的基本语句的频度f(n)决定的。
- 对数阶示例
for(i=1;i<=n;i=i*2)
{x++;s=0}
此算法的时间复杂度T(n)=O( log 2 n \log_{2}n log2n)
常见的时间复杂度按数量级递增排列依次为:常量阶O(1)、对数阶O(
log
2
n
\log_{2}n
log2n)、线性阶O(n)、线性对数阶O(
n
l
o
g
2
n
nlog_{2}n
nlog2n)、平方阶O(
n
2
n^2
n2)、立方阶O(
n
3
n^3
n3)、n次方阶(
n
k
n^k
nk),指数阶(
2
n
2^n
2n)等
最好、最坏和平均时间复杂度
例:对于某些问题的算法,其基本语句的频度不仅仅于问题的规模相关,还依赖于其他因素。
在一维数组a中查找某个值等于e的元素,并返回其所在位置。
for (i=0;i<n;i++)
if (a[i]==e) return i+1;
return 0;
若在n个元素中,必有一个元素等于e,其中会有最好的情况,那就是a[0]=e,此时不论数组多大,时间复杂度都是O(1);有最坏的情况,那就是数组的最后一个元素才和e相等,此时的时间复杂度为O(n).
一般情况下,可假设待查找的元素在数组中所有位置上出现的可能性均相同,则可取语句的频度在最好情况与最坏情况下的平均值,即f(n)=n/2,作为它的度量。
称算法在最好情况下的时间复杂度为最好时间复杂度
,指的是算法计算量可能达到的最小值;称算法在最坏情况下的时间复杂度为最坏时间复杂度
,指的是算法计算量可能达到的最大值;算法的平均时间复杂度
是指算法在所有可能情况下,按照输入实例以等概率出现时,算法计算量的加权平均值。
我们通常只讨论算法在最坏情况下的时间复杂度,因为最坏情况下,就是算法执行时间的上界。
算法的空间复杂度
作为算法所需存储空间的量度,简称空间复杂度
,它也是问题规模n的函数,记作:
S(n)=O(f(n))
一般情况下,一个程序在机器上执行时,除了需要寄存本身所用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的辅助存储空间
。其中,对于输入数据所占的具体存储量取决于问题本身,与算法无关,这样只需分析该算法在实现时所需要的辅助空间就可以了。
例:
数组逆序问题:
第一个和倒数最后一个交换,第二个和倒数第二个交换……
for (i=0;i<n/2;i++)
{
tmp=a[i];
a[i]=a[n-1-i];
a[n-1-i]=tmp;
}
在这个算法中,只需要有一个中间变量就够了。所需要的辅助空间即为常数,与数组的长度无关,所以空间复杂度为O(1)
定义一个新的数组,用来存放a数组的逆序
for (i=0;i<n;i++)
b[i]=a[n-1-i];
for (i=0;i<n;i++)
a[i]=b[i];
在这个算法中,需要一个数组长度为n的数组b来解决问题,所以算法的空间复杂度为S(n)=O(n)
对于一个算法,其时间复杂度和空间复杂度往往是相互影响的,当追求一个较好的时间复杂度时,可能会导致占用较多的存储空间,即可能会使空间复杂度的性能变差,反之亦然。
不过,通常情况下,鉴于运算空间较为充足,人们都以算法的时间复杂度
作为算法优劣的衡量指标
。