算法的时间复杂度
算法时间复杂度定义与大O记法
算法的时间复杂度是指算法在执行过程中,随着输入数据规模的增长,算法所需执行时间的增长趋势。它是衡量算法效率的一个重要指标
在进行算法分析时,语句的总执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级
T(n)= O(f(n)),它表示随问题规模n的增大,算法执行时间的增长率和 f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度
像这样用大写O来体现算法时间复杂度的记法,我们称之为大O记法
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法
一些常见的阶:
0(1):常数阶、O(n):线性阶、0(n²):平方阶…下面会再进行详细介绍
推导大O阶的方法
(1) 用常数1取代运行时间中的所有加法常数
(2) 只保留最高阶项
(3) 如果最高阶项存在且其系数不是1,则去除与这个项相乘的系数
常数阶O(1)
举个例子,现在我们要求1~100的和
根据等差数列求和公式,我们可以轻松得到:
int sum=0; //执行一次
sum=(1+100)*100/2; //一次
printf("%d",sum); //一次
可见,这段代码的运行次数为3,但是根据推导大O阶的方法,用1取代3,故仍为O(1)
因为,n的值的改变并不影响执行的次数
执行时间恒定的算法,称为具有O(1)的时间复杂度
线性阶O(n)
拿最简单的循环举例
int num=1;
for(int i=0;i<n;i++){
num++;
}
首先,因为有循环,所以单独语句的执行次数对时间复杂度影响不大,故省略
设循环里的语句要执行a次,要循环n次,那执行次数为an
根据推导大O阶的方法,我们要将a这个常数变为1
故得到其时间复杂度为O(n)
对数阶O(logn)
int num=1;
while(num<n){
num*=2;
}
把循环过程写出来的话,就是:num* 2 * 2 * 2 * 2…
随着循环次数的增加,num就会越来越接近n
也就是说,有多少个2相乘之后大于n
根据2的k次幂=n,得到x=log2(n)
记为O(logn)
平方阶O(n²)
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
}
}
可以看出,在n层循环外面又套了n层循环,即时间复杂度为O(n²)
若外层循环改为m次;即为O(mn)
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
}
}
还有一些其他的循环:
nlogn阶 O(nlogn)
for(int i=0;i<n;i++){
int j=1;
while(j<n){
j*=2;
}
}
立方阶O(n³)
指数阶O(2ⁿ)
常用的时间复杂度所耗费的时间:
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n3)<O(2")<O(n!)<O(n")
最坏情况与平均情况
最坏运算时间是一种保障,一般情况下,我们提到的运算时间都是最坏运算时间
平均运行时间是指在多次运行一个程序或算法的情况下,总运行时间除 以运行次数得到的平均值
平均运算时间是所有情况中最有意义的,因为它是期望的运行时间
平均时间复杂度:计算所有情况的平均值
最坏时间复杂度:计算在最坏情况下的时间复杂度
一般没有特殊说明的情况下,都是指最坏时间复杂度
ps:一道c的编程题
标题 自然数分解
类别
流程控制
时间限制
2S
内存限制
1000Kb
问题描述
任何一个自然数 m 的立方均可写成 m 个连续奇数之和。例如:
1³=1
2³=3+5
3³=7+9+11
4³=13+15+17+19
编程实现:输入一自然数 n,求组成 n的三次方 的 n 个连续奇数。
输入说明
一个正整数 n,0<n<30。
输出说明
输出 n 个连续奇数,数据之间用空格隔开,并换行
输入样例
4
输出样例
13 15 17 19
下面是我的方法:
复杂难懂
#include <stdio.h>
#include<math.h>
int plus(int n, int k);
int main() {
int num, sum = 0,k=1;
scanf_s("%d", &num);
while (1) {
sum = plus(num, k);
if (sum == (int)pow((double)num, 3)) {
break;
}
k += 2;
}
int t= k+num * 2;
for (; k <t; k += 2) {
printf("%d ", k);
}
printf("\n");
return 0;
}
int plus(int num, int k) {
int sum = 0;
int t= k+num * 2;
for (;k < t;k += 2) {
sum += k;
}
return sum;
}
以及一位学长的方法:
清晰易懂
#include <stdio.h>
int main() {
int num;
scanf_s("%d", &num);
for (int i = num * num - num + 1;i <= num * num + num - 1;i += 2) {
printf("%d ", i);
}
return 0;
}
可以看出有很大的区别
因为我在分析题意的时候,并没有观察其中的特殊规律,忽略了最简单的方法
我的思路:
先创建一个函数plus,将n个奇数加和,返回一个和sum
然后用while循环,调用plus,接着判断sum是否等于num,如果等,则输出,若不等,则k=k+2,将加和起始值k+2变到下一个奇数,重新plus,直到找到正确的第一个奇数,使sum=num
显然,当num比较大时,我这个方法的时间复杂度要远远大于另一种方法,而且在编写并修改程序的过程中,也十分困难
而这就相当于很经典的问题:
明明从1加到100可以通过等差数列求和公式一步求出,时间复杂度为 O(1)
却偏偏要用for循环:
int sum=0;
for(int i=1;i<=100;i++){
sum+=i;
}
时间复杂度为O(n)
所以,这也提醒了我,在编写程序的时候还是要多思考,不能拿到题上来就写,要学会反思自我
最后
正因为岁月漫长,所以才有了无数热爱,和期待的理由,
正因为岁月漫长,所以等待和追求也更有了意义
愿你我都能耐得住寂寞,实现更好的自我~