1. 概念
- 数据结构和算法本身解决的是“快”和省得问题,即如何让代码运行得更快,运行时更省内存空间。
- 如何衡量代码的执行效率?时间和空间复杂度。
2.事后统计法
事后统计法:运行代码,通过统计、监控就能得到算法执行的时间和占用的内存大小
缺点:
-
测试结果非常依赖环境
-
测试结果搜数据规模影响很大
我们需要一个不用具体的跑代码,就可以粗略地估算算法的执行效率的方法,这就是时间、空间复杂度。
3.大O复杂度表示法
3.1 概述
算法的执行效率,粗略地将,就是算法代码执行的时间
大O时间复杂度实际上并不具体表示具体代码真正的执行时间,而是表示代码执行时间随着数据规模增长的变化趋势,所以也叫做渐进时间复杂度(asymptotic time complexity,简称时间复杂度
3.2 时间复杂度分析方法
3.2.1 只关注循环执行次数最多的一段代码
private static int cal(int n) {
// 总复杂度 2n + 2 -> O(n)
int sum = 0; // 1
int i = 1; // 1
for(; i <= n; ++i) { // n
sum += i; // n
}
return sum;
}
3.2.2 加法法则:总复杂度等于量级最大的那段代码的复杂度
private static int cal3(int n) {
int sum1 = 0;
int p = 1;
// 总 2n² + 4n + 1 -> O(n²)
// 1 O(1)
for(; p <= 100; ++p) { // 100
sum1 += p; // 100
}
// 2n O(n)
int sum2 = 0;
int q = 1;
for (; q <= n; ++q) { // n
sum2 += q; // n
}
// 2n² + 2n -> O(n²)
int sum3 = 0;
int i = 1;
int j = 1;
for(; i <=n; ++i) { // n
j = 1; // n
for(; j <=n; ++j) { // n²
sum3 = sum3 + i * j; // n²
}
}
return sum1 + sum2 + sum3;
}
3.2.3 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
private static int cal4(int n) {
int ret = 0;
int i = 1;
// 总: O(n)*O(n) = O(n²)
for(; i < n; ++i) { // n -> O(n)
ret += f(i); // n
}
return ret;
}
/**
* O(n)
* @param n
* @return
*/
private static int f(int n) { // 2n -> O(n)
int sum = 0;
int i = 1;
for(; i < n; ++i) { // n
sum += i; // n
}
return sum;
}
3.3 几种常见复杂度实例分析
3.3.0 常用复杂度
- 常量阶 O(1)
- 对数阶 O(logn)
- 线性阶 O(n)
- 线性对数阶 O(nlogn)
- 平方阶 O(n²), 立方阶 O(n³) …
- 指数阶 O(2^n)
- 阶乘阶 O(n!)
- 以上复杂度量级,可以分为 多项式量级 和非多项式量级,其中 O(2^n) 和 O(n!)为非多项式量级。当数据规模n越来越大师,非多项式量级算法的执行时间会急剧增加,求解问的的执行时间会无限增长。所以,非多项式时间复杂度的算法其实是翡翠低效的算法。下图为例,选取了
y=50^x
,y=2^x
,y=x
,y=x!
的曲线图:
3.3.1 O(1)
/**
* 一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是O(1).
* O(1)
* @return
*/
private static int o1() {
int sum = 0;
int i = 1;
for(; i < 1000000; ++i) { // n
sum += i; // n
}
return sum;
}
一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是O(1).
3.3.2 O(n)
/**
* O(n)
* @param n
* @return
*/
private static int f(int n) { // 2n -> O(n)
int sum = 0;
int i = 1;
for(; i < n; ++i) { // n
sum += i; // n
}
return sum;
}
3.3.3 O(logn)
/**
* O(logN)
* 等比数列
* 3¹,3²,3³,...
* 即 3^x = n, x即循环次数, 所以 x = log(3)N,
* 由换底公式 log(a)b = log(c)b / log(c) a;
* 所以 log(3)N = log(2)N / log(3)2 = log(100)N / log(3)100 = log(x)N / log(3) x. <br/>
* 其中 log(3)x为 为常量,忽略, -> N -> ∞ ,log(3)N = log(x)N, 统一记为 O(logN).
* @param n
* @return
*/
private static int cal5(int n) {
int i = 1, cnt = 0;
while (i <= n) {
i = i*3;
cnt ++;
}
System.out.println("i/n=" + cnt + "/" + n);
return i;
}
3.3.4 O(nlogn)
/**
* O(nlogn)
* @param n
*/
private static void cal6(int n) {
// 总 O(n)*O(logN) -> O(nlogn)
for(int i = 1; i <= n; i++) { // n -> O(n)
int j = 1; // n
while (j <= i) { // log(2)N -> O(logN)
j = j * 2; // log(2)N
}
3.3.5 O(m+n)
/**
* O(m+n)
* @param m
* @param n
* @return
*/
private static int cal7(int m, int n) {
int sum1 = 0;
int i = 1;
// O(m)
for(; i < m; ++i) { // m
sum1 = sum1 + i; //m
}
int sum2 = 0;
int j = 1;
// O(n)
for(; j < n; ++j) { // n
sum2 = sum2 + j; // n
}
return sum1 + sum2;
}
3.3.6 O(m*n)
/**
* O(m*n)
* @param m
* @param n
* @return
*/
private static void cal7mn(int m, int n) {
int total = 0;
// 2m*n + m -> O(m*n)
for(int i= 0; i < m; ++i) { // m
for(int j=0; j<n;j++) { // m*n
total = i*j; // m*n
}
}
}
3.4 空间复杂度
3.4.1 概念
与时间复杂度一样,也表示执行时间或空间与数据规模之间的增长关系,定位为:
空间复杂度:全称为渐进空间复杂度(aysmptotic space complexity), 表示算法的存储空间与数据规模之间的增长关系。
3.4.2 几种常见的复杂度
3.4.2.1 O(1)
/**
* O(1)
* 空间复杂度
*/
private static void space1() {
int i = 0;
int[] a = new int[10000]; // 100 -> O(1)
for(; i<10000; ++i) {
a[i] = i * i;
}
for(int j=99; j >=0; j--) {
System.out.println(a[j]);
}
}
3.4.2.2 O(n)
/**
* O(n)
* 空间复杂度
* @param n
*/
private static void space2(int n) {
int i = 0;
int[] a = new int[n]; // n -> O(n)
for(; i<n; ++i) {
a[i] = i * i;
}
for(int j=n-1; j >=0; j--) {
System.out.println(a[j]);
}
}
3.4.2.3 O(n²)
/**
* O(n²)
* 空间复杂度
* @param n
*/
private static void space3(int n) {
List<Integer> list = new ArrayList<>();
// 总 n² -> O(n²)
for(int i=0; i<n; i++) { // n
for(int j=0; j<i; j++) { // n²
list.add(new Random().nextInt()); // n²
}
}
}
3.4.2.4 O(logn)
/**
* O(logN)
* 空间复杂度
* @param n
*/
private static void space4(int n) {
List<Integer> list = new ArrayList<>();
// 总 logN -> O(logN)
int i = 1;
while (i <= n) { // logN
i = i * 2; // logN
list.add(new Random().nextInt()); // logN
}
}
3.4.2.5 O(nlogn)
/**
* O(nlogN)
* 空间复杂度
* @param n
*/
private static void space5(int n) {
List<Integer> list = new ArrayList<>();
// 总 nlogN -> O(nlogN)
for(int i=0; i<n; i++) {// n
int j = 1; // n
while (j <= i) { // logN
j = j * 2; // logN
list.add(new Random().nextInt()); // logN
}
}
}
3.4.3 空间复杂度小结
空间复杂度较时间复杂度简单很多,常见空间复杂度为 O(1), O(n), O(n²), 像O(logn), O(nlogn)这样的对数阶复杂度基本上用不到。