![](https://img-blog.csdnimg.cn/20190426122819232.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fSmluaGFpX1N1bg==,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190426133615383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fSmluaGFpX1N1bg==,size_16,color_FFFFFF,t_70)
什么叫时间复杂度,并且告诉大家我们一般求时间最坏复杂度,下边我们该怎么求时间复杂度呢,没有必要去求他真实的计算次数,
所以我们最后把这个问题简化了,怎么来简化?
1. 找出算法中的基本语句,基本语句是哪个语句呢,一般来说是执行次数最多的那条语句,其他的都可以省略,找算法中的基本语句,
算法中执行次数最多的那条语句就是基本语句
2. 然后我们只要计算基本语句的执行次数的级别就可以了
3. 最后用大O来表示
下边我们都举几个列子都挺简单的
时间复杂度举例
(1)----------------------------------------------------
一个简单语句的时间复杂度为O(1).
int count = 1;
他的时间复杂度是多少,我们不是应该这么求吗,T(n)=1,因为一条语句他的执行次数就是一次,这不叫时间复杂度,这叫时间频度,
时间复杂度是怎么来的,T(n)=O(1),第一步找出基本语句,他的执行次数就一次,再用大欧来表示,他没有低次幂,只有常数项,
这用1来表示就可以了
(2)-----------------------------------------------------
100个简单语句的事件复杂度也为O(1).
int count = 0;
int count = 0;
int count = 0;
........
int count = 0;
int不能重复的定义,一共一百条语句,请你告诉我,他的执行次数,他的时间复杂度是多少,找出基本语句,每个语句执行一次,
那就是T(n)=1,T(n)=O(1),还是1,我不找基本语句,只是100条语句,他的执行次数是100,T(n)=100,不找基本语句,所有的语句
执行次数是100,T(n)=100,但是时间复杂度来求的话100都要去掉,去掉之后就变成1了,T(n)=O(1),有人说这里是100条,但是
这里不管是1万条还是10万条,这里最后还是1,因为10万条还是个有限的数,他也是个常数,这一点大家明确
(3)----------------------------------------------------
一个循环的时间复杂度为O(n)
int n = 8,count = 0;
for(int i=1;i<=n;i++){
count++;
}
一个循环的事件复杂度,n等于8,count等于0,for循环循环几次,count加加,注意啊,你不要看这个8,因为这次给个8,下次给的
可能就不是8了,也可能是给个无穷大的数,他就是个变量,请问他的时间复杂度是多少,注意了,要找基本语句,基本语句是不是
int n = 8,count = 0这条语句,基本语句执行次数最多的语句,最多的是count++,有些人会说最多的是i<=n,不计较这个,计较
这个有意义吗,是没有意义,我们找循环体就可以了,那你想一下,count++这个他应该执行多少次,我们要写T(n),告诉我他执行
多少次,那是不是和n有关,那就执行n次吧,T(n)=n,T(n)=n+1,和T(n)=n-1次这个没关系,我们就n次,这个n就可以了,不影响结果,
然后怎么办,如果T(n)用时间复杂度来表示,写一个大欧还是n,T(n)=O(n),还是n就可以了,n相当于一个变量,我想问大家一下,
如果我这里写的是10n+100,for(int i=1;i<=10n+100;i++),请告诉我,他的时间度是多少,那他的T(n)=10n+100,这个是时间频度,
但是我们是看复杂度,看他规模就可以了,怎么看呢,常数项100去掉,10n的系数10也去了,是不是还是n,多做几个就明白了,是O(n)
(4)------------------------------------------------------
时间复杂度为O(log2 n)的循环语句
int n = 5,count = 0;
for(int i=1;i<=n;i*=2){
count++;
}
有人说这个语句和上面的语句一样啊,不一样,哪儿不一样?关键是这儿i*=2,这儿不一样,i乘等于2,这个时间度是多少,它执行
多少次,你知道i*=2是什么含义吗,第一次它是1,然后他变成几了,它变成2,然后就变成4,然后变成8,然后16,然后32,有人说这个
变化也不快,这还叫不快吗,好像大家都还听说过一个故事,什么故事啊,什么印度一个国王,向国际象棋发明者老智者要赏赐他了
我赏赐你粮食吧,一个国际象棋的棋盘,一共64个格,第一个格放一个,第二个放2个,第三个放4个,第四个放8个,当时国王发现
整个国家的粮食放进来也放不够,为什么啊,只有64个格,他就放不下了,别说64个格,想一下30个格就是多少,1是2的0次方,
2是2的一次方,4是2的2次方,30个格那不就是2的30次方,2的30次方是多少啊,这对我们学计算机的来说应该很清楚啊,
不就是2的10次方乘以2的10次方,再乘以2的10次方,那不就是1024*1024*1024,我们简单一下=1000*1000*1000,就是10的9次方,
就是10亿,想说明一个什么意思,也就是说,对于我们上面这个来说,我们最后写的这个数,如果我们呢这个数是n,他等于10亿的话,
这个i++,每次加的话,如果这个数是10亿的话,它这个循环得循环多少次,i++一直变到10亿,10亿次,但是如果用(int i=1;i<=n;i*=2)
这种方式的话几次就可以了,如果这个n是10亿的话,30次左右就可以了,有没有想到我这个30次和10亿次的差别,还是很大的,这种算法
告诉我,我知道10亿了,我知道这个n了,我怎么知道n的多少次方就等于n了,log2 n,一个叫做指数,一个叫做对数,反过来的,
有的人说我有点糊涂了,我们在这里不做过多的纠结,在这种情况下,我们要掌握两点,第一点log2 n的话,他的效率是特别高,
你就想这个30次跟10亿次的差别就可以了,想那个差别就可以了,如果你以后设计了一种算法,从O(n)到O(log2 n),那你太棒了,
如果你再实现一种算法复杂度是O(1),那你就更厉害,这个大家明确了,这个log2 n就指数的反过来,2的30次方就是10亿,
这里特别标记一下,好好的想一下这一块
(5)--------------------------------------------------------------
时间复杂度为O(n²)的二重循环.
int n = 8,count=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
count++;
}
}
大家一定要记住,有人说这个有点难,难你不学肯定学不会,不可能眯一会睁开眼就回来,我们学就是学别人不会的,就学这些呗,
学一些难的内容,别人学不会我们会的,第三点这个真的很难吗,我们今天就可以掌握就变成自己的了,这差距有时候就是这么来产生
的,告诉我这个时间复杂度是多少,先找基本语句,基本语句是count++呗,这是基本语句,告诉我j<n是从1开始的,i小于n也是1开始的,
我们不说别的,里循环,里循环小循环多少次,从1到n次,这个n次有几个n次,这外循环又是n次,那不就是n乘以n吗,所以他的复杂度是
O(n²),如果我这里要是10n呢,for(int j=1;j<=10n;j++),我这里是100n呢,for(int i=1;i<=100n;i++),级别是不是还是n的
平方时间复杂度吗,T(n)=100n*10n最后等于多少,最后等于1000n*10n,那我们要用时间复杂度来说,就等于T(n)=O(n*n),一步一步来,
(6)--------------------------------------------------------------------
时间复杂度为O(nlog2 n)的二重循环
int n = 8,count = 0;
for(int i=1;i<=n;i*=2){
for(int j=1;j<=n;j++){
count++;
}
}
总觉得很相似,j小于等于n,i小于等于n一样啊? i*=2这儿不一样,是不是这儿不一样,双重循环,这里多少,在这个里循环里面多少次,
里循环里面n次,他有多少个n次,他有多少个小循环,log2 n呗, 所以他整个的时间复杂度就是n*log2 n,写他就可以,
O(log2 n*n),这个大家明确了
(7)-----------------------------------------------------------------------------
时间复杂度为O(n²)的二重循环.
int n =8,count = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
count++;
}
}
这个是多少,仔细来看,n等于8,这个不搭理他,我们要的是找循环体,i小于等于n,i加加,j加加,j小于等于i,这个好奇怪,
他的级别会是多少,这个要稍微繁琐一下,你一定要想一下,这个for(int i=1;i<=n;i++)循环几次,是不是n次,
那里面的这个循环for(int j=1;j<=i;j++)循环了几次,那是不确定的,因为i等于1就循环1次,i等于2,,从1到2,i等于3,
从1到3,所以你可以这么算一下,怎么算啊,当i等于1的话,这条语句执行1次,因为你看,从1到1嘛,j小于等于i嘛,
当i要是等于2的话,两次,i等于3的时候,3次,再加上4等于1+2+3+4,当i要是等于n的时候,j从1到n,是不是就是n次,
告诉我1+2+3+4+....+n=?,这是不是就是他的完整的执行次数,稍微复杂一下,这不就是刚才效率最高的算法吗,
1+2+3+...+n=(1+n)*n/2,这是多少啊,这是时间复杂度吗,这是时间频度,我们以更加简单的形式来写一下,
0.5n²+0.5n,这是时间频度,T(n)=0.5n²+0.5n,我求的是T(n)=O(n²),最后就剩n的平方
我们现在通过这么几个例子,让大家对时间复杂度入门,第一个知道有哪些级别,第二个他大概是怎么来算的,
还要明确这个概念,后面我们讲什么线性表啦,讲树啦,讲查询排序,往往都是去说他的时间复杂度,因为这是
衡量他优劣的最直接的指标,你要明确
常用的时间复杂度有多少? 我们列了一个表
1. 常数阶 O(1)
2. 对数阶 O(log2 n):你见到log2 n你就要想那个国际象棋,要米粒
3. 线数阶 O(n): 这个就和上面一个差别就很大了
4. 线性对数阶 O(n*log2 n)
5. 平方阶 O(n²): n的平方就是n乘以n, n大于log2 n
6. 立方阶 O(n³): 是不是复杂度效率更低
........
指数阶 :效率更低,2的n次方
阶乘阶: n的阶乘 n!,谁能写出这样一个算法我佩服你,这个级别效率太低了,
一般来说常用的复杂度,越往下时间复杂度越高,执行效率越低,我们以后编写程序,尽量控制一下规模,
你如果要来个三重的循环,那估计就是这个立方阶 O(n³),我们这里又一个表
当n等于8,等于10,等于1000的时候,什么意思,如果你的级别是O(n)的话,如果n等于1000,你就执行1000次,
如果是O(log2 n),就是2的10次方,9次10次就可以了,9次和1000次的差别,如果O(1)一次就可以了,
如果你的你别是O(n),执行此时是1亿次,但是如果是log2 n的话是8次就够了,8次和1亿次是什么差别
讲到这里我们就把时间的复杂度给大家说完了
再来看一下算法的空间复杂度,空间复杂度没有什么过多要说的吧,更多的是来计算时间复杂度,空间复杂度用S(n)来表示
为什么要用S来表示,S是什么意思,是Space空间,n是问题的规模,S(n)=O(g(n)),O是最坏的情况,他也代表复杂度,
这里面还有一个函数,我们一起分析一下
int fun(int n){
int i,j,k,s;
s=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=j;j++){
s++;
}
}
}
return s;
}
告诉我这个空间复杂度是多少,n是多少,那不确定,n可能是个无穷大的数,他不确定,i,j,k,s,s等于0,开始循环了,就是这4个变量了,
我们现在不是求时间复杂度,我们求的是空间复杂度,你觉得这段代码要执行,内存里面要占多少空间,第一个变量是i,第二个变量是j,
第三个变量是k,第四个变量是s,i是0,j是0,k是0,s是0,i要变成1,j要变成1,2,没有分配额外的空间,这个i要变,k也要变,s也要变,
只需要4个空间就够了,因为你变来变去,并没有分配额外的空间,我有4个空间就够了,并没有占别的空间,S(n)=4,4个空间就够了,
我要空间复杂度呢,S(n)=O(1),空间复杂度是1,因为n是100万,他也是4个空间,什么情况下空间复杂度也会很高,n越大分的空间越多,
比如说递归
由于算法中临时变量的个数与问题规模n无关,所以空间复杂度为S(n)=O(1),
空间复杂度分析2:
void fun(int a[],int n,int k){
// 数组a共有n个元素
int i;
if(k==n-1){
for(int i=0;i<n;i++){
// 执行n次
printf("%d\n",a[i]);
}
}else{
for(int i=k;i<n;i++){
// 执行n-k次
a[i] = a[i] + i*i;
fun(a,n,k+1);
}
}
}
fun(a,n,k+1)这个叫什么啊,这个叫递归,自己调自己叫递归,这个算法大家已经学过了,他要分多少空间,如果我们只是调用函数一次,
这个函数如果调一次,不递归的话,需要3个,一个n,一个k,一个i,再加一个数组a,数量是有限制的,级别还是1,如果还往下这么调呢,
它会调多少次,会递归往下调多少级别,如果他是n个级别的话,如果递归n次的话就是1*n,每次调用这个函数,比如栈的空间特别少,
但是调n次,哪怕他调用n/2次呢,那这个n也是无穷数,所以我们求这个级别的话怎么求,还是O(n)的,每调函数一次,花的空间很少,
此属于递归算法,每次调用本身都需要分配空间,空间很少,但是你经不住他调的次数多,每递归一次都要分配一次空间,所以他的空间
复杂度是O(n),那大家都记住一个结论,请问递归有什么缺点,效率低下,占用空间多,那我为什么还要用递归,代码简单,思路简单,
特别的方便,航天飞机里面一般不用递归,为什么,实时系统里面不用递归,实时反应,占的空间多,效率还低,可能计算结果要花很多
的时间,但是他代码简单,性能要求不是特别高的情况下,可以用递归来实现,在我们后面的数据结构里面,使用最多的一个内容就是
递归,很多地方树的遍历,图的遍历,折半查找,还有很多的查询排序,都用到递归,下来对递归进行了大量的练习,因为递归是我们面试的
时候用的比较多的内容,关于空间复杂度我们就简单的说到这里
记住:
1. 空间复杂度比时间复杂度分析要少
2. 用递归算法,代码比较简单,算法本身占的空间少,但是运行的时候要分配比较多的空间,写的代码少,但是运行是需要多次调用,
占很多的空间,如果采用非递归算法呢,代码可能会很长,代码会是很复杂的代码,代码多了,存代码就占的多了,运行的时候,
就像我们刚刚画的图一样,运行的时候就占的比较少