这篇主要讲解时间复杂度、空间复杂度概念。
复杂度分析
数据结构和算法解决的是“快”和“省”的问题。即如何让代码运行的更快,如何让代码更省存储空间。因此代码的执行效率是一个非常重要的考量指标,那如何来衡量代码的执行效率呢?我们可以用时间复杂度、空间复杂度来对代码的执行效率、性能进行评估,也就是算法的复杂度分析。
算法的复杂度分析主要包含两个方面:
时间复杂度分析
空间复杂度分析
为什么要进行复杂度分析?
1.和性能测试相比,复杂度分析不依赖执行环境、成本低、效率高、易操作、指导性强的特点。
2.掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本。
时间复杂度表示法
算法的执行效率,就是算法代码执行的时间,下面演示在不直接运行代码的前提下粗略的计算执行时间。
int sum(int n){
int sum = 0; // 执行一遍
for(int i = 1; i <=n; ++i){ // 执行n遍
sum = sum + i; // 执行n遍
}
return sum;
}
假设每行代码执行时间都一样为: timer, 此代码的执行时间为:(2n+1)*timer。
int sum(int n){
int sum = 0; //执行一遍
for(int i = 1; i <= n; ++i){ //执行n遍
for(int j = 1; j <= n; ++j) { //执行n*n遍
sum = sum + i * j;//执行n*n遍
}
}
}
同理: 此代码的执行时间为:(2n*n + n + 1) * timer
因此: 代码的执行时间T(n)与每行代码的执行次数n 成正比,可以把这本规律总结成一个公式。
T(n) = O(f(n))
解释一下:
T(n) 表示代码的执行时间,n表示数据规模的大小,f(n)表示了代码执行次数的总和,它是一个公式因此用f(n)表示,O表示了代码执行时间与f(n)成正比。
因此第一个例子中的T(n) = O(2n + 1),第二个例子中的T(n) = O(2n*n + n + 1),这就是大O时间复杂度表示法。
大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫渐进时间复杂度,简称时间复杂度。
当n很大时,公式中的低阶,常量,系数三部分并不左右其增长趋势,因此可以忽略,我们只需要记录一个最大的量级就可以了,因此如果用大O表示刚刚的时间复杂度
可以记录为: T(n) = O(n), T(n) = O(n*n)
时间复杂度分析方法:
- 代码循环次数最多原则:
分析一个算法或者一个代码的世界复杂度时,只需关注循环执行次数最多的那一段代码即可。 - 加法原则:
代码中有两段代码,复杂度分别为O(n) 和O(nn) 其结果本应该是: T(n) = O(n) + O(nn);我们取其中最大的量级,因此整段代码的复杂度为O(n*n).
也就是说:总的时间复杂度就等于量级最大的那段代码的世界复杂度。 - 乘法原则:
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积,
常见的时间复杂度
1. O(1)
O(1)并不是指代码只有一行,它是一种常量级复杂度的表示方法,比如说有一段代码如下:
public void test01(){
int i = 0;
int j = 1;
return i + j;
}
代码只有三行,它的复杂度也是O(1),而不是O(3)
再看如下代码:
public void test02(){
int i = 0;
int sum = 0;
for(; i < 100; i++){
sum = sum + i;
}
System.out.printLn(sum);
}
整个代码中因为循环次数是固定的就是100次,这样的代码复杂度我们认为也是O(1)
2. O(n)
public void test03(int n){
int i = 0;
int sum = 0;
for(; i < n; i++){
sum = sum + i;
}
System.out.printLn(sum);
}
3. O(logn), O(nlogn)
对数阶的复杂度非常的常见,但同时也是很难分析的一种复杂度。
//复杂度为: O(logn)
public void test04(int n){
int i = 1;
while(i <= n){
i = i * 2;
}
}
//复杂度为: O(nlogn)
public void test06(int n){
int i = 0;
for(; i <= n; i++) {
test04(n);
}
}
空间复杂度
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系,类似的,空间复杂度全称是渐进空间复杂度,表示算法占用的存储空间与数据规模之间的增长关系。
用一段代码说明:
void print(int n){
int i = 0;
int[] a = new int[n];
for(i; i < n; ++i){
a[i] = i * i;
}
for(i = n -1; i >= 0; --i){
System.out.println(a[i]);
}
}
代码第二行,申请一个空间存储变量i,但是是常量阶的,跟数据规模n没有关系,第三行申请了一个大小为n的int数组,
此外后面的代码几乎没有占用更多的空间,因此整段代码的空间复杂度就是O(n)。
我们常见的空间复杂度就是O(1),O(n), O(n*n),其他对数阶的复杂度几乎用不到,因此空间复杂度比时间复杂度分析要简单的多,因此掌握这些足够。