for循环次数太多的时间优化_算法的时间复杂度是怎么来的?

本文通过实例探讨如何计算代码执行次数,并引出时间复杂度的概念。从简单的常数阶、线性阶、对数阶到平方阶,解析不同算法的时间复杂度。以冒泡排序为例,分析其在最好和最坏情况下的时间复杂度,总结冒泡排序的时间复杂度为O(n^2)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1a783d13ca145cce556d7b76fb3d510c.png

作者 | Lee

在研究排序算法的时候,我们经常会听到时间复杂度的概念。很多时候只是习惯性的记下来,但是怎么来的,却很少能够研究明白。于是在笔试题中,经常要求时间复杂度为O(n),就很懵。

解决同样的问题,不同的代码消耗的时间会不一样。衡量一段代码的好坏通常有两个标准:

  1. 运行的时间;

  2. 占用的空间;

占用的空间不在本文讨论范围内。

运行的时间往往会随着输入规模以及机器性能的不同而不同,所以想要精准的算出代码的执行时间基本上不可能的。但是我们可以估算出代码的执行次数。

代码的执行次数

代码执行的次数记为 T(n)。

案例一:

void func(){    printf("helloworld\n");}

函数只有一条语句,代码执行了 1 次。T(n) = 1。

案例二:

void func(){    int i;    for (i = 0; i 5; i++)        printf("helloworld\n");}

输出语句执行了 5 次,循环变量执行 5 次,故T(n) = 10。(循环变量的定义和初始化也可以算作执行次数,只不过对最终的时间复杂度没有影响。为了方便计算,以下所有案例都没有算入循环变量,只计算循环体执行次数。)

案例一和案例二都可以称作常数阶

案例三:

void func(int n){    int i;    for (i = 0; i < n; i++)        printf("helloworld\n");}

输出语句执行次数 n 次,T(n) = n。

像下面这种的,属于同一个案例:

void func(int n){    int i;    for (i = 0; i < n; i++)    {        printf("helloworld\n");        printf("helloworld\n");        printf("helloworld\n");    }}

输出语句的执行次数 T(n) = 3n。

案例三属于线性阶

案例四:

int func(int n){    int i;    for (i = 1; i <= n; i *= 2)    {        printf("helloworld\n");    }}

这个应该怎么计算,假设执行的次数为x,则应该满足2 ^ x = n,所以输出语句执行的次数为 log2n,T(n) = log2n。

案例四属于对数阶

案例五:

int func(int n){    int i, j;    for (i = 0; i < n; i++)    {        for (j = 0; j < n; j++)        {            printf("helloworld\n");        }       }}

第一个循环执行了 n 次,第二个循环同样执行了 n 次,所以输出语句一共执行了 n^2次。T(n) = n^2。

下面的案例类似:

int func(int n){    int i, j;    for (i = 0; i < n; i++)    {        for (j = 0; j         {            printf("helloworld\n");        }       }    printf("12345\n");}

这个计算起来有点麻烦。字符串 “12345” 输出了 n 遍;字符串 “helloworld” 输出了 1 + 2 + 3 + 4 + ... + n 遍。两个输出语句一共执行了 0.5n^2 + 1.5n。T(n) = 0.5n^2 + 1.5n。

案例五属于平方阶

渐近时间复杂度

代码的执行次数 T(n) 还不能体现出代码的效率,主要是因为表达式中含有系数。比如算法 A 的执行次数:

T(n) = 5n;

算法 B 的执行次数:

T(n) = 2n^2。

如果 n = 1 很显然,算法 B 的效率更高;但是如果 n = 10 结果又变成了算法 A 的效率更高。所以代码的执行次数无法比较两种算法的好坏。

所以就有了渐近时间复杂度的概念。

定义如下:

若存在函数 f(n) 使得当 n 趋于无穷大时,T(n) / f(n) 的极限值为不等于 0 的常数,则 f(n) 是 T(n) 的同数量级函数。记作 T(n) = O(f(n)),其中 O(f(n)) 称为该算法的渐近时间复杂度,简称时间复杂度。

听起来很难懂。记住时间复杂度的几个原则:

  1. 如果执行的次数是常数量级,则用常数 1 来表示;

  2. 只保留 T(n) 中最高的阶项;

  3. 省略最高项前面的系数。

看下上面的案例。

案例一的代码执行次数:

T(n) = 1

案例二的代码执行次数:

T(n) = 5

案例一和案例二的执行次数都是常量,跟 n 的值无关,所以时间复杂度记为 O(1)。

案例三的代码执行次数:

T(n) = n

T(n) = 3n

保留最高阶并且省去他们的系数,得到时间复杂度为 O(n)。

案例四的代码执行次数:

T(n) = log2n

于是得到时间复杂度就是:O(log2n)。

案例五的代码执行次数:

T(n) = n^2

T(n) = 0.5n^2 + 1.5n

保留最高阶并且去掉系数,得到时间复杂度 O(n^2)。

有了时间复杂度,我们就能通过时间复杂度来判断代码的好坏,很显然:

O(1) 

冒泡排序的时间复杂度分析

先来看下冒泡的代码:

#include int main(){    int a[10] = {0};    printf("请输入10个数字:\n");    int i, j;    for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)    {           scanf("%d", &a[i]);    }       for (i = 0; i < sizeof(a) / sizeof(a[0]) - 1; i++)    {           for (j = 0; j < sizeof(a) / sizeof(a[0]) - i - 1; j++)        {               if (a[j] < a[j + 1])             {                  int t = a[j];                a[j] = a[j + 1];                a[j + 1] = t;            }           }       }       for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)    {           printf("%d ", a[i]);    }    printf("\n");    return 0;}

这里我们只关注算法本身的执行次数:

假设一共有 n 个数据,则一共循环 n + (n - 1) + ... + 2 + 1 = 0.5n^2 + 0.5n次。

因为循环体有个判断条件,所以循环体中的三条语句并不是每次都要执行。最好的情况是,判断条件每次都不成立(数据本身就是有序的),则代码执行次数就是循环变量的执行次数:

T(n) = 0.5n^2 + 0.5n

最坏的情况是,判断条件每次都成立(数据本身正好逆序),则

T(n) = 4 * (0.5n^2 + 0.5n)

综上,冒泡排序的时间复杂度为 O(n^2)。

注:有些时候冒泡排序的最好情况时间复杂度为 O(n),因为修改了代码,需要添加一个标志,如果一趟比较完发现没有需要交换的数据,则退出循环。

9b4bcf7ee2dc45932f76828a9cc9f207.png

70b9a1e61da37b4c8e7787c3bede81bb.png

c755dfcc4b5624b61cf5c970201db055.png

aed226a627b6cf4ac7700273af3e7bd2.png

196b61e8271b377a53010f47b0528dac.png

d418bc08630ec09c79d992d82a2c81d3.png

d05e66d8f6a02a8092ffe91e04504a67.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值