目录
1. 通俗地讲
- 算法是解决计算问题的方法
2. 算法的五大特性
- 输入总数 >= 0
- 输出数量 >= 1
- 有穷性
- 确定性
- 可行性
3. 众所周知的“公式”
程序 = 数据结构 + 算法
4. 举个例子
例 1. 百钱买百鸡
公鸡,五钱一只;母鸡,三钱一只;小鸡,一钱三只。
一百钱买一百只鸡,如何买?
1. 数学解法
解:设购买公鸡 x 只,母鸡 y 只,小鸡 z 只
\[
\left\{ \begin{aligned} x + y + z = 100 \\ 5x + 3y + \frac z3 = 100 \end{aligned} \right.
\]
2. C 的解法
#include <stdio.h> void buy_chicken(); int main() { buy_chicken(); return 0; } void buy_chicken() { int cocks = -1, hens, chicks; while (cocks < 20) { cocks++; hens = -1; while (hens < 33) { hens++; chicks = 100 - cocks - hens; if (chicks % 3) { continue; } if (5*cocks + 3*hens + chicks/3 == 100) { printf("百钱可买公鸡 %2d 只,母鸡 %2d 只,小鸡 %d 只\n", cocks, hens, chicks); } } } }
3. Python 的解法
# coding:utf-8 def buy_chicken(): for cocks in range(20): for hens in range(33): chicks = 100 - cocks - hens if chicks % 3 == 0 and 5*cocks + 3*hens + chicks//3 == 100: print(f"百钱可买公鸡 {cocks:>2} 只,母鸡 {hens:>2} 只,小鸡 {chicks} 只") return None if __name__ == "__main__": buy_chicken()
4. Java 解法
public class BaiJiwenti { public static void main(String[] args) { buy_chicken(); } public static void buy_chicken() { for (int x=0; x<20; x++) { for (int y=0; y<33; y++) { int z = 100 - x - y; if (z % 3 == 0 && (5*x + 3*y + z/3 == 100 )) { System.out.printf("百钱可买公鸡 %2d 只,母鸡 %2d 只,小鸡 %d 只\n", x, y, z); } } } } }
5. 小结
- 对于“百鸡问题”这一类甚至更多的问题,虽然不同语言的语法不尽相同,但解起来思路一致,关键几句更是如出一辙
5. 算法衡量
- 衡量算法应该剔除机器配置,运算数量等无关因素
5.1 有这样两个指标
- 时间复杂度 T(n)
- 空间复杂度 S(n)
5.2 大 O 记法
对于单调的整数函数,如果存在一个整数函数 g 和实常数 c (c>0),使得对于充分大的 n 总有
f(n) <= c * g(n)
即函数 g 是 f 的一个渐进函数(忽略常数),记为
f(n) = O(g(n))
即在趋向无穷的极限意义下,函数的增长速度受到函数 g 的约束
即函数 f y与函数 g 的特征相似
5.3 时间复杂度及其他
5.3.1 定义
- 假设存在函数 g,使得算法 A 处理规模为 n 的问题示例所用时间为
T(n) = O(g(n))
,则称O(g(n))
为算法 A 的渐进时间复杂度,简称时间复杂度,记为T(n)
5.3.2 此外,还有
- 最优时间复杂度
- 最坏时间复杂度
- 平均时间复杂度
- 一般关注最坏时间复杂度
5.3.3 时间复杂度计量规则
- 基本操作,如只有常数项, 时间复杂度是 1
- 顺序结构,时间复杂度按加法计算
- 循环结构,按乘法计算
- 分支结构, 取最大值
- 得到一个方程式,然后在做化简,取方程的阶
5.3.4 判断一个算法的效率
- 关注操作数量的最高次项,其余可忽略
- 若没特殊说明,我们所分析的算法时间复杂度一般指最坏的复杂度
5.3.5 其他
- “大样本统计方法”、“渐进等价”、“渐进展开”等数学概念我就略过了
- 题外话:大家都知道“复”应该读第四声,但在这个短语中“复”读第三声超顺;类似的还有“标志符”、“char”等
6. 举例分析
# coding:utf-8 import sys from time import perf_counter_ns def cal_time(func): def in_(): start = perf_counter_ns() name = func() stop = perf_counter_ns() print(f">>> {name}'s cost times: {stop - start}\n") return in_ @cal_time def solve1(): for cocks in range(101): for hens in range(101): for chicks in range(301): if cocks + hens + chicks == 100 \ and 5*cocks + 3*hens + chicks/3 == 100: print(f"公鸡 {cocks:>2} 只,母鸡 {hens:>2} 只,小鸡 {chicks} 只") return sys._getframe().f_code.co_name @cal_time def solve2(): for cocks in range(20): for hens in range(33): chicks = 100 - cocks - hens if chicks % 3 == 0 and 5*cocks + 3*hens + chicks//3 == 100: print(f"公鸡 {cocks:>2} 只,母鸡 {hens:>2} 只,小鸡 {chicks} 只") return sys._getframe().f_code.co_name if __name__ == "__main__": solve1() solve2()
- 不同的机子运算速度不同,但可以肯定的是 solve2() 比 solve1() 快,而且快挺多
- 简单地说,solve1() 的循环嵌套比 solve2() 的多一层,这就决定了它的时间复杂度要多一个幂次
- solve1() 的 T(n) = O(mnk)
- solve2() 的 T(n) = O(mn)