目录
算法的定义、特性、要求
定义
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有序序列,并且每条指令表示一个或多个操作
特性
- 输入输出:零个或多个输入,一个或多个输出
- 有穷性:不能出现死循环,处理时间可接受
- 确定性:每一个步骤都有确定的含义,没有二义性
- 可行性:每一个步骤都能通过执行有限次数完成
算法设计的要求
- 正确性
- 可读性
- 健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果
- 时间效率高和存储量低
算法效率
时间效率
算法的执行时间
- 事后统计
- 将算法实现,测算其时间和空间开销
- 缺点:编写程序实现算法将花费较多时间精力;时间的比较依赖计算机软硬件等环境因素,掩盖算法本身的优劣
- 事前分析
- 对算法所消耗资源的一种估算方法
算法运行时间 = ∑(每条语句的执行次数 × 该语句执行一次所需的时间)我们假设执行每条语句所需的时间均为单位时间。
所以我们讨论该算法中所有语句的执行次数,即频度之和
算法运行时间 = ∑每条语句频度
算法的时间复杂度 T(n)=O(f(n))
O(f(n))为算法的渐进时间复杂度
for (i = 1; i <= n; i++) // 执行n+1次,因为最后i++到n+1,没有进入循环,但进行判断也算1次 { for (j = 1; j <= n; j++) { /* 最外面这个循环(第1个for)的循环体 第一个for的循环体执行了n次 (第n+1是判断的,没有真正执行进去,循环体只执行了n次,之后同理) 每执行1次,这条for(第2个for)都要执行n+1次,所以一共执行了n*(n+1)次 */ c[i][j] = 0; // 第2个for循环的循环体,外层循环执行1次,内层循环执行n次,所以执行n*n次 for (k = 0; k < n; k++) { /* 在两层for循环里面,要执行n*n次 这条for(第3个)每执行1次,都要执行n+1次,所以一共执行了n*n*(n+1)次 */ c[i][j] = c[i][j] + a[i][k] * b[k][j]; // 在三层for循环里面,一共执行了n*n*n次 } } }
算法耗费时间:T(n)=2n³ + 3n² + 2n + 1
我们比较函数的数量级
渐进时间复杂度:T(n) = O(n³)
时间复杂度由嵌套层次最深的语句的频度决定的
常数阶O(1)
int sum = 0, n = 100; /* 执行一次 */ sum = (1 + n) * n / 2; /* 执行一次 */ printf("%d", sum); /* 执行一次 */
执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶
(不管这个常数是多少,我们都记做O(1),而能不是O(3)等其他任何数字)
线性阶O(n)
单层循环结构
int i; for(i = 0; i < n; i++) { /* 时间复杂度为O(1)的程序步骤序列 */ }
对数阶O(logn)
int i = 1; while (i <= n) { i = i * 2; /* 执行log₂n次 */ /* 时间复杂度为O(1)的程序步骤序列 */ }
若循环1次:i = 1 * 2 = 2
若循环2次:i = 2 * 2 = 2²
若循环3次:i = 2² * 2 = 2³
若执行x次:i = 2ˣ
∵ i <= n
∴2ˣ <= n
∴x <= log₂n
平方阶O(n²)
int i,j; for(i = 0; i < n; i++) { for(j = 0; j < n; j++) { /* 时间复杂度为O(1)的程序步骤序列 */ } }
时间复杂度排序
一般指的是最坏时间复杂度
: O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!) < O(nⁿ)
空间复杂度S(n) = O(f(n))
n为问题规模,f(n)为算法语句关于n所占存储空间的函数
算法要占据的空间:
- 算法本身要占据的空间,输入、输出、指令、常数、变量
- 算法要使用的辅助空间
for (i = 0; i < n / 2; i++) { t = a[i]; // t是临时空间,就是辅助空间 a[i] = a[n - 1 - i]; a[n - 1 - i] = t; }
这是将一维数组 a 中的 n 个数逆序存放到原数组中的算法
在此算法中,除了要存放变量之外,需要额外的存储空间。t 作为临时空间,就是辅助空间