一、嵌套循环的时间复杂度
1-1
//计算func1中++count语句总共执行了多少次?
void func1(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;
printf("%d\n", count);
}
我们可以计算出其准确值为:
写成一个跟N相关的函数式:
即为时间复杂度的函数式
func1的基本操作次数:
N = 10 | F(N) = 130 |
N = 100 | F(N) = 10210 |
N = 1000 | F(N) = 1002010 |
N越大,后两项对整个结果的影响就越小
简化这个表达式,只留下对结果影响最大的项
所以,这个嵌套循环的时间复杂度为:O(N^2)
要点
所以,实际中我们计算时间复杂度时,其实并不一定要计算精确的执行次数,而只需要大概执行次数, 那么这里我们使用大O的渐进表示法。 (类似于估算)
大O的渐进表示法
大O符号(Big O notation): 是用于描述函数渐进行为的数学符号。
推导大O阶的方法:
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
这样列出来稍显抽象,理解起来也不够深刻
我们根据后面的例题来巩固理解到的方法
二、双重循环的时间复杂度
2-1
//计算func2的时间复杂度?
void func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; k++)
++count;
int M = 10;
while (M--)
++count;
printf("%d\n", count);
}
精确计算值为:2N + 10
运行时间中没有常数。
保留最高阶项 - > 2N
最高阶存在且不为1,去除与之相乘的常数 - > N
最终取时间复杂度为:O(N)
2-2
//计算func3的时间复杂度?
void func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; k++)
++count;
for (int k = 0; k < N; k++)
++count;
printf("%d\n", count);
}
func3的时间复杂度为:O(M+N)
注:一般情况下,时间复杂度计算时用的未知数都是N,但也可以是M、K、S......
若题目中给出条件,时间复杂度则为:
M >> N | O(M) |
N >> M | O(N) |
M、N接近 | O(M)、O(N) |
三、常数循环的时间复杂度
3-1
//计算func4的时间复杂度?
void func4(int N)
{
int count = 0;
for (int k = 0; k < 100; k++)
++count;
printf("%d\n", count);
}
该循环次数为100次,在大O的渐进表示法中,用1取代常数次数
所以func4的时间复杂度是:O(1)
四、strchr的时间复杂度
4-1
//计算strchr的时间复杂度?
const char* strchr(const char* str, int character);
strchr
![](https://i-blog.csdnimg.cn/blog_migrate/8f2befacc138d722408bc51bcaffde14.png)
用于在一个字符串中查找单个字符
要点
![](https://i-blog.csdnimg.cn/blog_migrate/81644c8dde72ccada0aa42c0840b07c6.png)
最好情况:任意输入规模的最小运行次数(下界)
平均情况:任意输入规模的期望运行次数
最坏情况:任意输入规模的最大运行次数(上界)
最好情况:次
平均情况:次
最坏情况:次
在实际中一般情况关注的是算法的最坏运行情况,所以strchr的时间复杂度为:O(N)
五、冒泡排序的时间复杂度
5-1
//计算BullleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
过程
我们通过举简单的例子来算出BubbleSort的时间复杂度
![](https://i-blog.csdnimg.cn/blog_migrate/2d919ce3c0e2638f07aea66d66dd63cf.png)
这个算式其实就是一个等差数列,等差数列的前n项和公式为:
结果
所以准确值为:
因时间复杂度采用大O的渐进表示法,综上,BubbleSort的时间复杂度为;O(N^2)
六、二分查找的时间复杂度
6-1
//计算BinarySearch的时间复杂度?
int BinarySearch(int* arr, int aim, int sz)
{
assert(arr);
int left = 0;
int right = sz - 1;
while (left <= right)
{
int subscript = (left + right) / 2;
if (arr[subscript] < aim)
{
left = subscript + 1;
}
else if (arr[subscript] > aim)
{
right = subscript - 1;
}
else
{
return subscript;
break;
}
}
if (left > right)
return -1;
}
二分查找的详细思路可以参照 - > 鹏哥二分法查找数组中元素
设数组的个数为N,查找的次数为X
计算时间复杂度,我们考虑最坏情况:该元素在头尾或找不到
正向
第一次查找,N/2;找不到
第二次查找,N/2/2;找不到
第三次查找,N/2/2/2;找不到
......
第X次查找,N一直除直到等于1。
表达式为:,所以查找次数X为:
即BinarySearch的时间复杂度为:O().
逆向
从1开始往回展开
![](https://i-blog.csdnimg.cn/blog_migrate/7164e011dd15db262fa045f52c22bdc0.png)
这样展开直到结果等于数组元素个数N,查找次数X
1*2*2*2*......=N,即
也算出了BinarySearch的时间复杂度为:O().
七、递归算法的时间复杂度
递归算法的时间复杂度 = 递归次数 * 每次递归调用的次数
7-1
//计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
每次递归调用的次数为2,记为O(1)
递归次数为N,记为O(N)
所以Fac的时间复杂度为:O(N)
7-2
//计算斐波那契数列递归Fib的时间复杂度?
long long Fib(size_t N)
{
if (N > 2)
return Fib(N - 1) + Fib(N - 2);
else
return 1;
}
斐波那契数列递归的分析
![](https://i-blog.csdnimg.cn/blog_migrate/e48abf14d0af25c937c8ec2d4d4b0190.png)
这样看下来,总共的递归次数像是一个等比数列求前n项和
等比数列的前n项和公式:
所以,...
整理得,
且x远远小于2的n次方
综上,Fib的时间复杂度为:O()