时间复杂度
时间复杂度实际就是一个函数(这里指的是数学里的函数),该函数计算的是执行基本操作的次数。
为什么时间复杂度不是计算执行的实践而是次数?
答:因为我们无法计算执行的时间,比如不同的机器不同的配置,同一个算法的运行时间都是不一样的。所以我们只能在这里执行的次数来表示算法的性能。
void test(int n)
{
int count = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
count++;
}
}
for (int k = 0; k < 2 * n; k++)
{
count++;
}
int m = 10;
while (m--)
{
count++;
}
}
语句执行次数:f(n)=n^2+2*n+10
算法分析的分类
(1)最坏情况:任意输入规模的最大运行次数。(上界)
(2)平均情况:任意输入规模的期望运行次数。
(3)最好情况:任意输入规模的最小运行次数,通常最好情况不会出现。(下界)
在实际中,我们通常考量的是算法的最坏运行情况,理由:
- 一个算法的最坏情况的运行时间是在任意输入下的运行时间的上界
- 最坏情况出现的较为频繁
- 平均情况和最坏情况一样差
O的渐进表示法
通常我们使用O记号法表示最坏的运行情况的渐进上界。其实也就是说我们使用O标记法表示时间复杂度,一般情况关注的是算法的运行最坏的情况。
为什么要关注最坏的情况?
常见算法的时间复杂度:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除这个项相城的常数
void test1(int n)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
count++;
}
}
//时间复杂度:O(10)->O(1)
void test2(int n)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
count++;
}
for (int j = 0; j < 2 * n; j++)
{
count++;
}
}
//时间复杂第:O(10+2*n)->O(n)
void test3(int n)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
count++;
}
for (int i = 0; i < 2*n; i++)
{
count++;
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
count++;
}
}
}
//时间复杂度:O(10+2*n+n^2)->O(n^2)
void test4(int n,int m)
{
int count = 0;
for (int i = 0; i < n; i++)
{
count++;
}
for (int j = 0; j < m; j++)
{
count++;
}
}
//时间复杂度:O(m+n)
递归算法的时间复杂度:
递归总次数*每次递归次数
int F(int n)
{
if (n == 1)
return n;
return F(n - 1);
}
//时间复杂度:O(N)
空间复杂度
计算与时间复杂度类似,也用O(n)——对象的个数
递归算法的时间复杂度=递归深度*每次递归的空间大小
int fib1(int n)//递归
{
return n > 1 ? fib1(n - 1) + fib1(n - 2) : n;
}
//时间复杂度:O(2^n)
//空间复杂度:O(n)
int fib2(size_t n)//非递归
{
if (n < 2)
return n;
int first = 0;
int second = 1;
int ret = 0;
for (size_t i = 2; i <= n; i++)
{
ret = first + second;
first = second;
second = ret;
}
return ret;
}
//时间复杂度:O(n)
//空间复杂度:O(1)
常见的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logN) < O(N) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)