本文引用 《大花设计模式》 所做笔记
算法:算法解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。(简单来说,算法是描述解决问题的方法)
算法的特性
算法具有5个基本特性:输入,输出,有穷行,确切性和可行性
- 输入输出
简单两句话(话不在多,在简):
算法具有零个或多个输入
算法具有一个或多个输出
- 有穷行
指算法在执行有限步骤之后自动结束,不会出现无限循环,并且每个步骤都在可以接受的时间内完成
- 确切性
指算法的每个步骤都被精确定义而无歧义
当你犹豫不决的时候,说明的你的目标不明确。 – 渡一教育 成哥
- 可行性
指算法的每一步都必须是可行的(也就是说,每一个步骤都能通过执行有限次数完成)
算法的设计要求
- 正确性
算法的正确性:指算法至少应该具有输入,输出和加工处理无歧义,能够反映问题需求并能的得到正确答案
正确性的层次:
- 算法程序没有语法错误(基本)
- 算法程序对于合法的输入数据能产生满足要求的输出结果
- 算法程序对于非法的输入数据能够得出满足规格说明的结果(标准)
- 算法程序对于精心选择,甚至刁难的测试数据都有满足要求的输出结果(高度)
- 可读性
面对开发人员之间的阅读与交流
- 健壮性
当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果
- 高效率和低存储
设计算法应该尽量满足时间效率高和存储量低的需求
综上:好的算法应该满足 正确性,可读性,健壮性,高效率,低存储量的特征
算法效率的度量方法
- 事后统计方法
事后统计方法:通过设计好的程序和数据,利用计算机计时器对不同算法的编制的程序的运行时间进行比较,从而确定算法效率的高低
缺陷:
- 必须事先编织好程序,测试后,如果发现算法很糟糕,那么一切都是白费力气
- 受计算机硬件和软件的影响(cpu,内存,操作系统)
- 测试数据设计困难
- 事前分析估算方法
事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算
一段高级程序语言在计算机上运行消耗的时间取决于因素:
- 算法采用的策略,方法(算法好坏的根本)
- 编译产生的代码质量(软件支撑)
- 问题的输入规模
- 机器执行指令的速度(硬件性能)
所以,一个程序的运行时间, 主要 依赖于 算法的好坏和问题的输入规模(只输入量的多少)
例如:求 1 - 100 之间的和
迭代法:
int sum = 0; // 执行 1 次 for (int i = 1; i < 101; i++) { sum += i; // 执行 i 次 } System.out.println(sum); // 执行 1 次 // 执行了 i + 2 次
数学法:
int sum = 0,num = 100; // 执行 1 次 sum = num/2*(num+1); // 执行 1 次 System.out.println(sum); // 执行 1 次 // 执行了 3 次
所以 ,此时数学法 更胜一筹
算法时间复杂度
进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化并确定T(n)的数量
-
常数阶
int sum = 0,num = 200; // 执行了 第 1 次 sum = num/2*(num+1); // 执行了 第 1次 sum = num/2*(num+1); // 执行了 第 2次 sum = num/2*(num+1); // 执行了 第 3次 sum = num/2*(num+1); // 执行了 第 4次 sum = num/2*(num+1); // 执行了 第 5次 sum = num/2*(num+1); // 执行了 第 6次 sum = num/2*(num+1); // 执行了 第 7次 System.out.println(sum); // 执行了 第 1 次
注意: 无论单条语句执行多少次 ,都计作 O(1) ,又称为 常数阶
对于分支结构而言,无论是真是假,其时间复杂度也是 O(1)
-
平方阶
主要是循环内部
int n = 100; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { System.out.println(j); } }
分析:当i= 0 时 ,内部执行了 n次
当i=1时,内部执行了n-1次
当i= 2 时,内部执行了 n-2次
所以 ,总的执行次数 n+(n-1)+(n-2)+…+1= n^2/2 + n/2
推倒大O阶的方法:
第一条,加法常数不予考虑;
第二条,只保留高阶项
综上,这段代码的时间复杂度为 O(n^2)
-
对数阶
int n = 100,count = 1; // 执行 1 次 while(count < n){ count *= 2; }
分析: 每次 count * 2 ,就距离 n 就 更近一分 ,所以 就是 2^x = n -> x = log2^n
所以 时间复杂度 为 O(logn)
思考: 数学很重要
常见时间复杂度
执行次数函数 | 阶 | 非正式术语 |
---|---|---|
1 2 | O(1) | 常数阶 |
2n + 3 | O(n) | 线性阶 |
3n^2 + 2n + 1 | O(n^2) | 平方阶 |
5log2^n + 20 | O(logn) | 对数阶 |
2n+3nlog2^n+19 | O(nlogn) | nlogn阶 |
6n3+2n2+3n+4 | O(n^3) | 立方阶 |
2^n | O(2^n) | 指数阶 |
常用时间复杂度所耗费时间从小到大一次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n ^3) < O(2^n) < O(n!) < O(n^n)
注意: 当多项式组合在一起时,按照此顺序取右边的
最坏情况与平均情况
最坏情况:我们提到的运行时间都是最坏情况的运行时间
平均情况:所有情况中最有意义的,因为它是所期盼的运行时间
算法空间复杂度
空间复杂度指空间需求,时间复杂度指运行时间需求,通常说的复杂度 主要指 时间复杂度
此处略去一万字