时空复杂度
时间复杂度
基本认识
看程序时,我们只需要了解时间复杂度可以大致地通过一个算法的运算的次数来描述程序运行的效率,常常用大写字母O来表示。在表示时间复杂度的时候,只保留数量级最大的一项,并忽略系数。
举个例子
对于给定的常数N,若某一个算法计算的次数是 5 N 3 + 6 N 2 + 7 N + 8 5N^3 + 6N^2 + 7N + 8 5N3+6N2+7N+8,那么它的时间复杂度就是O( N 3 N^3 N3)。
空间复杂度
基本认识
顾名思义,用来衡量内存的占有量。吃了计算复杂度,我们有时候也可以直接算出来运行程序需要占用多少内存。
单位换算
1MB = 1024KB
1KB = 1024B
1B(byte,字节) = 8b(bit,比特)
常见数据类型
char/bool/short:1B
int/float:4B
long long/double:8B
另外,也可以通过 sizeof() 直接进行运行查看。
一些例子
常数阶
利用顺序结构的时间复杂度,以下面这个段程序为例:
int sum = 0, n = 100; /*执行一次*/
sum = (1 + n) * n /2; /*执行一次*/
printf("%d, sum); /*执行一次*/
像这样的,我们需要叫它O(3)嘛?不必要,我们通常称之为O(1)。对于计算机而言,1s大概(起码,感觉上,emmmm)能计算
1
0
8
10^8
108 到
1
0
9
10^9
109次。
经过查阅,总结一下:比如说1G主频的CPU,1GHZ是指一秒钟内CPU脉冲震荡次数,每次震荡可视为一次指令计算执行,CPU的时钟周期就是1/1G,所以1G基本上可以简单说是CPU的运算次数也就是1 * 1000 * 1000 * 1000,也就是10亿次,但实际上CPU的运算次数还跟CPU所使用的指令集、CPU流水线长度、寄存器数量及使用方式等有关,而那些大型计算机所公布的运算次数多是浮点运算。(虽然我也不知道说了什么)
线性阶
线性阶的循环结构会有些复杂,常常需要确定某个特定语句或特定语句集的运行的次数。因此,要分析好循环结构。
以下面这个段程序为例:
int i;
for(i = 0; i < n; i++){
/* 此处为O(1)的程序段 */
}
由上可知,该代码需要运行n次,复杂度为O(n)。
对数阶
代码如下:
int count = 1;
while(count < n){
count *= 2;
}
上述代码的时间复杂度为O(
log
2
n
\log_2n
log2n)。
循环嵌套
双重循环,对比以下两个例子:
int i, j;
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
/* 此处为O(1)的程序段 */
}
}
int i, j;
for(i = 0; i < n; i++){
for(j = i; j < n; j++){
/* 此处为O(1)的程序段 */
}
}
对于第一个例子而言,显然时间复杂度是O(
n
2
n^2
n2),对于第二个而言,程序运行了
n
2
−
n
2
\frac{n^2-n}{2}
2n2−n次。但是我们依然说它的时间复杂度为O(
n
2
n^2
n2)。
立方阶
int i, j;
for(i = 1; i < n; i++){
for(j = 1; j < n; j++){
for(j = 1;j < n; j++){
/* 此处为O(1)的程序段 */
}
}
}
这里循环了
1
2
+
2
2
+
3
3
+
.
.
.
+
n
2
=
n
(
n
+
1
)
(
2
n
+
1
)
/
6
1^2 +2^2+3^3+...+n^2 =n(n+1)(2n+1)/6
12+22+33+...+n2=n(n+1)(2n+1)/6 次。时间复杂度为O(
n
3
n^3
n3)。
练练手吧!
计算
(
∑
i
=
1
n
i
!
)
%
(
1
e
9
+
7
)
(\sum_{i=1}^{n} i!)\%(1e9+7)
(∑i=1ni!)%(1e9+7) 的结果。(N<=1000000)
N是
1
0
6
10^6
106,我们有如下代码:
for(int i = 1; i <= n; i++){
int t = 1;
for(int j = 1; j <= i; j++){
t *= j;
}
ans += t;
}
但是显然,这样一定是无法通过要求。对于计算机而言,它无法在1s内完成,若估计计算机1s运算次数为
1
0
8
10^8
108 ,该运算数量级为
1
0
12
10^{12}
1012 ,那么,就需要1000s来执行这个计算。太浪费时间了。因此,我们要突破!
上述代码是将每个 n!都重新计算了一遍,但是对于每一个n!,我们都可以先通过 (n - 1)! * n 来计算得出。有如下代码:
int t = 1;
for(int i = 1; i <= n; i++){
t *= i;
ans += t;
}
这样我们就实现了时间复杂度从 O(
n
2
n^2
n2) 到 O(n) 的转变。
NOIP2011 铺地毯
数据范围:
对于 30% 的数据:有 n ≤ 2。
对于 50% 的数据:0 ≤ a, b, g, k≤ 1000。
对于 100% 的数据:有 0 ≤ n ≤ 100000 ,0 ≤ a, b, g, k ≤ 100000。
读完题目之后,我们有了第一个想法:直接卡一个二维数组进行模拟填充:
for(int i = 1; i <= n; i++0){
cin >> a >> b >> g >> k;
for(int dx = 0; dx <= g; dx++){
for(int dy = 0; dy <= k; dy++)
map[a + dx][b + dy] = i;
}
}
cin >> x >> y;
cout << map[x][y];
空间复杂度为O(
n
2
n^2
n2) ,时间复杂度为O(
n
3
n^3
n3) 。
第二个想法:我们记录下来每个地毯的信息,对于(x,y)我们在n个地毯中倒序查找。
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int a, b, lx, ly;
}node[100010];
int main(){
int n, x, y, ans, f = 0;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d%d%d%d",&node[i].a,&node[i].b,&node[i].lx,&node[i].ly);
scanf("%d%d",&x,&y);
for(int i = n; i; i--)
if(x >= node[i].a && x <= (node[i].a + node[i].lx) &&y >= node[i].b && y <= (node[i].b + node[i].ly))
return printf("%d",i),0;
puts("-1");
return 0;
}
优化思路
时间换空间,空间换时间。注意计算机每秒运行的上限,单位换算等等问题。
最后鸣谢极值学院。同时,希望这篇文章对你有所帮助!感谢观看!