数据结构01
算法的时间复杂度
时间复杂度的概念
代码执行前预估算法时间开销T(n)与问题规模n的关系(T表示时间’time’),T = o(n)
栗子1
分析以下代码的空间复杂度
int main (){
loveYou(3000);
}
void loveYou(int n){
int i = 1;//编号1
while(i<=n){//编号2
i++;//编号3
printf("I Love You More Than %d/n Times",&i);//编号4
}
printf("I Love You More Than %d/n Times",&n);//编号5
}
部分输出结果
I Love You 2996 Times
I Love You 2997 Times
I Love You 2998 Times
I Love You 2999 Times
I Love You 3000 Times
I Love You 3001 Times
I Love You More Than 3000 Times
下面我们来分析栗子1中的五条语句各自的语句频度
1号–1次
2号–3001次
3号4号–3000次
5号–1次
得出结论T(3000) = 1 + 3001 + 2*3000 + 1
时间开销与问题规模n的关系:
T(n) = 3n+3
问题1:是否可以忽略表达式的某些部分
假设有如下时间开销与问题规模n的关系:
T1(n) =o(3n)+o(3)
T2(n) =o(3n²)+o(3n)+o(1000)
T3(n) =o(3n³)+o(n²)+o(1000)
根据我们所学过的高数知识,得出结论:
可以只考虑阶数高的部分,其前面的常数可以省略
式子中的常数项也可以忽略
以上表达式化简为:
T1(n) =o(n)
T2(n) =o(n²)
T3(n) =o(n³)
常见公式的阶数排名
口诀:常对幂指阶
o(1)<o(log2n)<o(n)<o(nlog2n)<o(n²)<o(n³)<o(2n)<o(n!)<o(nn)
问题2:如果有几千行代码,是否需要一行行数
结论一:顺序执行的代码只会影响常数项,可以忽略。
结论二:只需要挑选一个循环中的基本操作分析他的执行次数与n的关系即可。
栗子2(嵌套循环)
下面看栗子的代码
void loveYou(int n){
int i = 1;
while(i<=n){
i++;
printf("I Love You %d Times\n",i);
int j;
for(j=1;j<=i;j++){
printf("I am Icon Man\n");
}
}
printf("I love you more than %d times\n",n);
}
该嵌套循环的特点:
1.外层循环执行n次
2.内层循环共执行n²次
所以按照上文所述时间复杂度为:
T(n)=O(n)+O(n²)=O(n²)
结论3:多层嵌套循环,只需关注最深层循环了几次
练习1
分析下列代码的时间复杂度
void loveYou(int n){
int i = 1;
while(i<=n){
i=i*2;
printf("I Love You %d Times\n",i);
}
printf("I love you more than %d times\n",n);
}
有循环条件得,循环结束条件为 2x>n
所以循环次数为 log2n +1
T(n) = O(x)= O( log2n )
练习2
分析下列代码的时间复杂度
void loveYou(int flag[],int n){
printf("I am Icon Man\n");
int i;
for(i=0;i<n;i++){
if(flag[i]==n){
printf("I love you");
break;
}
}
}
//flag数组中乱序存放了1~n这些数字
根据题目条件。输入的flag数组不同,复杂度也不同
最好的情况:n在第一位
T(n)=O(1)
最坏的情况:n在最后一位
T(n)=O(n)
算法的空间复杂度
程序运行时的内存需求
在这段代码中,无论问题规模怎么变,算法运行所需的空间都是固定的常量(只需要n和i两个变量),所以算法的空间复杂度为:
S(n)=O(1)
S表示’Space’
栗子1
void test(int n){
int flag[n];
int i;
//省略后面的代码
}
设一个int变量占四个字节
所需内存空间会随问题的规模n变化,为4+4n+4 = 4n+8
和时间复杂度省略的项类似
本栗子空间复杂度为
S(n)=O(n)
栗子2
void test(int n){
int flag[n][n];
int other[n];
int i;
}
本栗子的空间复杂度为:
T(n)=O(n²)+O(n)+O(1)=O(n²)
T(n)=O(n²)+O(n)+O(1)=O(n²)
这个式子同样适用于空间复杂度!
函数递归调用时带来的内存开销
void loveyou(int n){
int a,b,c;
if(n>1){
loveyou(n-1);
}
printf("I love you");
}
改段代码定义了4个变量nabc,并不停递归调用,并且每次调用时都会开辟四个变量的空间,16个字节。
所以问题规模为n时,递归n’次,需要16n的空间
S(n)=O(16n)=O(n)
结论:空间复杂度 = 递归调用的深度(大多数情况)
栗子3
void loveyou(int n){
int flag[n];
if(n>1){
loveyou(n-1);
}
}
在上述代码中,每次递归都会生成一个长度为n的数组,所以所需空间为:1+3+3+……[n(1+n)]/2
1
2
\frac{1}{2}
21n²+
1
2
\frac{1}{2}
21n
T(n)=O(
1
2
\frac{1}{2}
21n²+
1
2
\frac{1}{2}
21n)=O(n²)