文章目录
- 1. 算法的定义及特性
- 算法的特性
- 算法设计的要求
- 2. 算法的时间复杂度
- 分析算法时间复杂度的基本方法
- 算法时间复杂度分析例题
- 算法时间复杂度的计算
- 3. 算法的空间复杂度
1. 算法的定义及特性
算法的定义
- 对特定问题求解方法和步骤的一种描述,它是指令的有限序列,其中每个指令表表示一个或多个操作。
- 简而言之:算法是解决问题的方法和步骤。
算法的描述
- 自然语言:英语、中文
- 例如:算法,求一元二次方程的根,这个算法就能用文字描述出来。.
- 这种方法描述起来就很麻烦了。
- 流程图:传统流程图、NS 流程图。
- NS 流程图比较简洁,更适合描述结构化的程序的算法。
- 伪代码:类语言:类 C 语言。
算法与程序
- 算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法。
- 程序是用某种程序设计语言对算法的具体实现。
- 程序 = 数据结构 + 算法
- 数据结构通过算法实现操作。
- 算法根据数据结构设计程序。
算法的特性
一个算法必须具备一下五个重要特性
- 有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
- 确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
- 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
- 输入:一个算法有零个或多个输入,算法可以没有输入。
- 输出:一个算法有一个或多个输出,算法必须要有输出。
算法设计的要求
- 正确性:算法满足问题的要求,能正确解决问题,算法转化为程序后要注意:
- 程序中不含语法错误;
- 程序对于几组输入数据能够得出满足要求的结果;
- 程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果;
- 程序对于一切合法的输入数据都能得出满足要求的结果。
- 可读性:
- 算法主要是为了人的阅读和交流,其次才是为计算机执行,因此算法应该易于人的理解;
- 另一方面,晦涩难懂的算法容易隐藏较多的错误而难以调试。
- 健壮性:
- 指当输入非法数据时,算法恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。
- 处理出错的方法,不应该是中断程序的执行,而应该是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。
- 高效性:要求花费尽量少的时间和尽量低的空间。
评价算法优劣的基本标准
- 一个好的算法首先要具备正确性,然后是健壮性、可读性,在几个方面都满足的情况下,主要考虑算法的效率,通过算法的效率高低来评判不同算法的优劣程度
- 算法效率从以下两个方面开考虑:
- 时间效率:指的是算法所耗费的时间;
- 空间效率:指的是算法执行过程中所耗费的存储空间。
- 时间效率和空间效率有时候是矛盾的。
2. 算法的时间复杂度
算法时间效率的度量
- 算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
- 两种度量方法:
- 事后统计:
- 将算法实现,测算其时间和空间开销。
- 缺点:编写程序实现算法将花费较多的时间和经历;所得到的实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣。
- 事前分析:
- 对算法所消耗资源的一种估算方法。
- 事后统计:
事前分析方法
- 一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行的一种简单的操作(如:赋值、比较、移动等)所需要的时间与算法中进行的简单操作次数乘积。
- 算法运行时间 = 一个简单操作所需的时间 x 简单操作次数。
- 也即算法中每条语句的指向时间之和
- 算法运行时间 = ∑ 每条语句的执行次数 x 该语句执行一次所需要的时间。
- 其中,每条语句的执行次数称为语句频度。
- 每条语句执行一次所需要的时间,一般随机器而异。取决于机器的指令性能,速度以及编译的代码质量。是由机器本身软硬件环境决定的,它与算法无关。
- 所以,可以假设执行每条语句所需的时间单位均为单位时间。此时对对算法的运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和。
举个例子:两个 n x n 矩阵相乘的算法可描述为
- 将所有语句的执行次数相加
- 把算法耗费的时间定义为该算法中每条语句的频度之和,则上述算法的时间消耗为 T(n) 为:T(n) = 2n3 + 3n2 + 2n + 1
算法时间复杂度的渐进表示法
- 为了便于比较不同算法的时间效率,我们仅比较它们的数量级(抓次方最大的)。
- 例如:两个不同的算法,时间消耗分别是:T₁(n) = 10n2 与 T₂(n) = 5n3 。数量级(次方)越大的越不好。
- 若有某个辅助函数 f(n),使得当 n 趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称 f(n) 是 T(n) 的同数量级函数。记作 T(n) = O(f(n)),成 O(f(n)) 为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度。
对于求解矩阵相乘问题,算法耗费时间为:
- 一般情况下,不必计算所有操作的执行次数,而只考虑算法中基本操作执行的次数,它是问题规模 n 的某个函数,用 T(n) 表示。
算法时间复杂度定义
- 算法中基本语句重复执行的次数是问题规模 n 的某个函数 f(n)没算法的时间量度记作:T(n) = O(f(n))。
- 它表示随着 n 的增大,算法执行的时间增长率和 f(n) 的增长率相同,成为渐进时间复杂度。
分析算法时间复杂度的基本方法
- 找出语句频度最大(执行次数最多)的那条语句作为基本语句;
- 计算基本语句(执行多少次)的频度得到问题规模 n 的某个函数 f(n);
- 取其数量级用符号 “O” 表示。(函数 f(n) 抓大头,并且去掉大头的系数,剩下的就是数量级)。
举个例子
int x = 0,y = 0;//执行1次
for(int k = 0;k < n;k++)//这条for语句执行n + 1次
{
x++ ;//这个循环体执行n次
}
for(int i = 0;i < n;i++)//执行n + 1次
{
for(int j = 0;j < n;j++)//外层循环每执行1次这个for循环就要执行n + 1次
//所以这个for循环要执行 n * (n + 1)次
{
y++ ;//执行n * n 次
}
- 循环体中,嵌套层次最深的,往往就是执行次数最多的语句。
- 所以这段代码执行次数最多的是函数f(n)=n * (n+1) 次。
- 抓大头之后,这段代码的数量级就是 n2,时间复杂度就是T(n) = O (n2) 。
算法时间复杂度分析例题
- 时间复杂度是由嵌套最深层的语句的频度决定的。
【例1】
- 直接先找嵌套最深层的语句。
- 外层循环从 0 到 m-1 执行 m 次,内层循环从 0 到 n-1 执行 n 次。外层循环每执行一次,内部循环就要执行 n 次,外面的循环总共执行了 m 次,那么总共就执行了 n * m次。
- 构造的函数就是 f(n) = m * n,这个函数的数量级就是 T(n) = O(m * n)。
【例2】:N x N 矩阵相乘
- 去找执行次数最多的语句。
- 这条语句的循环次数采用瞪眼法可知为 n * n * n 次。
- 可知算法的时间复杂度为:T(n) = O(n3)。
【例3】
- 找到执行次数最多的语句:x = x + 1
- 这条最深层循环内的基本语句的频度,依赖于各层循环变量的取值,由内向外可分析出 x = x + 1 的执行次数为:
- 则该算法的时间复杂度为 T(n) = O(n3),称为立方阶。
【例4】
- 找执行次数最多语句:i = i * 2
- 找出函数 f(n):
- 若循环执行 1 次:i = 1 * 2 = 21;
- 若循环执行 2 次:i = 2 * 2 = 22 ;
- 若寻欢执行 3 次:i = 4 * 2 = 23;
- 若循环执行 x 次:i = 2x;
- 假设语句 2 执行次数为 x 次,由循环条件 i <= n可知,2x <= n,所以 x <= log₂n
- 取 f(n) <= log₂n,取最大值 f(n) = log₂n,所以该程序段的时间复杂度 T(n) = O(log₂n)。
算法时间复杂度的计算
- 在有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同。
- 最好的情况是第一个元素就是要查找的元素,最坏则是最后一个元素,循环体执行的次数和 e 实际放的位置有关。
- 平均时间复杂度为:O(n)。
考虑算法的时间复杂度时还应考虑
- 最坏时间复杂度:指在最坏情况下,算法的时间复杂度。
- 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
- 最好时间复杂度:指在最好情况下,算法的时间复杂度。
- 一般总是在考虑最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长。
简便表示函数数量级
- 对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大 O 加法法则和乘法法则,计算算法的时间复杂度:
- 加法规则:如果函数可能分成两个函数,那么就分别求解这两个函数的数量级,然后求这两个函数之间数量级的最大值。
- 乘法法则:分成两个稍微简单点的函数,然后求这两个函数的数量级的乘积,乘积结果就是整个函数的数量级。
算法时间效率的比较
- 当 n 取得很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊。
- 在设计算法的时候,尽量设计时间复杂度低的。
3. 算法的空间复杂度
- 空间复杂度:算法所需要的存储空间的度量,
- 记作:S(n) = O(f(n))
- 其中 n 为问题的规模(或大小)。
- 算法所要占据的空间:
- 算法本身要占据的空间,输入/输出,指令,常数,变量等。
- 算法要使用的辅助空间。
算法空间复杂度分析例题
【例1】将一维数组 a 中的 n 个数逆序存放到原数组中。
-
算法1:
- 让第一个和最后一个交换,让第二个和导数第二个进行交换,依次类推。
- 这个时候总共需要交换一半(n / 2)的元素。
- S(n) = O(1):原地工作。
- 算法2:
- 有两个数组,a、b,将 a 数组中的元素从最后一个开始依次放到 b 数组中。
- 将 a 数组中的元素逆序放到 b 中后,还需要放回原来的数组 a 中。
- S(n) = O(n):a 数组有多大,作为辅助空间的 b 数组就得有多大。