这篇文章作为一个小插曲,把我们之前漏讲的时间复杂度,空间复杂度讲一下
为什么要引用时间复杂度,空间复杂度呢?
一个好的算法,在正确性,健壮性,可读性这几个方面都满足的条件下,我们要考虑的就是算法的效率,我们可以通过以下两个方面考虑
1.时间效率:指的是算法所耗费的时间
2.空间效率:指的是算法执行过程中所耗费的存储空间
我们要注意,空间效率和时间效率有时候是矛盾的。有些时候为了时间效率不得不增加所用空间,为了空间效率不得不增加说用时间
那我们该如何测量他所用的时间与空间呢?
这里又两个方法
1,事后统计
也就是我们把代码跑一遍,看看他所花的时间与空间
缺点就是花费较多的时间和精力;实验结果依赖计算机的软硬件等因素,掩盖了算法本身的优劣
2,事前分析
对算法所消耗的资源的一种估算方法:算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作所需的时间与算法中进行简单操作的次数乘积。
简单来说就是:算法运行时间 = 一个简单的操作所需的时间X简单操作次数。有多少个简单操作就把他们相加,举个例子来说,我printf了两个句子,进行了5次其他循环。那么我花费的时间就是5+2= 7次的单位时间这样子
我们主要讲 事前分析法
我们举个例子来说,计算求和的算法
第一种算法:
int i, sum = 0, n = 100; //执行了1次
for(i = 1; i<= n; i++) //执行了n+1次,因为最后跳出循环又算一次
{
sum = sum + 1; //执行了n次
}
printf("%d", sum); //执行了1次
第二章算法;
int sum = 0, n = 100; //执行1次
sum = (1+n)*n/2; //执行1次
prinf("%d", sum); //执行1次
我们可以看见第一种算法执行了1+(n+1)+n+1 = 2n+3次,而第二种算法我们只执行了3次
所以在这两种算法里面,我们可以发现第二种的算法更好。
我们再来看另一种算法
int i,j x = 0,sum = 0,n = 100; //执行1次
for(i = 1;i<=n; i++)
{
for(j = 1;j<=n;j++)
{
x++;
sum = sum + x; //执行n*n次
}
}
printf("%d",sum); //执行1次
我们可以看到,如果i从1到100,j也要循环100遍,而x++和 sum = sum + x就要进行100的二次方
所以我们可以看到这个算法的执行时间随着n的增加也将远远多于前面两个
算法时间复杂度
算法时间复杂度的定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模的函数,进而分析T(n)随着n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作T(n) = O(f(n))。他表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度。其中f(n)是问题规模n的某个函数。
看着很复杂,其实就是告诉我们时间复杂度这个东西跟函数很像,会因为n的变化而变化,然后我们把我们上面的2n+3写成O(2n+3)的形式就是时间复杂度我们称之为大O记法
一般情况,我们随着n的增大,T(n)增长最慢的算法称为最优算法
我们对于这三个求和算法的时间复杂度取了个非官方的名字
O(1) O(n) O(n^2)
常数阶 线性阶 平方阶
推导大O阶的方法
①常数1取代运行时间中的加法常数
②再修改后的运行次数函数中,只保留最高阶项
③如果最高阶项存在且其系数不是1,则去除与这个项相乘的系数
得到的结果就是大O阶
什么意思呢?
就是
(1)常数阶
int sum = 0, n = 100; //执行1次
sum = (1+n)*n/2; //执行1次
prinf("%d", sum); //执行1次
我们利用公式,①:对于下面的算法f(n) = 3对吧,那我们把他的大O阶写成 O(1) 而不是 O(3)这个新手常犯这个错我
②:只保留最高阶项,我们发现他并没有最高阶项,所以O(1)
同理他就算执行10次,100次……他的时间复杂度也是O(1)
(2)我们再来看线性阶
int i, sum = 0, n = 100;
for(i = 1; i<= n; i++)
{
sum = sum + 1;
}
对于这个代码,他的循环的时间复杂度为O(n),因为循环体里面需要执行n次
(3)平方阶
int i,j
for(i = 1;i<=n; i++)
{
for(j = 1;j<n;j++)
{
x++;
sum = sum + x;
}
}
这段代码里面是一个循环嵌套,我们刚才分析过了,时间复杂度为O(n)。而对于外层的循环,不过内部这个时间复杂度为O(n)的语句,在循环n次。所以这段代码的时间复杂度为O(n^2)
如果外循环的循环次数改为了m 那么时间复杂度就变为了O(m x n)
所以我们可以总结出 循环的时间复杂度 = 循环体的复杂度 X 该循环运行的次数
常见的时间复杂度
执行次数函数 | 阶 | 非正式术语 |
O(1) | 常数阶 | |
O(n) | 线性阶 | |
O() | 平方阶 | |
O() | 对数阶 | |
O() | 阶 | |
O() | 立方阶 | |
O() | 指数阶 |
常用的时间复杂度所耗费的时间从小到大依次是
O(1) < O() < O(n) < O() < O() < O() < O() < O(n!) < O()
算法空间复杂度
我们再写代码的时候,完全可以用空间来换取时间。
比如说,要计算某某年是不是闰年,那是不是每次给一个年份,都要通过计算得到是否是闰年的结果。
还有另一个办法就是,建立一个2050个元素的数组,把所有年份按照下标对应,如果是闰年,那么就变成1,不然就是0;这样我们就可以判断某一年是不是闰年变换成了找这个数组里面的某一项的值是多少了。
我们的运算是简化了,但是我们花费了更多的存储空间了
算法的空间复杂度通过计算算法所需的存储空间实现,记作:S(n) = O(f(n))
我们主要讲的是时间复杂度,空间复杂度我们了解一下就可以了。