1.算法效率
递归代码———斐波那契数列的代码量十分简洁,所有最优解? 实际上使用递归是错误的,当使用递归计算第40位斐波那契数列会WA,究其原因是进行大量重复的计算。那该如和衡量算法的优劣呢?
#include<iostream>
using namespace std;
int fid(int n){
if(n>2){
return fid(n-1)+fid(n-2);
}
else return 1;
}
int main(){
int n=0;
cin>>n;
int ret=fid(n);
cout<<"第"<<n<<"个斐波那契数列是"<<ret;
return 0;
}
算法的复杂度
- 算法在编写程序时会,消耗一定的时间和空间资源。因此,衡量一个算法的优劣,得综合考虑时间和空间的复杂度。
- 时间复杂度主要衡量一个算法的运行快慢,空间复杂度主要衡量进行算法运行所需的额外空间。
- 相对于时间和空间,因为计算机的高速发展,计算机的存储达到了一个很高的程度,所以影响程序的优劣,仅需关注时间复杂度。
时间复杂度
1.什么是时间复杂度
- 计算机科学中,算法的时间复杂度是一个函数,描述了算法的运行时间,分析算法耗费的时间,就是分析时间复杂度,以减少上机测试的次数;
- 在算法中花费的时间,跟其中语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。
- 找到某条基本语句与问题规模N之间的数学表达式就是算法的时间复杂度;
- 程序每执行一条语句的执行时间看做一样的,记为一个时间单元
#inclued<iostream>
using namespace std;
int main(){
int n=100;
int a;
for(int i=0;i<n;i++)
{
a++;
}
cout<<a<<endl;
return 0;
}
- 其中赋值语句 ,花费3个时间单元;
- i<n,循环条件, 花费n+1个时间单元;
- i++ &&a++; 花费2*n个时间单元;
T(n)=3n+4;线性关系
赋值语句执行1个时间单元,
循环条件执行n+1个时间单元(最后一次还要比较)
执行循环,n个时间单元
可以看出程序消耗的时间与问题规模N有关,成线性关系;
|问题规模N|1 |10000|
|3n+4|7|30004|n变大时,两者几乎相同,可忽略常数项
| 3n | 3 |30000|
| n | 1 |10000|n变大时,系数的作用变小了(两个相同数量级),可以忽略
所以一般我们会保留最高次项并忽略该项系数;
例如
T(n)=n+1;忽略常数项T(n)–n
T ( n ) = n + n 2 ; 忽略低阶项 T ( n ) − − n 2 T(n) =n+n^2 ; 忽略低阶项T(n)--n^2 T(n)=n+n2;忽略低阶项T(n)−−n2
T(n)=3n;忽略最高阶的系数T(n)–n
- 由上我们得出 如何计算时间复杂度呢?
1.忽略常数项;
2.忽略系数;
3.只保留最高项;
常见的时间复杂度:
从小到大
O ( 1 ) < o ( l o g n ) < O ( n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) O(1)<o(logn)<O(n)< O(nlogn)< O ( n^2 ) <O(n^3)<O(2^n )<O(n!) O(1)<o(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)
数量级 | 名称 |
---|---|
O(1) | 常数阶 |
O(logn) | 对数阶 |
O(n) | 线性阶 |
O(nlogn) | 线性对数阶 |
O ( n 2 ) O(n^2) O(n2) | 平方阶 |
O ( n 3 ) O(n^3) O(n3) | 立方阶 |
O ( 2 n ) O(2^n) O(2n) | 指数阶 |
O ( n !) O(n!) O(n!) | 阶乘阶 |
时间复杂度的应用
1.由题目数据范围判断代码是否会超时(TLE);
2.通过题目数据范围反推算法;yxc博客
p8780;
/*
1秒测评机 会执行 10^9次运算
1.为什么只有50分?
O(n) = 10^6 执行时间小于1秒
2.为什么是WA而不是TLE?
2.1.超时了,一直跑都没跑出来 WA
2.2.超时了,但是跑出来了和数据是匹配的 TLE
3.为什么要算最坏的情况?
最坏的情况:
a = 1 b = 1 n = 10^18
10^18
基本上每个题都有一个极限的数据范围。如果最坏的情况都不会超时说明程序一定不会超时。
*/
#include<iostream>
using namespace std;
typedef long long ll;//用ll 来表示 long long
int main()
{
ll a, b, n;
cin >> a >> b >> n;
//方法1 会超时
/*ll cnt = 0;//统计一下当前刷了多少道题
//枚举天数
for(ll i = 1; ; i ++)
{
if(i % 7 == 6 || i % 7 == 0) cnt += b;//如果是6和7的倍数就写b道题
else cnt += a;
if(cnt >= n)
{
cout << i << endl;
break;
}
} */
//方法2 首先计算出一个星期的总刷题量。
ll m = a * 5 + 2 * b;//一个星期的刷题量
ll ans = 0;//记录一共需要多少天
if(n % m == 0){
ans = n / m * 7;
} else { //是n不是m的倍数
ans = n / m * 7;//花这么多个星期*7天数
n = n - n / m * m;//n/m*m 得到已经做了多少道题。
for(int i = 1; i <= 7; i ++)
{
if(i == 6 || i == 7) n -= b;
else n -= a;
if(n <= 0) {
ans += i;
break;
}
}
}
cout << ans << endl;
return 0;
}
p2249;
/*
1.有循环
1.1 单重循环 直接看循环次数
1.2 多重循环 直接把每重循环次数成乘起来
2.无循环 O(1)
*/
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];//读入
// 不减 包括 1.严格单调递增1 2 3 4 5 6 2.非严格单调递增的 1 1 2 2 3 5 6
//10^9
//1.为什么10^9会超时
//O(n) 系数n
//处理m个询问
//暴力时间复杂度是O(nm) n=10^6 m=10^5 nm = 10^11 1s 10^9 所以O(nm)一定会超时
while(m --) //外层的O(m)是必定的,优化不了 所以我们考虑优化循环里面的操作。
{
//查找一个数出现的第一个下标 暴力O(n) 正解:二分查找O(log n)
int x;
cin >> x;//带查找的数x
int l = 1, r = n;
while(l < r)
{
int mid = l + r >> 1;//取中间点 mid = (l + r) / 2
if(a[mid] >= x) r = mid;
else l = mid + 1;
}
if(a[l] == x) cout << l << " ";
else cout << -1 << " ";
}
//正解的时间复杂度:O(mlogn) = 10^5 * log 10^6 = 2*10^6
// 2^10 = 1024 约等于 1000=10^3
//log 10^6 = 20次
return 0;
}
通过合理的算法来,对代码进行优化