当前位置: 首页 > news >正文

时间复杂度计算(以for循环为例)

本文理论内容来自严蔚敏版《数据结构(C语言版 第2版)》

  

*本文仅为复习时的总结,描述不准确、过程不严谨之处,还请理解

一、算法的相关概念

首先复习一下算法的定义及5个重要特性

其次是算法的评价标准

可以看到  时间复杂度  属于算法评价标准中的高效性

  

  

二、时间复杂度的相关概念

下面介绍一些时间复杂度的相关概念

如果不考虑计算机的软硬件等环境因素

影响算法时间代价的最主要因素是问题规模(用非负整数 n 表示,n \geqslant 0

问题规模是算法求解问题输入量的多少,是问题大小的本质表示

比如输入2个数排序、3个数排序、4个数排序,问题规模(n) 逐渐增大,执行时间也逐渐增加

  

  

再来看执行时间

语句的执行时间 进行拆解:

语句的重复执行次数 称作 语句频度(用 f(n) 表示)

算法中能使语句重复执行的结构通常是循环结构,所以本文以for循环为例

为了简化分析,假设 每条语句执行一次所需的时间 均是 单位时间

  

所以 语句的执行时间=语句频度×单位时间

同理 算法的执行时间=所有语句频度之和×单位时间 (即提取公因式 "单位时间" )

  

由于 单位时间 是定值

因此 算法的执行时间 与 所有语句频度之和 成正比

  

对于稍微复杂一些的算法,计算所有语句频度之和通常是比较困难的

因此,我们引入基本语句的概念,用于度量算法的工作量

基本语句指的是对算法的执行时间贡献最大的语句,它的语句频度 与 算法的执行时间 成正比

  

*为了方便描述,例题均以for循环为例,仅写出主要代码

如果不太了解for循环可以先看我的这篇文章——

《 for、while、do while循环的流程图表示及相应continue、break的流程图表示 》

for循环可以理解为

for( 循环变量初始化 ; 循环条件 ; 循环变量自增 )
{循环体
}

举例:for循环求1到100的和

for( i=1; i<=100; i++ )
{sum+=i;
}

对应关系

   

【例1】有以下代码,输入n代表问题规模(在后续例题中,此点将不再赘述),计算for循环体语句频度之和

int n,i,j,k;
int a=0,b1=0,b2=0,c1=0,c2=0,c3=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{a++;for(j=1; j<=n; j++){b1++;b2++;for(k=1; k<=n; k++){c1++;c2++;c3++;}}
}

在第一层for循环体中,只有1条语句:a++;

循环变量 i 从1到n,循环共执行 n

所以第一层for循环体的语句频度=循环体语句条数 x 循环执行次数= 1\times n=n

  

在第二层for循环体中,有2条语句:b1++; b2++;

循环变量 i 从1到n,循环变量 j 从1到n,循环共执行 n\times n=n^{2}

所以第二层for循环体的语句频度=循环体语句条数 x 循环执行次数= 2\times n^{2}=2n^{2}

  

在第三层for循环体中,有3条语句:c1++; c2++; c3++;

循环变量 i 从1到n,循环变量 j 从1到n,循环变量 k 从1到n,循环共执行 n\times n\times n=n^{3}

所以第三层for循环体的语句频度=循环体语句条数 x 循环执行次数= 3\times n^{3}=3n^{3}

  

所以for循环体语句频度之和为 f(n)=3n^{3} +2n^{2} +n

问题规模(n)趋于无穷大时

\lim\limits_{n \to \infty}\frac{f(n)}{n^{3}}=\lim\limits_{n \to \infty}\frac{3n^{3}+2n^{2}+n}{n^{3}}=3

因为3是常数,所以 f(n) 与 n^{3} 同阶(或称 f(n) 的数量级为 n^{3}


时间复杂度 T(n)  语句频度的数量级 来表示(大O表示法)

O表示法在数学上的定义:

 T(n) 和 f(n) 都是定义在正整数集合上的函数

若存在正的常数:c 和 n_{0},对于所有 n\ (n\geqslant n_{0}), 均满足不等式 0\leqslant T(n)\leqslant c\cdot f(n)

则记作 T(n)=O (f(n))

该定义用 f(n) 来渐近表示时间复杂度 T(n)

从图中可以看出时间复杂度 T(n) 的增长至多趋向于 f(n) 的增长

换句话说,大O表示法 O (f(n)) 给出的是时间复杂度的一个渐近上界 f(n)


【例1】的语句频度之和为 f(n)=3n^{3} +2n^{2} +n,数量级为 n^{3} ,则时间复杂度为 O(n^{3})

可以看到语句频度中起主要作用的是 3n^{3}

对应第三层for循环体的3条语句:c1++; c2++; c3++; 

则这3条语句是对算法的执行时间贡献最大的语句(即基本语句

所以计算时间复杂度时,无需计算所有语句的语句频度,只需计算基本语句语句频度,得出数量级即可


计算时间复杂度的步骤:

1.找出基本语句

2.计算基本语句语句频度

3.省略系数和无关项,得出数量级,即为时间复杂度

  

  

三、求和式及其运算规则

在计算时间复杂度之前,需要先了解求和式 \sum\limits_{i=1}^{n} a_{i}

如果不太了解求和式可以先看我的这篇文章——《 求和式及其运算规则 》

   

求和符号\sum (读作sigma)

求和下限 i=1 :代表求和变量 i 从1开始递增

求和上限 n :代表递增到n结束

求和表达式 a_{i}: 一般是通项公式(只不过是把通项下标符号从 n 换成 i 而已)

求和变量 i 从1递增到n,则求和表达式 a_{i} 从 a_{1} 变化到 a_{n}

求和式整体即从 a_{1} 累加到 a_{n}:  \sum\limits_{i=1}^{n} a_{i}=a_{1}+a_{2}+a_{3}+\cdots+a_{n-1}+a_{n}

  

如果学过for循环就很好理解了

for(i=1; i<=n; i++)
{sum+=a[i];
}

i=1 时 :当前项 a_{i}=a_{1},求和sum= a_{1},i++自增到2

i=2 时 :当前项 a_{i}=a_{2},求和sum= a_{1}+a_{2},i++自增到3

i=3 时 :当前项 a_{i}=a_{3},求和sum= a_{1}+a_{2}+a_{3},i++自增到4

……

i=n-1 时 :当前项 a_{i}=a_{n-1},求和sum= a_{1}+a_{2}+a_{3}+\cdots+a_{n-1},i++自增到n

i=n 时 :当前项 a_{i}=a_{n},求和sum= a_{1}+a_{2}+a_{3}+\cdots+a_{n-1}+a_{n},i++自增到n+1

i=n+1 时:i<=n为假,循环结束

最终求和sum的值: sum=\sum\limits_{i=1}^{n} a_{i}=a_{1}+a_{2}+a_{3}+\cdots+a_{n-1}+a_{n}

  

  


求和式的数乘规则(提取常数):

\sum\limits_{i=1}^{n} (c\cdot a_{i})=c\sum\limits_{i=1}^{n} a_{i} (其中c为常数)


求和式的加法规则(减法同理):

\sum\limits_{i=1}^{n} (a_{i}+b_{i})=\sum\limits_{i=1}^{n} a_{i}+\sum\limits_{i=1}^{n} b_{i}


求和式的分段规则:

\sum\limits_{i=1}^{n}a_{i}=\sum\limits_{i=1}^{m}a_{i}+\sum\limits_{i=m+1}^{n}a_{i}(其中 1\leqslant m< m+1\leqslant n


求和式的上下限变换规则:

\sum\limits_{i=1}^{n} a_{i}=\sum\limits_{i=1+k}^{n+k} a_{i-k}


上述规则为简便起见,求和上下限都是从1到n

实际遇到的求和式上下限不一定是从1到n

有可能是从0到k,从m到n+1,从0到n-1-i,……

但是均适用上述规则

  

  

四、嵌套无关循环和嵌套相关循环

下面区分一下 嵌套无关循环嵌套相关循环

(有点类似于SQL的 不相关子查询 和 相关子查询)

【例2】嵌套无关循环,内层循环条件与外层循环变量无关

int n,i,j;
int a=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{for(j=1; j<=n; j++){a++;}
}

可以看到内层循环条件是 j<=n,j 从1到n,执行n次

无论外层循环变量 i 取何值,每轮内层循环都会执行n次

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\Large \sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n}1=n \times n=n^{2}

3.时间复杂度T(n)=O(n^{2})

  

解释一下,这里的语句频度是怎么列式出来的

嵌套无关循环,所以内外层循环互不干扰,语句频度独立计算

因为外层循环条件是 i<=n ,所以 i 能取的最大值(即求和上限)是:n

因为外层循环变量初始化是 i=1,所以 i 能取的最小值(即求和下限)是:1

(通常情况, 求和下限 直接照抄 循环变量初始化 即可,后续不再赘述)

而内层循环可以看作是外层循环体的1条基本语句,则求和表达式(通项公式)是1

所以外层循环的语句频度

\sum\limits_{i=1}^{n}1 =\overbrace{1+\cdots +1}^{n} = n

 

因为内层循环条件是 j<=n ,所以 j 能取的最大值(即求和上限)是:n

内层循环体只有1条基本语句:a++;  则求和表达式(通项公式)是1

所以内层循环的语句频度

\sum\limits_{j=1}^{n}1 =\overbrace{1+\cdots +1}^{n} = n

 

只有当内外层循环都结束时,算法才结束

(即算法结束需要分内层循环、外层循环两个步骤)

根据统计学的乘法原理,需要把二者相乘得

\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n}1=n \times n=n^{2}

语句频度是 n^{2},数量级为 n^{2} ,则时间复杂度为 O(n^{2})

  

其实【例1】就是嵌套无关循环,再把【例1】的代码贴过来

int n,i,j,k;
int a=0,b1=0,b2=0,c1=0,c2=0,c3=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{a++;for(j=1; j<=n; j++){b1++;b2++;for(k=1; k<=n; k++){c1++;c2++;c3++;}}
}

按现在的步骤重新计算时间复杂度

1.基本语句

c1++;

c2++;

c3++;

2.基本语句语句频度\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n}1 \times \sum\limits_{k=1}^{n}3=n \times n \times 3n=3n^{3}

3.时间复杂度T(n)=O(n^{3})

解释一下,这里的语句频度是怎么列式出来的

嵌套无关循环,所以内外层循环互不干扰,语句频度独立计算

将前两层循环与第三层循环分开计算:

因为内层循环可以看作是外层循环体的1条基本语句

所以前两层循环可以直接参考【例2】,列式为

\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n}1

 

基本语句是对算法的执行时间贡献最大的语句

所以基本语句是第三层循环体中的3条语句:c1++; c2++; c3++;  

所以第三层循环的求和表达式(通项公式)是3,列式为

\sum\limits_{ k=1}^{n}3 =\overbrace{3+\cdots +3}^{n} = 3n

将其展开,其实就是n个3相加,结果为3n

或者根据求和式的数乘规则,将常数提出

\sum\limits_{ k=1}^{n}3=3\sum\limits_{ k=1}^{n}1 =3\times (\overbrace{1+\cdots +1}^{n}) =3\times n = 3n 

提取常数可以理解为乘法分配律的逆运算 

c\times a+c\times b=c\times (a+b)

将a=1, b=1, c=3代入得

3\times 1+3\times 1=3\times (1+1)

只不过现在不是2项,而是n项 

\overbrace{3\times1+\cdots+3\times1}^{n}=3\times (\overbrace{1+\cdots+1}^{n})=3\sum\limits_{ i=1}^{n}1

  

根据统计学的乘法原理,将前两层求和式与第三层求和式相乘得语句频度

\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n}1 \times \sum\limits_{k=1}^{n}3=n \times n \times 3n=3n^{3}

虽然语句频度是 3n^{3},但省略系数后,数量级为 n^{3} ,则时间复杂度为 O(n^{3})

  

  

【例3】嵌套相关循环,内层循环条件与外层循环变量相关

int n,i,j;
int a=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{for(j=1; j<=i; j++){a++;}
}

可以看到内层循环条件是 j<=i,j 从1到 i,执行 i 次

当外层循环变量 i 的值发生改变时,内层循环的执行次数也会随之改变

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} 1=\sum\limits_{i=1}^{n} i=1+2+\cdots +n=\frac{n(n+1)}{2}

3.时间复杂度T(n)=O(n^{2})

解释一下,这里的语句频度是怎么列式出来的

因为是嵌套相关循环,内外层循环相关,不能分开计算

因为外层循环条件是 i<=n ,所以 i 能取的最大值(即求和上限)是:n

因为内层循环条件是 j<=i ,所以 j 能取的最大值(即求和上限)是:i

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} 1

双重求和可以加括号以区分

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} 1=\sum\limits_{i=1}^{n} (\sum\limits_{j=1}^{i} 1) 

可以类比复合函数,先计算括号内的

\sum\limits_{j=1}^{i}1 =\overbrace{1+\cdots +1}^{i} = i

(这里只不过是把求和上限从n变成 i,与前面本质相同,就是 i 个1相加)

将括号内的求和式替换为计算结果,双重求和变为 

\sum\limits_{i=1}^{n} (\sum\limits_{j=1}^{i} 1)=\sum\limits_{i=1}^{n} i


注意,当求和变量 i 与 通项公式 a_{i} 的下标 是相同符号时,代表累加的项随求和变量进行变化(如下所示)

\sum\limits_{i=1}^{n} a_{i}=a_{1}+a_{2}+a_{3}+\cdots + a_{n}


本题比较特殊,通项公式就是求和变量: a_{i}=i

\sum\limits_{i=1}^{n} i=1+2+3+\cdots +n

求和变量 i 从 1 到 n,则通项公式也是从 1 到 n,求和式整体即从1累加到n

此时变为等差数列求和,代入等差数列求和公式 

S_{n}=\frac{n(a_{1}+a_{n})}{2} 

得到语句频度

\sum\limits_{i=1}^{n} i=1+2+3+\cdots +n=\frac{n(n+1)}{2}

将其展开得

\frac{n(n+1)}{2}=\frac{1}{2}n^{2}+\frac{1}{2}n

省略系数和无关项后,数量级为 n^{2},则时间复杂度为 O(n^{2})

  

  

五、常见的时间复杂度

下面介绍常见的时间复杂度

将各时间复杂度当作函数,里面的自变量n就是问题规模

因为问题规模(n)是描述问题大小的非负整数 (n\geqslant 0)

所以比较时间复杂度只需看第一象限

 

比较时间复杂度,通常按问题规模(n)趋于无穷大(n \to \infty)来比较

所以常见时间复杂度关系如下:

\begin{matrix} O(2^n)>O(n^3)>O(n^2)>O(nlog_{2}n)>O(n)>O(\sqrt{n})>O(log_{2}n)>O(1) \end{matrix}

但是上图为了看清每个函数的趋势,对自变量问题规模(n)的取值范围过小,导致误解

误解一:对 2^{n} 和 n^{3} 的增长速率产生误解

下面将 2^{n} 和 n^{3} 单独画出,并扩大取值范围

可以看到 2^{n} 和 n^{3} 这两个函数在第一象限有两个交点

一个交点在 n \in[1,2] 区间内(如果看不清可以看第一张函数图)

另一个交点在 n \in[9,10] 区间内

在第二次相交后,2^{n} 的增长速率明显要比 n^{3} 快 

所以当问题规模(n)趋于无穷大时:O(2^n)>O(n^3)

  

误解二:对 \sqrt{n} 和 log_{2}n 的增长速率产生误解

在第一张函数图中这两个函数图像几乎重合

下面将 \sqrt{n} 和 log_{2}n 单独画出来,并扩大取值范围

可以看到 \sqrt{n} 和 log_{2}n 这两个函数在第一象限有两个交点(4,2)和(16,4)

在第二次相交后, \sqrt{n}  的增长速率明显要比 log_{2}n 快 

所以当问题规模(n)趋于无穷大时:O(\sqrt{n})>O(log_{2}n)

题目选项可能会把 \sqrt{n} 写成幂次形式 n^{\frac{1}{2}} ,上式变为:O(n^{\frac{1}{2}})>O(log_{2}n)

  

    

(一)常数阶

【例4.1】常数阶 O(1) 

int i,x=0,s=0;
for(i=0;i<1000;i++)
{x++;s+=x;
}

这个例子是我特意挑的

因为它的for循环从0开始,循环条件是<

而前面的for循环都是从1开始,循环条件是<=

 

计算时间复杂度

1.基本语句

x++; 

s+=x;

2.基本语句语句频度\sum\limits_{i=0}^{999} 2=\sum\limits_{i=1}^{1000} 2=\overbrace{2+\cdots + 2}^{1000} =2000

3.时间复杂度T(n)=O(1)

因为循环条件是 i<1000 ,所以 i 能取的最大值(即求和上限)是:1000-1=999

0—999和1—1000在循环的执行次数没有区别,都是1000次

如果反应不过来,那就将0—999同时加1

相当于在数轴上将0—999整体向右移1个单位,得到1—1000

比如1—5就执行5次,1—200就执行200次,1—1000就执行1000次

for循环有2条基本语句,所以求和表达式(通项公式)是2,列式为

\sum\limits_{i=0}^{999} 2

根据求和式的上下限变换规则

\sum\limits_{i=1}^{n} a_{i}=\sum\limits_{i=1+k}^{n+k} a_{i-k}

本例的通项公式是常数2,没有下标(或者说无论下标如何变化,通项不变)

a_{1}=2,\ a_{2}=2,\ \cdots ,\ a_{n}=2

所以只变换求和上下限

\sum\limits_{i=0}^{999} 2=\sum\limits_{i=0+1}^{999+1} 2=\sum\limits_{i=1}^{1000} 2=\overbrace{2+\cdots + 2}^{1000} =2000

在变换求和上下限后,也可根据求和式的数乘规则,提出常数再计算

\sum\limits_{i=1}^{1000} 2=2\sum\limits_{i=1}^{1000} 1=2\times (\overbrace{1+\cdots + 1}^{1000}) = 2\times1000=2000

算出语句频度为2000(常数),数量级为 1,则时间复杂度O( 1 )


以后只要见到语句频度是常数,不带问题规模(n),那么时间复杂度就是 O( 1 )


  

  

【例4.2】常数阶 O(1)

int x=90,y=100; 
while(y>0)
{if(x>100) {x=x-10;y--;} else {x++;}
}

这道题挺唬人,一时间还不好算出来

但是如果你看出循环变量初始化和循环条件都是常数,不带问题规模(n)

那么语句频度肯定是常数,一秒即可得出时间复杂度 O(1)

计算时间复杂度

1.基本语句

x=x-10;

y--;

x++;

2.基本语句语句频度100\times(11+1)+1=100\times12+1=1201

3.时间复杂度T(n)=O(1)

如果仍要算出语句频度,就得代入看逻辑了

【第1轮】

x==90,不满足x>100,进else:x++(自增到91)

x==91,不满足x>100,进else:x++(自增到92)

……

x==101,满足x>100,进if:x=x-10(减到91);  y--(自减到99)

   

【第2轮】

x==91,不满足x>100,进else:x++(自增到92)

……

x==101,满足x>100,进if:x=x-10(减到91);  y--(自减到98)

    

【第3轮】

x==91,不满足x>100,进else:x++(自增到92)

……

x==101,满足x>100,进if:x=x-10(减到91);  y--(自减到97)

     

……

……

……

     

【第100轮】

x==91,不满足x>100,进else:x++(自增到92)

……

x==101,满足x>100,进if:x=x-10(减到91);  y--(自减到0)

    

以此类推,可以看到,除第1轮有x==90,其余轮次均是 x从91增到101,每轮循环执行11次

有人看到执行11次可能反应不过来,那就将91—101同时减90

相当于在数轴上将91—101整体向左移90个单位,得到1—11,这肯定是11次

注意,每轮最后x==101时,执行的不是else中的1条语句,而是if中的2条语句

所以每轮的语句频度要比循环执行次数多1,即 11+1=12


语句频度与循环执行次数不一定相等,因为循环体可以有多条语句


可以看到,每轮最后都会让y自减:y--

因为while循环条件是 y>0,循环变量 y 能取的最小值(即求和下限)是1

所以 y从100减到1,循环共执行100轮

100轮,每轮执行11+1=12次,计算语句频度

100\times(11+1)=100\times12=1200

看起来似乎是正确的,但是前面说了除第1轮有x==90,其余都没有

所以还得把x==90时,多执行的1条语句:x++; 也算上,才是最终的语句频度

100\times(11+1)+1=100\times12+1=1201

不过这只是探究罢了,实际上不需要算出语句频度,只要看出是常数,时间复杂度就是 O(1)

    

(二)线性阶  

【例5】线性阶 O(n)

int n,i,t,a[100];
scanf("%d", &n);
for(i=0; i<n/2; i++) 
{ t=a[i]; a[i]=a[n-1-i]; a[n-1-i]=t; 
}

计算时间复杂度

1.基本语句

t=a[i]; 

a[i]=a[n-1-i]; 

a[n-1-i]=t;

2.基本语句语句频度\sum\limits_{i=0}^{\lfloor\frac{n-1}{2}\rfloor } 3=\sum\limits_{i=1}^{\lfloor\frac{n-1}{2}\rfloor +1} 3=\overbrace{3+\cdots + 3}^{\lfloor\frac{n-1}{2}\rfloor+1} =3\times (\lfloor\frac{n-1}{2}\rfloor+1)

3.时间复杂度T(n)=O(n)

首先说明一下, \lfloor \ \rfloor 这个符号代表向下取整

向下取整,如果不是整数,则只保留整数部分,忽略小数部分

向下取整,如果是整数,则仍是自身

\lfloor 3.9 \rfloor=3 ,向下取整得 3

\lfloor 16.183 \rfloor=16 ,向下取整得16

\lfloor 7 \rfloor=7 ,整数向下取整,仍是自身

  

因为循环条件是 i<\frac{n}{2}

但n不确定是奇数还是偶数,所以 \frac{n}{2} 不能确定是小数还是整数

下面分情况讨论


当n为奇数时,设 n=2x+1(x是整数)

代入循环条件 i<\frac{n}{2},得 i<\frac{2x+1}{2},即 i<x+\frac{1}{2}

因为x是整数,所以整数 i 的最大值 i_{max}=x

前面设 n=2x+1,反之 x=\frac{n-1}{2}

将其代入 i_{max}=x

奇数情况最大值 i_{max}=x=\frac{n-1}{2}


当n为偶数时,设 n=2x(x是整数)

代入循环条件 i<\frac{n}{2},得 i<\frac{2x}{2},即 i<x

因为x是整数,所以整数 i 的最大值 i_{max}=x-1

前面设 n=2x,反之 x=\frac{n}{2}

将其代入 i_{max}=x-1 得

偶数情况最大值 i_{max}=x-1=\frac{n}{2}-1


两种情况的最大值表达式不一样,如何统一呢

前面提到的 \lfloor \ \rfloor 向下取整就派上用场了


先假设,最大值公式可用向下取整统一为,奇数情况最大值: i_{max}=\lfloor \frac{n-1}{2} \rfloor

因为该式是从奇数情况推导出的,所以奇数肯定没问题

代入验证偶数情况,如果计算结果与偶数情况最大值相同,说明可以统一

当n为偶数时,设 n=2x(x是整数)

代入上式得

i_{max}=\lfloor \frac{n-1}{2} \rfloor=\lfloor \frac{2x-1}{2} \rfloor

进一步化简得

i_{max} = \lfloor \frac{2x-1}{2} \rfloor \\[1.5ex] =\lfloor \frac{2x-1-1+1}{2} \rfloor \\[1.5ex] =\lfloor \frac{2x-2+1}{2} \rfloor \\[1.5ex] =\lfloor \frac{2(x-1)+1}{2} \rfloor \\[1.5ex] =\lfloor \frac{2(x-1)}{2} + \frac{1}{2} \rfloor\\[1.5ex]=\lfloor (x-1)+ \frac{1}{2} \rfloor \\[1.5ex] =x-1

解释一下,因为 x 是整数,所以 x-1 也是整数

所以向下取整时,只保留整数部分:x-1 ,忽略小数部分: \frac{1}{2}

前面设 n=2x,反之 x=\frac{n}{2}

将其代入得 i_{max}=x-1=\frac{n}{2}-1

  

将计算结果与偶数情况最大值 i_{max}=\frac{n}{2}-1 进行对照

可以看到,两式均为 \frac{n}{2}-1,则偶数情况也成立,所以可以统一为 i_{max}=\lfloor \frac{n-1}{2} \rfloor

  


再假设,最大值公式可用向下取整统一为,偶数情况最大值: i_{max}=\lfloor \frac{n}{2}-1 \rfloor

因为该式是从偶数情况推导出的,所以偶数肯定没问题

代入验证奇数情况,如果计算结果与奇数情况最大值相同,说明可以统一

当n为奇数时,设 n=2x+1(x是整数)

代入上式得

i_{max}=\lfloor \frac{n}{2}-1 \rfloor=\lfloor \frac{2x+1}{2}-1 \rfloor

进一步化简得

i_{max}=\lfloor \frac{2x+1}{2}-1 \rfloor\\[1.5ex] =\lfloor \frac{2x+1}{2}-\frac{2}{2} \rfloor\\[1.5ex] =\lfloor \frac{2x+1-2}{2} \rfloor\\[1.5ex] =\lfloor \frac{2x-2+1}{2} \rfloor\\[1.5ex] =\lfloor \frac{2(x-1)+1}{2} \rfloor\\[1.5ex] =\lfloor \frac{2(x-1)}{2} + \frac{1}{2} \rfloor\\[1.5ex] =\lfloor (x-1)+\frac{1}{2} \rfloor\\[1.5ex] =x-1

因为 x 是整数,所以 x-1 也是整数

所以向下取整时,只保留整数部分:x-1 ,忽略小数部分: \frac{1}{2}

前面设 n=2x+1,反之 x=\frac{n-1}{2}

将其代入得 i_{max}=x-1=\frac{n-1}{2}-1

  

将计算结果与奇数情况最大值 i_{max}=\frac{n-1}{2} 进行对照

可以看到,两式一个为 \frac{n-1}{2}-1,另一个为 \frac{n-1}{2},则奇数情况不成立,所以不能统一

  

将n=1到10的计算结果列成表格,也能看出本式不符合


最终统一为奇数情况最大值 i_{max}=\lfloor \frac{n-1}{2} \rfloor

所以 i 的最大值(求和上限)表示为 \lfloor\frac{n-1}{2}\rfloor 

循环变量 i 的变化区间是 0\sim \lfloor\frac{n-1}{2}\rfloor 

如果看不出执行次数,则同时+1

相当于在数轴上将 0 \sim\lfloor\frac{n-1}{2}\rfloor 整体向右移1个单位,得 1 \sim (\lfloor\frac{n-1}{2}\rfloor+1)

则循环实际执行次数为 \lfloor\frac{n-1}{2}\rfloor+1 (这是因为 i 从0开始,所以执行次数要多1)

  

for循环有3条基本语句,所以求和表达式(通项公式)是3,列式为

\sum\limits_{i=0}^{\lfloor\frac{n-1}{2}\rfloor} 3

根据求和式的上下限变换规则

\sum\limits_{i=1}^{n} a_{i}=\sum\limits_{i=1+k}^{n+k} a_{i-k}

本例的通项公式是常数3,没有下标,所以只变换求和上下限

\sum\limits_{i=0}^{\lfloor\frac{n-1}{2}\rfloor} 3=\sum\limits_{i=0+1}^{\lfloor\frac{n-1}{2}\rfloor+1 } 3=\sum\limits_{i=1}^{\lfloor\frac{n-1}{2}\rfloor+1 } 3=\overbrace{3+\cdots + 3}^{\lfloor\frac{n-1}{2}\rfloor+1} =3\times (\lfloor\frac{n-1}{2}\rfloor+1)

在变换求和上下限后,也可根据求和式的数乘规则,提出常数再计算

\sum\limits_{i=1}^{\lfloor\frac{n-1}{2}\rfloor+1 } 3 =3\sum\limits_{i=1}^{\lfloor\frac{n-1}{2}\rfloor+1 } 1 =3\times (\overbrace{1+\cdots + 1}^{\lfloor\frac{n-1}{2}\rfloor+1}) =3\times (\lfloor\frac{n-1}{2}\rfloor+1)

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

  

  

(三)平方阶

【例6.1】平方阶 O(n^{2})

int n,i,j;
int a=0;
scanf("%d", &n);
for(i=0; i<n; i++)
{for(j=0; j<i; j++){a++;}
}

本例与【例3】嵌套相关循环 略有不同,循环变量 i , j 不是从1开始,而是从0开始;循环条件不是<=,而是<

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\begin{matrix} \sum\limits_{i=0}^{n-1} \sum\limits_{j=0}^{i-1} 1=\sum\limits_{i=0}^{n-1} \sum\limits_{j=1}^{i} 1=\sum\limits_{i=0}^{n-1} i=0+1+2+\cdots +(n-1)=\frac{n(n-1)}{2} \end{matrix}

3.时间复杂度T(n)=O(n^{2})

与【例3】嵌套相关循环 同理,内外层循环相关,不能分开计算

因为外层循环条件是 i<n ,所以 i 能取的最大值(即求和上限)是:n-1

因为内层循环条件是 j<i ,所以 j 能取的最大值(即求和上限)是:i-1

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{i=0}^{n-1} \sum\limits_{j=0}^{i-1} 1

双重求和可以加括号以区分

\sum\limits_{i=0}^{n-1} \sum\limits_{j=0}^{i-1} 1=\sum\limits_{i=0}^{n-1} (\sum\limits_{j=0}^{i-1} 1)

可以类比复合函数,先计算括号内的

根据求和式上下限变换规则

\sum\limits_{j=0}^{i-1} 1=\sum\limits_{j=0+1}^{i-1+1} 1=\sum\limits_{j=1}^{i} 1

将其展开得

\sum\limits_{j=1}^{i}1 =\overbrace{1+\cdots +1}^{i} = i

将括号内的求和式替换为计算结果,双重求和式变为  

\sum\limits_{i=0}^{n-1} (\sum\limits_{j=0}^{i-1} 1)=\sum\limits_{i=0}^{n-1} i

这里比较特殊,通项公式就是求和变量: a_{i}=i

\sum\limits_{i=0}^{n-1} i=0+1+2+\cdots +(n-1)

求和变量 i 从 0 到 n-1,则通项公式也是从 0 到 n-1,求和式整体即从0累加到n-1

此时变为等差数列求和,代入等差数列求和公式 

S_{n}=\frac{n(a_{1}+a_{n})}{2} 

得到语句频度

\sum\limits_{i=0}^{n-1} i=0+1+2+\cdots +(n-1)=\frac{n(n-1)}{2}

将其展开得

\frac{n(n-1)}{2}=\frac{1}{2}n^{2}-\frac{1}{2}n

  

除了上述解法,其实还可以使用求和式的上下限变换规则

求和上下限变换(+1),通项下标也对应变换(-1)

这里比较特殊,因为通项公式 a_{i}=i ,所以变换为 a_{i-1}=i-1

\sum\limits_{i=0}^{n-1} i =\sum\limits_{i=0+1}^{n-1+1} (i-1) =\sum\limits_{i=1}^{n} (i-1)

再使用求和式的加法规则(减法同理)进行拆分

\sum\limits_{i=1}^{n} (i-1) =\sum\limits_{i=1}^{n} i-\sum\limits_{i=1}^{n} 1

同样可以得到语句频度

\sum\limits_{i=1}^{n} i-\sum\limits_{i=1}^{n} 1=\frac{n(n+1)}{2}-n=\frac{n^{2}+n-2n}{2}=\frac{n^{2}-n}{2}=\frac{n(n-1)}{2}

将其展开得

\frac{n(n-1)}{2}=\frac{1}{2}n^{2}-\frac{1}{2}n

省略系数和无关项后,数量级为 n^{2},则时间复杂度为 O(n^{2})

  

  

【例6.2】平方阶 O(n^{2}) ——冒泡排序(最坏情况)

int n,i,j,t,a[100];
scanf("%d", &n);
for(i=0; i<n; i++)
{scanf("%d", &a[i]);
}
for(i=0; i<n-1; i++)
{for(j=0; j<n-1-i; j++){if(a[j]>a[j+1]){t=a[j];a[j]=a[j+1];a[j+1]=t;}}
}
for(i=0; i<n; i++)
{printf("%d ", a[i]);
}

计算时间复杂度

1.基本语句

t=a[j];

a[j]=a[j+1];

a[j+1]=t;

2.基本语句语句频度

\begin{matrix} \sum\limits_{i=0}^{n-2} \sum\limits_{j=0}^{n-2-i} 3=\sum\limits_{i=0}^{n-2} \sum\limits_{j=1}^{n-1-i} 3= \sum\limits_{i=0}^{n-2}3 (n-1-i)= 3\sum\limits_{i=0}^{n-2} (n-1-i)=3[\sum\limits_{i=0}^{n-2} (n-1)-\sum\limits_{i=0}^{n-2} i]=\frac{3}{2}n(n-1) \end{matrix}

3.时间复杂度T(n)=O(n^{2})

代码中有三个for循环

第1个是一层for循环,输入数据,执行n次

第2个是二层for循环,冒泡排序,外层循环执行n-1次,且内层循环次数与 i 相关,所以执行次数肯定>n

第3个是一层for循环,输出数据,执行n次

假设当前冒泡排序为最坏情况(逆序),每次循环都会交换变量

所以基本语句是二层for循环体中的3条交换语句,则求和表达式(通项公式)是 3

  

基本语句所在的二层for循环是嵌套相关循环,内外层循环相关,不能分开计算

因为外层循环条件是 i<n-1 ,所以 i 能取的最大值(即求和上限)是:(n-1)-1=n-2

因为内层循环条件是 j<n-1-i ,所以 j 能取的最大值(即求和上限)是:(n-1-i)-1=n-2-i

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{i=0}^{n-2} \sum\limits_{j=0}^{n-2-i} 3

双重求和可以加括号以区分

\sum\limits_{i=0}^{n-2} \sum\limits_{j=0}^{n-2-i} 3=\sum\limits_{i=0}^{n-2} (\sum\limits_{j=0}^{n-2-i} 3)

可以类比复合函数,先计算括号内的

根据求和式的上下限变换规则

\sum\limits_{j=0}^{n-2-i} 3=\sum\limits_{j=0+1}^{n-2-i+1} 3=\sum\limits_{j=1}^{n-1-i} 3

将其展开得

\sum\limits_{j=1}^{n-1-i} 3 =\overbrace{3+\cdots+3}^{n-1-i} =3(n-1-i)

(这里只不过是把求和上限变成 n-1-i,与前面本质相同,就是 n-1-i 个1相加)

将括号内的求和式替换为计算结果,双重求和变为  

\sum\limits_{i=0}^{n-2} (\sum\limits_{j=0}^{n-2-i} 3)=\sum\limits_{i=0}^{n-2} [3(n-1-i)]

根据求和式的数乘规则,将常数提出

\sum\limits_{i=0}^{n-2} [3(n-1-i)]=3\sum\limits_{i=0}^{n-2} (n-1-i)

根据求和式的加法规则(减法同理),进行拆分

3\sum\limits_{i=0}^{n-2} (n-1-i)=3[\sum\limits_{i=0}^{n-2} (n-1)-\sum\limits_{i=0}^{n-2} i]

将两个求和式分别计算:

第一个求和式

根据求和式的上下限变换规则,通项公式(n-1)与求和变量 i 无关,可以看作常数,所以只变换求和上下限

\sum\limits_{i=0}^{n-2} (n-1) =\sum\limits_{i=0+1}^{n-2+1} (n-1)=\sum\limits_{i=1}^{n-1} (n-1)

将其展开得

\sum\limits_{i=1}^{n-1} (n-1)=\overbrace{(n-1)+\cdots+(n-1)}^{n-1}=(n-1)(n-1)

第二个求和式

将其展开得

\sum\limits_{i=0}^{n-2} i=0+\cdots+(n-2)=\frac{(n-1)(0+n-2)}{2}=\frac{(n-1)(n-2)}{2}

将两式结果代入得到语句频度

3[\sum\limits_{i=1}^{n-1} (n-1)-\sum\limits_{i=0}^{n-2} i]\\[1.5ex] =3[(n-1)(n-1)-\frac{(n-1)(n-2)}{2}]\\[1.5ex] =\frac{3}{2}[2(n-1)(n-1)-(n-1)(n-2)]\\[1.5ex] =\frac{3}{2}(n-1)[2(n-1)-(n-2)]\\[1.5ex] =\frac{3}{2}(n-1)(2n-2-n+2)\\[1.5ex] =\frac{3}{2}(n-1)n\\[1.5ex] =\frac{3}{2}n(n-1)

将其展开得

\frac{3}{2}n(n-1)=\frac{3}{2}n^{2}-\frac{3}{2}n

省略系数和无关项后,数量级为 n^{2},则时间复杂度为 O(n^{2})

  

  

【例6.3】O(n\times m)

int n,m,i,j,a[10][10];
scanf("%d%d", &n, &m);
for(i=0; i<n; i++)
{for(j=0; j<m; j++) {a[i][j]=i*m+j;}
}

注意,本例的问题规模由n和m共同决定

  

计算时间复杂度

1.基本语句:a[i][j]=i*m+j;

2.基本语句语句频度\Large \sum\limits_{i=0}^{n-1}1 \times \sum\limits_{j=0}^{m-1}1=\Large \sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{m}1=n \times m

3.时间复杂度T(n)=O(n\times m )

嵌套无关循环,所以内外层循环互不干扰,语句频度独立计算

因为外层循环条件是 i<n ,所以 i 能取的最大值(即求和上限)是:n-1

而内层循环可以看作是外层循环体的1条基本语句,则求和表达式(通项公式)是1

所以外层循环的语句频度为(上下限变换规则)

\sum\limits_{i=0}^{n-1}1=\sum\limits_{i=0+1}^{n-1+1}1=\sum\limits_{i=1}^{n}1 = n

  

因为内层循环条件是 j<m ,所以 j 能取的最大值(即求和上限)是:m-1

内层循环体只有1条基本语句:a[i][j]=i*m+j;  则求和表达式(通项公式)是1

所以内层循环的语句频度为(上下限变换规则)

\sum\limits_{j=0}^{m-1}1=\sum\limits_{j=0+1}^{m-1+1}1=\sum\limits_{j=1}^{m}1 = m

  

只有当内外层循环都结束时,算法才结束

(即算法结束需要分内层循环、外层两循环个步骤)

根据统计学的乘法原理,需要把二者相乘得语句频度

\Large \sum\limits_{i=0}^{n-1}1 \times \sum\limits_{j=0}^{m-1}1=\Large \sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{m}1=n \times m

因为问题规模由n和m共同决定,不能合并,所以数量级为 n\times m时间复杂度为 O(n\times m)

    

(四)立方阶

【例7.1】立方阶 O(n^{3}) 

int n,i,j,k,a=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{for(j=1; j<=n; j++){for(k=1; k<=j; k++){a++;}}
}

可以看到,第二层循环条件是 j<=n,与第一层循环变量 i 无关,j 从1到 n,执行 n 次

无论第一层循环变量 i 取何值,第二层循环都会执行n次

   

可以看到,第三层循环条件是 k<=j,与第二层循环变量 j 相关,k 从1到 j,执行 j 次

当第二层循环变量 j 的值发生改变时,第三层循环的执行次数也会随之改变

  

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n} \sum\limits_{k=1}^{j} 1 =n \times \sum\limits_{j=1}^{n}j =n \times \frac{n(n+1)}{2} =\frac{n^{2}(n+1)}{2}

3.时间复杂度T(n)=O(n^{3})

  

第一层是嵌套无关循环语句频度独立计算

因为第一层循环条件是 i<=n ,所以 i 能取的最大值(即求和上限)是:n

内层循环可以看作是外层循环体的1条基本语句,所以求和表达式(通项公式)是1

所以第一层循环的求和式为

\sum\limits_{i=1}^{n}1=\overbrace{1+\cdots +1}^{n}=n

  

第二、三层是嵌套相关循环,不能分开计算

因为第二层循环条件是 j<=n ,所以 j 能取的最大值(即求和上限)是:n

因为第三层循环条件是 k<=j ,所以 k 能取的最大值(即求和上限)是:j

内层循环体只有1条基本语句,所以求和表达式(通项公式)是1

所以第二、三层循环的求和式为

\sum\limits_{j=1}^{n} \sum\limits_{k=1}^{j} 1

双重求和可以加括号以区分

\sum\limits_{j=1}^{n} \sum\limits_{k=1}^{j} 1 =\sum\limits_{j=1}^{n} (\sum\limits_{k=1}^{j} 1)

可以类比复合函数,先计算括号内的

\sum\limits_{k=1}^{j}1 =\overbrace{1+\cdots +1}^{j} = j

将括号内的求和式替换为计算结果

\sum\limits_{j=1}^{n} (\sum\limits_{k=1}^{j} 1)=\sum\limits_{j=1}^{n} j

将其展开得到

\sum\limits_{j=1}^{n}j =1+\cdots +n

这里比较特殊,通项公式就是求和变量: a_{j}=j

求和变量 j 从 1 到 n,则通项公式也是从 1 到 n,求和式整体即从1累加到 n

此时变为等差数列求和,代入等差数列求和公式 

S_{n}=\frac{n(a_{1}+a_{n})}{2} 

得到

\sum \limits_{j=1}^{n}j =1+\cdots +n = \frac{n(n+1)}{2}

  

只有当第一层和第二、三层循环都结束时,算法才结束

(即算法结束需要分第一层循环和第二、三层循环两个步骤)

根据统计学的乘法原理,需要把二者相乘得语句频度

\sum\limits_{i=1}^{n}1 \times \sum\limits_{j=1}^{n} \sum\limits_{k=1}^{j} 1\\[1.5ex] =n \times \sum\limits_{j=1}^{n} (\sum\limits_{k=1}^{j} 1)\\[1.5ex] =n \times \sum\limits_{j=1}^{n}j\\[1.5ex] =n \times \frac{n(n+1)}{2}\\[1.5ex] =\frac{n^{2}(n+1)}{2}

将其展开得

\frac{n^{2}(n+1)}{2}=\frac{1}{2}n^{3} + \frac{1}{2}n^2

省略系数和无关项后,数量级为 n^{3},则时间复杂度为 O(n^{3})

  

  

【例7.2】立方阶 O(n^{3}) 

int n,i,j,k,a=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{for(j=1; j<=i; j++){for(k=1; k<=j; k++){a++;}}
}

可以看到,第二层循环条件是 j<=i,与第一层循环变量 i 相关,j 从1到 i,执行 i 次

当第一层循环变量 i 的值发生改变时,第二层循环的执行次数也会随之改变

   

可以看到,第三层循环条件是 k<=j,与第二层循环变量 j 相关,k 从1到 j,执行 j 次

当第二层循环变量 j 的值发生改变时,第三层循环的执行次数也会随之改变

  

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} \sum\limits_{k=1}^{j} 1 =\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} j =\sum\limits_{i=1}^{n} (\frac{i^{2}+i}{2}) = \frac{\sum\limits_{i=1}^{n}i^{2}+\sum\limits_{i=1}^{n}i}{2} = \frac{n(n+1)(n+2)}{6}

3.时间复杂度T(n)=O(n^{3})

嵌套相关循环,一、二、三层循环相关,不能分开计算

因为第一层循环条件是 i<=n ,所以 i 能取的最大值(即求和上限)是:n

因为第二层循环条件是 j<=i ,所以 j 能取的最大值(即求和上限)是:i

因为第三层循环条件是 k<=j ,所以 k 能取的最大值(即求和上限)是:j

第三层循环体只有1条基本语句,所以求和表达式(通项公式)是1

三层循环(三重循环)计算语句频度需要写成三重求和

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} \sum\limits_{k=1}^{j} 1

三重求和可以加括号以区分

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} \sum\limits_{k=1}^{j} 1 =\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} (\sum\limits_{k=1}^{j} 1) 

可以类比复合函数,先计算括号内的

\sum\limits_{k=1}^{j}1 =\overbrace{1+\cdots +1}^{j} = j

将括号内的求和式替换为计算结果,三重求和变为双重求和  

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} (\sum\limits_{k=1}^{j} 1)= \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} j

双重求和也可以加括号以区分

\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i} j =\sum\limits_{i=1}^{n} (\sum\limits_{j=1}^{i} j)

可以类比复合函数,先计算括号内的

\sum\limits_{j=1}^{i}j =1+\cdots +i

这里比较特殊,通项公式就是求和变量: a_{j}=j

求和变量 j 从 1 到 i,则通项公式也是从 1 到 i,求和式整体即从1累加到 i

此时变为等差数列求和,代入等差数列求和公式 

S_{n}=\frac{n(a_{1}+a_{n})}{2} 

得到

\sum\limits_{j=1}^{i}j =1+\cdots +i = \frac{i(i+1)}{2}

将括号内的求和式替换为计算结果,双重求和变为

\sum\limits_{i=1}^{n} (\sum\limits_{j=1}^{i} j) =\sum\limits_{i=1}^{n} [\frac{i(i+1)}{2}]

将分子展开得

\sum\limits_{i=1}^{n} [\frac{i(i+1)}{2}] =\sum\limits_{i=1}^{n} (\frac{i^{2}+i}{2})

根据求和式的线性规则(数乘规则、加法规则)

对多项式求和就是对其中每一项分别应用求和运算

\sum\limits_{i=1}^{n} (\frac{i^{2}+i}{2}) = \frac{\sum\limits_{i=1}^{n}i^{2}+\sum\limits_{i=1}^{n}i}{2}

这两个求和式的值都是已知的

第一个求和式

\sum\limits_{i=1}^{n}i^{2}=\frac{n(n+1)(2n+1)}{6}

如果想了解如何推导出 i^{2} 求和的值,可以看我的这篇文章:《 求和式及其运算规则

第二个求和式

\sum\limits_{i=1}^{n}i=1+\cdots +n=\frac{n(n+1)}{2}

 

将两式结果代入得到语句频度

\frac{\sum\limits_{i=1}^{n}i^{2}+\sum\limits_{i=1}^{n}i}{2} \\[1.5ex] = \frac{\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2}}{2} \\[1.5ex] = \frac{1}{2} [\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2}]\\[1.5ex] = \frac{1}{2} [\frac{n(n+1)(2n+1)}{6}+\frac{3n(n+1)}{6}]\\[1.5ex] = \frac{1}{2} [\frac{n(n+1)(2n+1+3)}{6}] \\[1.5ex] = \frac{1}{2} [\frac{n(n+1)(2n+4)}{6}] \\[1.5ex] = \frac{1}{2} [\frac{n(n+1)2(n+2)}{6}] \\[1.5ex] = \frac{n(n+1)(n+2)}{6}

将其展开得

\frac{n(n+1)(n+2)}{6}=\frac{1}{6}n^{3} + \frac{1}{2}n^2 +\frac{1}{3}n

省略系数和无关项后,数量级为 n^{3},则时间复杂度为 O(n^{3})

  

  

(五)对数阶

【例8.1】对数阶 O(log_{2}n) 

int n,i,a=0;
scanf("%d", &n);
for(i=1; i<=n; i*=2)
{a++;
}

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(n)}+1\rfloor}=\lfloor log_{2}{(n)}+1\rfloor

3.时间复杂度T(n)=O(log_{2}{n})

因为本例的循环变量自增不是以往的 i++,而是 i*=2

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 i 的变化规律,将其当作数列

循环变量初始化:i=1,则首项 a_{1}=1

循环变量自增:i*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 i 随着通项公式改变 

i=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] i=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] i=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] i=a_{x}=2^{x-1} \\[1.5ex]

所以最后一次循环(第x次循环)时

i=2^{x-1}

(注:此处及后续提到的 "最后一次循环" 均是指能进循环体的 "最后一次循环")

又因为循环条件是 i \leqslant n

所以最后一次循环时 i=n,则

i=2^{x-1}= n

等式两边同时取对数

x-1=log_{2}{(n)}

移项,得到循环执行次数x的值

x=log_{2}{(n)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor log_{2}{(n)}+1\rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor log_{2}{(n)}+1\rfloor,代表循环执行 \lfloor log_{2}{(n)}+1\rfloor

语句频度为:

\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(n)}+1\rfloor}=\lfloor log_{2}{(n)}+1\rfloor

省略系数和无关项后,数量级为 log_{2}{n},则时间复杂度为 O(log_{2}{n})

  

  

【例8.2】对数阶 O(log_{2}n) 

int n,i,a=0;
scanf("%d", &n);
for(i=2; i<n; i*=2)
{a++;
}

本例是对【例8.1】稍作修改,循环变量初始化:i=2,循环条件:i<n

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lceil log_{2}(n) \rceil-1} 1=\overbrace{1+\cdots +1}^{\lceil log_{2}(n) \rceil-1}=\lceil log_{2}(n) \rceil-1

3.时间复杂度T(n)=O(log_{2}{n})

  

因为本例的循环变量自增不是以往的 i++,而是 i*=2

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 i 的变化规律,将其当作数列

循环变量初始化:i=2,则首项 a_{1}=2

循环变量自增:i*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=2\times 2^{n-1}=2^{n}

则循环变量 i 随着通项公式改变 

i=a_{1}=2^{1} \\[1.5ex] i=a_{2}=2^{2} \\[1.5ex] i=a_{3}=2^{3} \\[1.5ex] \cdots \\[1.5ex] i=a_{x}=2^{x}

所以最后一次循环(第x次循环)时

i=2^{x}

又因为循环条件是 i<n

不等式两边不都是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 i=2^{x} 代入不等式,得

i=2^{x}<n

不等式两边同时取对数,得到循环执行次数x的范围

x<log_{2}(n)

但是循环执行次数只能是整数,所以x的最大值是

x_{max}=\lceil log_{2}(n) \rceil-1

到这你肯定很疑惑,这个最大值是怎么来的

首先说明一下, \lceil \ \rceil 这个符号代表向上取整

向上取整,如果不是整数,则让整数部分加1,忽略小数部分

向上取整,如果是整数,则仍是自身

\lceil 3.9 \rceil=4 ,向上取整得 4

\lceil 16.183 \rceil=17 ,向上取整得17

\lceil 7 \rceil=7 ,整数向上取整,仍是自身

    

下面解释最大值如何得出

将对数 log_{2}(n) 作为一个整体

设其整数部分为m,小数部分为f,则 

log_{2}(n)=m+f  


当小数部分不存在时(即 f=0),则 m+f=m+0=m

因为 log_{2}(n)=m+f,将对数整体代换得 log_{2}(n)=m

前面已推导出x的范围为 x<log_{2}(n),将上式代入得 x<m

此时不等式两边都是整数,所以当小数部分不存在时,x的最大值 x_{max}=m-1


当小数部分存在时(即 0<f<1),则 m<(m+f)<(m+1)

因为 log_{2}(n)=m+f,将对数整体代换得 m<log_{2}(n)<(m+1)

前面已推导出x的范围为 x<log_{2}(n),不等式放缩得 x<(m+1)

此时不等式两边都是整数,所以当小数部分存在时,x的最大值 x_{max}=(m+1)-1=m


可以用 \lceil \ \rceil 向上取整将两种情况统一起来


当小数部分不存在时(即 f=0),前面整体代换已得 log_{2}(n)=m

m是整数,所以向上取整后仍为m,即 \lceil log_{2}(n)\rceil=m

代入小数部分不存在时,x的最大值 x_{max}=m-1=\lceil log_{2}(n)\rceil-1


当小数部分存在时(即 0<f<1),前面整体代换已得 m<log_{2}(n)<(m+1)

m是整数,则m+1也是整数,所以向上取整后为m+1,即 \lceil log_{2}(n)\rceil=m+1

代入小数部分存在时,x的最大值 x_{max}=m=(m+1)-1=\lceil log_{2}(n)\rceil-1


所以最终统一为

x_{max}=\lceil log_{2}(n) \rceil-1

  

如果设 f(n)=log_{2}(n),可得出以下结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

这个可以作为普遍结论,因为推导的全程都是把 log_{2}(n) 当作一个整体

  


顺便提一下,这个结论对于【例5】线性阶 同样适用

再把【例5】的代码贴过来

int n,i,t,a[100];
scanf("%d", &n);
for(i=0; i<n/2; i++) 
{ t=a[i]; a[i]=a[n-1-i]; a[n-1-i]=t; 
}

根据上面的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入【例5】:

i 是整数,不等式关系为 i<\frac{n}{2},则整数 i 的最大值 i_{max}=\lceil \frac{n}{2} \rceil-1

所以【例5】的语句频度也可以表示为

\sum\limits_{i=0}^{\lceil \frac{n}{2} \rceil-1} 3 =\sum\limits_{i=0+1}^{\lceil \frac{n}{2} \rceil-1 +1} 3 =\sum\limits_{i=1}^{\lceil \frac{n}{2} \rceil} 3 =\overbrace{3+\cdots + 3}^{\lceil \frac{n}{2} \rceil} =3\times \lceil \frac{n}{2} \rceil

省略系数后,数量级为 n,所以时间复杂度仍为 O(n)

  

将n=1到10的计算结果列成表格,也能看出本式是符合的


  

回到本题,现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lceil log_{2}(n) \rceil-1,代表循环执行 \lceil log_{2}(n) \rceil-1

语句频度为:

\sum\limits_{x=1}^{\lceil log_{2}(n) \rceil-1} 1=\overbrace{1+\cdots +1}^{\lceil log_{2}(n) \rceil-1}=\lceil log_{2}(n) \rceil-1

省略系数和无关项后,数量级为 log_{2}{n},则时间复杂度为 O(log_{2}{n})

  

  

【例8.3】对数阶 O(log_{3}{n})=O(log_{2}{n})=O(log\,{n})

int n,i,a=0;
scanf("%d", &n);
for(i=2; i<=n; i*=3)
{a++;
}

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor}=\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor

3.时间复杂度T(n)=O(log_{3}{n})=O(log_{2}{n})=O(log\,{n})

   

因为本例的循环变量自增不是以往的 i++,而是 i*=3

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 i 的变化规律,将其当作数列

循环变量初始化:i=2,则首项 a_{1}=2

循环变量自增:i*=3,则公比 q=3 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=2\times 3^{n-1}

则循环变量 i 随着通项公式改变 

i=a_{1}=2\times 3^{1-1}=2\times 3^{0}=2\times 1=2 \\[1.5ex] i=a_{2}=2\times 3^{2-1}=2\times 3^{1}=2\times 3=6 \\[1.5ex] i=a_{3}=2\times 3^{3-1}=2\times 3^{2}=2\times 9=18 \\[1.5ex] \cdots \\[1.5ex] i=a_{x}=2\times 3^{x-1}

所以最后一次循环(第x次循环)时

i=a_{x}=2\times 3^{x-1}

又因为循环条件是 i \leqslant n

所以最后一次循环时 i=n,则

i=2\times 3^{x-1} = n

等式两边同时除以2

3^{x-1} = \frac{n}{2}

等式两边同时取对数

x-1=log_{3}{(\frac{n}{2})}

根据对数的减法法则

log_{a}{(\frac{M}{N})}=log_{a}{(M)}-log_{a}{(N)}

等式右边可以拆分为

log_{3}{(\frac{n}{2})}=log_{3}{(n)}-log_{3}{(2)}

代入原式得

x-1=log_{3}{(n)}-log_{3}{(2)}

移项,得到循环执行次数x的值

x=log_{3}{(n)}-log_{3}{(2)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor,代表循环执行 \lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor

语句频度为:

\sum\limits_{x=1}^{\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor}=\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor

其中 log_{3}{(2)} 是常数

省略系数和无关项后,数量级为 log_{3}{n},则时间复杂度为 O(log_{3}{n})

  

或根据对数的换底公式

log_{a}{(b)}=\frac{log_{c}{(b)}}{log_{c}{(a)}}

上面的对数可以改写为

log_{3}{(n)}=\frac{log_{2}{(n)}}{log_{2}{(3)}}=\frac{1}{log_{2}{(3)}}log_{2}{(n)}

log_{3}{(2)}=\frac{log_{2}{(2)}}{log_{2}{(3)}}=\frac{1}{log_{2}{(3)}}

语句频度改写为

\lfloor log_{3}{(n)}-log_{3}{(2)}+1 \rfloor=\lfloor \frac{1}{log_{2}{(3)}}log_{2}{(n)}-\frac{1}{log_{2}{(3)}}+1 \rfloor  

其中 \frac{1}{log_{2}{(3)}} 是常数

省略系数和无关项后,数量级为 log_{2}{n},则时间复杂度为 O(log_{2}{n})

   


因为可以换底,所以 任意底数(k)的对数 都可以转化为 "常数 × 底数为2的对数"

log_{k}(n)=\frac{log_{2}(n)}{log_{2}(k)}=\frac{1}{log_{2}(k)}log_{2}(n)

(注:n是自变量,k是常数)

因为计算时间复杂度会省略系数,所以对数阶的时间复杂度均相同(即底数可以省略)

O(log_{k}{n})=O(log_{2}{n})=O(log\,{n})


  

(六)线性对数阶

【例9】线性对数阶 O(nlog_{2}n)=O(nlog\,n) 

int n,i,j,a=0;
scanf("%d", &n);
for(i=1; i<=n; i++)
{for(j=1; j<=n; j*=2){a++;}
}

本例其实就是将【例2】嵌套无关循环 和【例8.1】对数阶 结合起来

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{i=1}^{n}1 \times \sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=n\times \lfloor log_{2}{(n)}+1\rfloor

3.时间复杂度T(n)=O(nlog_{2}{n})=O(nlog\,n)

嵌套无关循环,所以内外层循环互不干扰,语句频度独立计算

因为外层循环条件是 i<=n ,所以 i 能取的最大值(即求和上限)是:n

而内层循环可以看作是外层循环体的1条基本语句,则求和表达式(通项公式)是1

所以外层循环的语句频度

\sum\limits_{i=1}^{n}1 =\overbrace{1+\cdots +1}^{n} = n

因为内层循环变量自增不是以往的 j++,而是 j*=2

所以循环变量 j 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 j 的变化规律,将其当作数列

循环变量初始化:j=1,则首项 a_{1}=1

循环变量自增:j*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 j 随着通项公式改变 

j=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] j=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] j=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] j=a_{x}=2^{x-1} \\[1.5ex]

所以最后一次循环(第x次循环)时

j=2^{x-1}

又因为循环条件是 j \leqslant n

所以最后一次循环时 j=n,则

j=2^{x-1}= n

等式两边同时取对数所以求和

x-1=log_{2}{(n)}

移项,得到循环执行次数x的值

x=log_{2}{(n)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor log_{2}{(n)}+1\rfloor

  

现在就可以列求和式计算了

内层循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor log_{2}{(n)}+1\rfloor,代表循环执行 \lfloor log_{2}{(n)}+1\rfloor

所以内层循环的语句频度为:

\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(n)}+1\rfloor}=\lfloor log_{2}{(n)}+1\rfloor

根据统计学乘法原理,两式相乘得

\sum\limits_{i=1}^{n}1 \times \sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=n\times \lfloor log_{2}{(n)}+1\rfloor

省略系数和无关项后,数量级为 nlog_{2}{n},则时间复杂度为 O(nlog_{2}{n})=O(nlog\,{n})

  

  

(七)根号阶

【例10.1】根号阶 O(\sqrt{n})=O(n^{\frac{1}{2}})

int n,i,a=0;
scanf("%d", &n);
for(i=1; i*i<=n; i++)
{a++;
}

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

3.时间复杂度T(n)=O(\sqrt{n})=O(n^{\frac{1}{2}})

 

因为本例的循环条件不是以往的 i<=n,而是 i*i<=n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 i*i 的变化规律,将其当作数列

循环变量初始化:i=1

循环变量自增:i++

将循环变量 i 的值依次代入循环条件 i*i,探寻数列规律

i*i=a_{1}=1\times 1=1^{2} \\[1.5ex] i*i=a_{2}=2\times 2=2^{2} \\[1.5ex] i*i=a_{3}=3\times 3=3^{2} \\[1.5ex] \cdots \\[1.5ex] i*i=a_{x}=x\times x=x^{2} \\[1.5ex]

所以最后一次循环(第x次循环)时

i*i=x^{2}

又因为循环条件是 i*i\leqslant n

所以最后一次循环时 i*i=n,则

i*i=x^{2}= n

等式两边同时开平方,得到循环执行次数x的值

x=\sqrt{n}

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor \sqrt{n} \rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor \sqrt{n} \rfloor,代表循环执行 \lfloor \sqrt{n} \rfloor

语句频度为:

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

省略系数和无关项后,数量级为 \sqrt{n},则时间复杂度为 O(\sqrt{n})=O(n^{\frac{1}{2}})

  

【例10.2】根号阶 O(\sqrt{n})=O(n^{\frac{1}{2}})

int n,i,a=0;
scanf("%d", &n);
for(i=2; i*i<n; i++)
{a++;
}

本例是对【例10.1】稍作修改,循环变量初始化:i=2,循环条件:i*i<n

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil-2} 1=\overbrace{1+\cdots +1}^{\lceil \sqrt{n}\ \rceil-2}=\lceil \sqrt{n}\ \rceil-2

3.时间复杂度T(n)=O(\sqrt{n})=O(n^{\frac{1}{2}})

 

因为本例的循环条件不是以往的 i<n,而是 i*i<n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 i*i 的变化规律,将其当作数列

循环变量初始化:i=2

循环变量自增:i++

将循环变量 i 的值依次代入循环条件 i*i,探寻数列规律

i*i=a_{1}=2\times 2=2^{2} \\[1.5ex] i*i=a_{2}=3\times 3=3^{2} \\[1.5ex] i*i=a_{3}=4\times 4=4^{2} \\[1.5ex] \cdots \\[1.5ex] i*i=a_{x}=(x+1)\times (x+1)=(x+1)^{2} \\[1.5ex]

所以最后一次循环(第x次循环)时

i*i=(x+1)^{2}

又因为循环条件是 i*i < n

不等式两边不都是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 i*i=(x+1)^{2} 代入不等式,得

i*i=(x+1)^{2}<n

不等式两边同时开平方

x+1<\sqrt{n}

移项,得到循环执行次数x的范围

x<\sqrt{n}-1

  

根据【例8.2】中的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入本题:

x是整数,不等式关系为 x<\sqrt{n}-1 ,则整数x的最大值 x_{max}=\lceil \sqrt{n}-1 \rceil-1=\lceil \sqrt{n}\ \rceil-2

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lceil \sqrt{n}\ \rceil-2,代表循环执行 \lceil \sqrt{n}\ \rceil-2

语句频度为:

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil-2} 1=\overbrace{1+\cdots +1}^{\lceil \sqrt{n}\ \rceil-2}=\lceil \sqrt{n}\ \rceil-2

省略系数和无关项后,数量级为 \sqrt{n},则时间复杂度为 O(\sqrt{n})=O(n^{\frac{1}{2}})

  

【例10.3】根号阶 O(\sqrt{n})=O(n^{\frac{1}{2}})

int n,i,a=0;
scanf("%d", &n);
for(i=1; i<=sqrt(n); i++)
{a++;
}

本例与【例10.1】 如出一辙

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{i=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

3.时间复杂度T(n)=O(\sqrt{n})=O(n^{\frac{1}{2}})

 

本例的循环条件不是以往的 i<=n,而是 i<=sqrt(n)

sqrt是平方根( square root )的缩写,sqrt(n)即为\sqrt{n}

所以循环条件可写为 i\leqslant \sqrt{n}

按理说 i 能取的最大值应该是\sqrt{n}

但是\sqrt{n} 不一定是整数,所以需要向下取整 \lfloor \sqrt{n} \rfloor

则最后一次循环时 i=\lfloor \sqrt{n} \rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

语句频度为:

\sum\limits_{i=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

省略系数和无关项后,数量级为 \sqrt{n},则时间复杂度为 O(\sqrt{n})=O(n^{\frac{1}{2}})

  

【例10.4】根号阶 O(\sqrt[3]{n})=O(n^{\frac{1}{3}})

int n,i,a=0;
scanf("%d", &n);
for(i=1; i*i*i<=n; i++)
{a++;
}

计算时间复杂度

1.基本语句:a++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor \sqrt[3]{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt[3]{n} \rfloor}=\lfloor \sqrt[3]{n} \rfloor

3.时间复杂度T(n)=O(\sqrt[3]{n})=O(n^{\frac{1}{3}})

 

因为本例的循环条件不是以往的 i<=n,而是 i*i*i<=n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 i*i*i 的变化规律,将其当作数列

循环变量初始化:i=1

循环变量自增:i++

将循环变量 i 的值依次代入循环条件 i*i*i,探寻数列规律

i*i*i=a_{1}=1\times 1\times 1=1^{3} \\[1.5ex] i*i*i=a_{2}=2\times 2\times 2=2^{3} \\[1.5ex] i*i*i=a_{3}=3\times 3\times 3=3^{3} \\[1.5ex] \cdots \\[1.5ex] i*i*i=a_{x}=x\times x\times x=x^{3}\\[1.5ex]

所以最后一次循环(第x次循环)时

i*i*i=x^{3}

又因为循环条件是 i*i*i \leqslant n

所以最后一次循环时 i*i*i=n,则

i*i*i=x^{3}= n

等式两边同时开立方,得到循环执行次数x的值

x=\sqrt[3]{n}

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor \sqrt[3]{n} \rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor \sqrt[3]{n} \rfloor,代表循环执行 \lfloor \sqrt[3]{n} \rfloor

语句频度为:

\sum\limits_{x=1}^{\lfloor \sqrt[3]{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt[3]{n} \rfloor}=\lfloor \sqrt[3]{n} \rfloor

省略系数和无关项后,数量级为 \sqrt[3]{n},则时间复杂度为 O(\sqrt[3]{n})=O(n^{\frac{1}{3}})

  

(八)指数阶

【例11.1】指数阶 O(2^{n}) 

int f(int n)
{if(n==1) return 1;else return f(n-1)+f(n-1);
}

计算时间复杂度

1.基本语句

return 1;

return f(n-1)+f(n-1);

2.基本语句语句频度2^{n}-1

3.时间复杂度T(n)=O(2^{n})

 

递归函数无法按常规方式计算语句频度

但是可以转换思路,将递归过程模拟为二叉树

一个递归函数当作二叉树的一个节点

本例的函数比较特殊,f(n)刚好递归调用的是 两个f(n-1)

从二叉树的视角来看,就是该节点连接下一层的两个孩子

所以每个节点(不算叶子节点)都有两个孩子,最终得到满二叉树

例如:f(4) 的递归满二叉树(如下图)

每个节点(不算叶子节点)都有两个孩子,最终得到满二叉树

当递归到叶子节点 f(1) 时,n==1,返回1,不向下递归

  

再看函数体,无论进 if 还是进 else,都只会执行1条语句

所以递归函数内基本语句语句频度是1

因为是满二叉树,且每个节点(即递归函数)的语句频度是1

所以 语句频度之和 = 满二叉树的总节点数

如果了解满二叉树的性质,可以直接得出总节点数为 2^{n}-1

  

不了解也没关系,下面逐步推导

我们知道二叉树只有1个根节点

对于满二叉树,每个节点(不算叶子节点)都有两个孩子

那么上层节点数与下层节点数是2倍关系,将 {节点数} 当作数列

第1层:根节点,节点数为 a_{1}=1=2^{0}

第2层:节点数为 a_{2}=1\times 2=2=2^{1}

第3层:节点数为 a_{3}=2\times 2=4=2^{2}

第4层:节点数为 a_{4}=4\times 2=8=2^{3}

第5层:节点数为 a_{5}=8\times 2=16=2^{4}

……

第n层:节点数为 a_{n}=2^{n-1}

可以看出其为等比数列

首项 a_{1}=1,公比 q=2 

等比数列通项公式 a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

总节点数就是把第1层到第n层的所有节点数相加

其实相当于对 {节点数} 这个数列求和

代入等比数列求和公式

S_{n}=\frac{a_{1}(1-q^{n})}{1-q}=\frac{1\cdot(1-2^{n})}{1-2}=-(1-2^{n})=2^{n}-1 

(其中 n 既是问题规模,又是满二叉树的层数)

至此推导出语句频度之和 = 满二叉树的总节点数 =2^{n}-1

省略系数和无关项后,数量级为 2^{n},则时间复杂度为 O(2^{n})

  

  

【例11.2】指数阶 O(2^{n}) 

int f(int n)
{if(n==1 || n==2) return 1;else return f(n-2)+f(n-2);
}

计算时间复杂度

1.基本语句

return 1;

return f(n-2)+f(n-2);

2.基本语句语句频度2^{\lfloor \frac{n+1}{2} \rfloor }-1

3.时间复杂度T(n)=O(2^{n})

 

递归函数无法按常规方式计算语句频度

但是可以转换思路,将递归过程模拟为二叉树

一个递归函数当作二叉树的一个节点

本例与【例11.1】有所不同

【例11.1】的递归过程是

f(n)\rightarrow f(n-1)\rightarrow f(n-2)\rightarrow \cdots \rightarrow f(2) \rightarrow f(1)

而本例f(n)递归调用的是两个f(n-2),缺少了f(n-1)这一层

所以这颗二叉树是间隔一层向下递归的(仍是满二叉树,但层数不是n)

说一下奇偶数的性质

奇数 - 偶数 = 奇数, 则 奇数n - 2 = 奇数

偶数 - 偶数 = 偶数, 则 偶数n - 2 = 偶数

由于间隔递归,且n不确定是奇数还是偶数

所以最终可能在奇数 f(1)返回,也可能在偶数 f(2)返回

下面分情况讨论


当n为奇数时,最终在奇数 f(1)返回

f(n)递归过程如下

f(n)\rightarrow f(n-2)\rightarrow f(n-4)\rightarrow \cdots \rightarrow f(3) \rightarrow f(1)

从后往前看,当作数列

1,3,\cdots,(n-4),(n-2),n

则为等差数列

a_{1}=1,\ a_{2}=3,\ \cdots,\ a_{m-2}=(n-4),\ a_{m-1}=(n-2),\ a_{m}=n

首项 a_{1}=1,公差 d=2

等差数列通项公式 a_{m}=a_{1}+(m-1)d=1+(m-1)\cdot2=2m-1

代入最后一项 

a_{m}=2m-1=n

移项除以2求得m的值

m=\frac{n+1}{2}

数列从a_{1} 到 a_{m} 共m项,则递归过程有m层

所以奇数情况满二叉树的层数为 m=\frac{n+1}{2}

再看函数体,无论进 if 还是进 else,都只会执行1条语句

所以递归函数内基本语句语句频度是1

因为是满二叉树,且每个节点(即递归函数)的语句频度是1

所以奇数情况语句频度之和 = 满二叉树的总节点数=2^m-1=2^{\frac{n+1}{2}}-1


当n为偶数时,最终在偶数 f(2)返回

f(n)递归过程如下

f(n)\rightarrow f(n-2)\rightarrow f(n-4)\rightarrow \cdots \rightarrow f(4) \rightarrow f(2)

从后往前看,当作数列

2,4,\cdots,(n-4),(n-2),n

则为等差数列

a_{1}=2,\ a_{2}=4,\ \cdots,\ a_{m-2}=(n-4),\ a_{m-1}=(n-2),\ a_{m}=n

首项 a_{1}=2,公差 d=2

等差数列通项公式 a_{m}=a_{1}+(m-1)d=2+(m-1)\cdot2=2m

代入最后一项 

a_{m}=2m=n

除以2求得m的值

m=\frac{n}{2}

数列从a_{1} 到 a_{m} 共m项,则递归过程有m层

所以偶数情况满二叉树的层数为 m=\frac{n}{2}

再看函数体,无论进 if 还是进 else,都只会执行1条语句

所以递归函数内基本语句语句频度是1

因为是满二叉树,且每个节点(即递归函数)的语句频度是1

所以偶数情况语句频度之和 = 满二叉树的总节点数=2^m-1=2^{\frac{n}{2}}-1


类比【例5】的推导,可以使用 \lfloor \ \rfloor 向下取整将两式统一


先假设,总节点数公式可用向下取整统一为,奇数情况总节点数: 2^{\lfloor \frac{n+1}{2} \rfloor }-1

因为该式是从奇数情况推导出的,所以奇数肯定没问题

代入验证偶数情况,如果计算结果与偶数情况总节点数相同,说明可以统一

当n为偶数时,设 n=2x(x是整数)

代入上式得

2^{\lfloor \frac{n+1}{2} \rfloor }-1=2^{\lfloor \frac{2x+1}{2} \rfloor }-1

进一步化简得

2^{\lfloor \frac{2x+1}{2} \rfloor }-1\\[1.5ex] =2^{\lfloor \frac{2x}{2}+\frac{1}{2} \rfloor }-1\\[1.5ex] =2^{\lfloor x+\frac{1}{2} \rfloor }-1\\[1.5ex] =2^{x}-1\\[1.5ex]

因为向下取整时,只保留整数部分:x ,忽略小数部分: \frac{1}{2}

  

前面设 n=2x,反之 x=\frac{n}{2}

将其代入得 2^{x}-1=2^{\frac{n}{2}}-1

  

将计算结果与偶数情况总节点数 2^{\frac{n}{2}}-1 进行对照

可以看到,两式均为 2^{\frac{n}{2}}-1,则偶数情况也成立,所以可以统一为  2^{\lfloor \frac{n+1}{2} \rfloor }-1


再假设,总节点数公式可用向下取整统一为,偶数情况总节点数: 2^{\lfloor \frac{n}{2} \rfloor }-1

因为该式是从偶数情况推导出的,所以偶数肯定没问题

代入验证奇数情况,如果计算结果与奇数情况总节点数相同,说明可以统一

当n为奇数时,设 n=2x+1(x是整数)

代入上式得

2^{\lfloor \frac{n}{2} \rfloor }-1=2^{\lfloor \frac{2x+1}{2} \rfloor }-1

进一步化简得

2^{\lfloor \frac{2x+1}{2} \rfloor }-1\\[1.5ex] =2^{\lfloor \frac{2x}{2}+\frac{1}{2} \rfloor }-1\\[1.5ex] =2^{\lfloor x+\frac{1}{2} \rfloor }-1\\[1.5ex] =2^{x}-1\\[1.5ex]

因为向下取整时,只保留整数部分:x ,忽略小数部分: \frac{1}{2}

  

前面设 n=2x+1,反之 x=\frac{n-1}{2}

将其代入得 2^{x}-1=2^{\frac{n-1}{2}}-1

  

将计算结果与奇数情况总节点数 2^{\frac{n+1}{2}}-1 进行对照

可以看到,两式一个为 2^{\frac{n-1}{2}}-1,另一个为 2^{\frac{n+1}{2}}-1,则奇数情况不成立,所以不能统一


所以最终统一为:2^{\lfloor \frac{n+1}{2} \rfloor }-1

语句频度之和 = 满二叉树的总节点数 =2^{\lfloor \frac{n+1}{2} \rfloor }-1

省略系数和无关项后,数量级为 2^{n},则时间复杂度为 O(2^{n})


因为 2^{\lfloor \frac{n+1}{2} \rfloor } \approx 2^{\frac{n}{2}}

而 2^{\frac{n}{2}} 可以写成 (\sqrt{2})^{n}

所以有人认为时间复杂度应该是 O((\sqrt{2})^{n}) 

   

根据大O表示法在数学上的定义得

O表示法 O (f(n)) 给出的是时间复杂度的一个渐近上界 f(n)

所以上述两种均可渐近表示时间复杂度

不过为了方便统一理解,我还是倾向于 O(2^{n})

  

  

【例11.3】指数阶 O(2^{n}) ——斐波那契数列

int f(int n)
{if(n==1 || n==2) return 1;else return f(n-1)+f(n-2);
}

计算时间复杂度

1.基本语句

return 1;

return f(n-1)+f(n-2);

2.基本语句语句频度\frac{2}{\sqrt{5}} [(\frac{1+\sqrt{5}}{2})^{n}-(\frac{1-\sqrt{5}}{2})^{n}]-1

3.时间复杂度T(n)=O(2^{n})

 

由于左孩子 f(n-1)与右孩子 f(n-2)不在同一层

即左子树连续递归,右子树间隔递归

每个节点(即递归函数)的语句频度是1

所以依然是语句频度之和 = 二叉树的总节点数

虽然总节点数不好定量计算,但是我们可以定性分析

【例11.1】f(n)=f(n-1)+f(n-1)

【例11.2】f(n)=f(n-2)+f(n-2)

【例11.3】f(n)=f(n-1)+f(n-2)

可以分析出

f(n-2)+f(n-2)\leqslant f(n-1)+f(n-2)\leqslant f(n-1)+f(n-1)

所以得到时间复杂度关系

\begin{matrix} O(\ f(n-2)+f(n-2)\ )\leqslant O(\ f(n-1)+f(n-2)\ )\leqslant O(\ f(n-1)+f(n-1)\ ) \end{matrix}

前面已得出【例11.1】和【例11.2】的时间复杂度均为 O(2^{n}),即

O(f(n-1)+f(n-1))=O(2^{n})

O(f(n-2)+f(n-2))=O(2^{n})

时间复杂度关系变为(类似于夹逼定理)

O(2^{n})\leqslant O(\ f(n-1)+f(n-2)\ )\leqslant O(2^{n})

所以定性分析,斐波那契数列的时间复杂度也是 O(2^{n})


斐波那契数列递推公式: f(n)=f(n-1)+f(n-2)

通过特征方程推导可得通项公式: F_{n}=\frac{1}{\sqrt{5}} [(\frac{1+\sqrt{5}}{2})^{n}-(\frac{1-\sqrt{5}}{2})^{n}]

   

实际上,总节点数满足递推公式: f(n)=f(n-1)+f(n-2)+1  (即算上子树的根节点

通过特征方程推导可得通项公式: 2F_{n}-1=\frac{2}{\sqrt{5}} [(\frac{1+\sqrt{5}}{2})^{n}-(\frac{1-\sqrt{5}}{2})^{n}]-1

所以有人认为时间复杂度应该是 O((\frac{1+\sqrt{5}}{2})^{n})

  

根据大O表示法在数学上的定义得

O表示法 O (f(n)) 给出的是时间复杂度的一个渐近上界 f(n)

所以上述两种均可渐近表示时间复杂度

不过为了方便统一理解,我还是倾向于 O(2^{n})

  

  

六、考研408真题

【2011年 第 1 题】设 n 是描述问题规模的非负整数,下列程序段的时间复杂度是(A

x=2; 
while(x<n/2) x=2*x;

A.O(log_{2}n)    B.O(n)    C.O(nlog_{2}n)    D.O(n^{2})

本题与【例8.2】对数阶 相似,只不过循环条件不同 

  

计算时间复杂度

1.基本语句:x=2*x;

2.基本语句语句频度\sum\limits_{k=1}^{\lceil log_{2}(n) \rceil-2} 1=\overbrace{1+\cdots +1}^{\lceil log_{2}(n) \rceil-2}=\lceil log_{2}(n) \rceil-2

3.时间复杂度T(n)=O(log_{2}{n})

  

while循环可以理解为

循环变量初始化
while( 循环条件 )
{循环体【其中包含循环变量自增】
}

本例比较特殊,因为循环体只有1条语句

所以 x=2*x,既是循环体,也是循环变量自增

所以循环变量 x 不能反映实际的循环执行次数

设循环的执行次数为 k 次

  

为探寻循环变量 x 的变化规律,将其当作数列

循环变量初始化:x=2,则首项 a_{1}=2

循环变量自增:x*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=2\times 2^{n-1}=2^{n}

则循环变量 x 随着通项公式改变 

x=a_{1}=2^{1} \\[1.5ex] x=a_{2}=2^{2} \\[1.5ex] x=a_{3}=2^{3} \\[1.5ex] \cdots \\[1.5ex] x=a_{k}=2^{k}

所以最后一次循环(第k次循环)时

x=2^{k}

又因为循环条件是 x<\frac{n}{2}

不等式两边都不是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 x=2^{k} 代入不等式,得

x=2^{k}< \frac{n}{2}

不等式两边同时乘2

2^{k+1}<n

不等式两边同时取对数

k+1<log_{2}(n)

移项,得到循环执行次数k的范围

k<log_{2}(n)-1

  

根据【例8.2】中的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入本题:

k是整数,不等式关系为 k<log_{2}(n)-1 ,则整数k的最大值 k_{max}=\lceil log_{2}(n)-1 \rceil-1=\lceil log_{2}(n) \rceil-2

  

现在就可以列求和式计算了

while循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 k 从1到 \lceil log_{2}(n) \rceil-2,代表循环执行 \lceil log_{2}(n) \rceil-2

语句频度为:

\sum\limits_{k=1}^{\lceil log_{2}(n) \rceil-2} 1=\overbrace{1+\cdots +1}^{\lceil log_{2}(n) \rceil-2}=\lceil log_{2}(n) \rceil-2

省略系数和无关项后,数量级为 log_{2}{n},则时间复杂度为 O(log_{2}{n})

  

  

【2012年 第 1 题】求整数 n\ (n\geqslant0) 阶乘的算法如下,其时间复杂度是 (B

int fact(int n)
{if(n<=1) return 1;else return n*fact(n-1);
}

A.O(log_{2}n)    B.O(n)    C.O(nlog_{2}n)    D.O(n^{2})

计算时间复杂度

1.基本语句

return 1;

return n*fact(n-1);

2.基本语句语句频度n

3.时间复杂度T(n)=O(n)

递归函数无法按常规方式计算语句频度

但是可以转换思路,将递归过程模拟为二叉树

一个递归函数当作二叉树的一个节点

函数f(n)递归调用的是f(n-1)

从二叉树的视角来看,就是该节点连接下一层的一个孩子

所以每个节点(不算叶子节点)都只有一个孩子,最终得到"单叉"的二叉树

其实这棵"单叉"的二叉树就是线性表(或称二叉树退化为线性表)

纵向可能看不出来是线性表,改成横向就一目了然

例如:f(4)的递归二叉树(退化为线性表)如下图

  

再看函数体,无论进 if 还是进 else,都只会执行1条语句

所以递归函数内基本语句语句频度是1

又因为退化为线性表,所以总节点数就是n

所以 语句频度之和 = 线性表总节点数 =n

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

  

  

【2013年 第 1 题】已知两个长度分别为m和n的升序链表,若将它们合并为一个长度为m+n的降序链表,则最坏情况下的时间复杂度是(D

A.O(n)    B.O(mn)    C.O(min(m,n))    D.O(max(m,n))

升序改降序:只需在遍历时,用头插法将元素插入新链表

比如原链表为1、2、3、4、5,遍历时用头插法插入新链表,新链表变为5、4、3、2、1

这样做的好处是,遍历还是正常顺序,不需要倒着来

  

链表合并的最坏情况:两链表交替遍历

举例:链表1是:13579,链表2是:246

那么遍历合并为新链表时,二者就会交替遍历:123456

然后新链表将链表1的剩余部分:79 并入

链表1的长度m=5,链表2的长度n=3

那么整个合并过程会将 m+n=5+3=8 个数并入新链表

如果m,n没有给出具体值,那么并入过程是 m+n 个数并入

数量级为 m+n,所以时间复杂度为 O(m+n)

 

如果两个链表长度中的最大值用 max(m,n) 表示,则

m\leqslant max(m,n)

n\leqslant max(m,n)

相加得

m+n\leqslant 2\cdot max(m,n)

所以最多有 2\cdot max(m,n) 个数合并,省略系数后,数量级为 max(m,n),则

O(m+n)\leqslant O(max(m,n))

所以时间复杂度也可以看作是 O(max(m,n))


前面是举例分析得出的,也可以拿代码来分析

原题没有代码,这里给出一段链表合并的代码(以不带头节点的链表为例)

《头插法实现两升序链表合并为降序链表》

//头插法实现两升序链表合并为降序链表
ListNode* merge(ListNode* L1, ListNode* L2)
{ListNode *head = NULL; // 新链表的头指针ListNode *temp; //临时指针while (L1 != NULL && L2 != NULL){// 比较两个链表当前节点的值if (L1->val <= L2->val){//取出L1节点temp = L1;L1 = L1->next;// 头插法:将L1节点插入新链表头部temp->next = head;head = temp;}else{//取出L2节点temp = L2;L2 = L2->next;// 头插法:将L2节点插入新链表头部temp->next = head;head = temp;}}// 处理剩余节点(L1更长)while (L1 != NULL){//取出L1节点temp = L1;L1 = L1->next;// 头插法:将L1节点插入新链表头部temp->next = head;head = temp;}// 处理剩余节点(L2更长)while (L2 != NULL){//取出L2节点temp = L2;L2 = L2->next;// 头插法:将L2节点插入新链表头部temp->next = head;head = temp;}return head;
}

计算时间复杂度

1.基本语句

temp = L1;   //或temp = L2;

L1 = L1->next;   //或L2 = L2->next;

temp->next = head;

head = temp;

2.基本语句语句频度4\times(2n)+4\times(m-n)=4\times(2n+m-n)=4(m+n)

3.时间复杂度T(n)=O(m+n)\leqslant O(max(m,n))

假设两链表长度m>n(反之同理)

则链表1的前n项与链表2的n项,交替并入新链表(第一个while循环)

链表1剩余的m-n项,直接并入新链表(后面两个while循环二选一)

由于并入操作都是4条语句,所以不用区分是链表1还是链表2

交替并入时的语句频度 =4\times n+4\times n=4\times(2n)

剩余并入时的语句频度 =4\times(m-n)

所以语句频度之和 =4\times(2n)+4\times(m-n)=4\times(2n+m-n)=4(m+n)

省略系数后,数量级为 m+n,所以时间复杂度为 O(m+n)

如果两个链表长度的最大值用 max(m,n) 表示,则

m\leqslant max(m,n)

n\leqslant max(m,n)

相加得

m+n\leqslant 2\cdot max(m,n)

所以最多有 2\cdot max(m,n) 个数合并,省略系数后,数量级为 max(m,n),则

O(m+n)\leqslant O(max(m,n))

所以时间复杂度也可以看作是 O(max(m,n))


有人可能会疑惑,为什么剩余部分并入还要耗费时间,用指针直接连上剩余部分不就行了?

这就是不审题的结果,题目要求是:两升序链表合并为降序链表

如果直接将剩余部分连上,岂不是把一段升序链表并入降序链表,那结果就不对了

剩余部分并入之所以耗费时间,是因为要用头插法将这部分升序链表变为降序链表


(下面仅作探究,与本题无关)

引申一下,假如题目要求改为:两升序链表合并为升序链表

这就跟上面提出的思路一样了,两链表交替并入,最后连接剩余部分

《尾插法实现两升序链表合并为升序链表》

//尾插法实现两升序链表合并为升序链表
ListNode* merge(ListNode* L1, ListNode* L2)
{ListNode *head = NULL; // 新链表的头指针ListNode *tail = NULL; // 新链表的尾指针//确定第一个节点if (L1->val <= L2->val){head = L1;L1=L1->next;}else{head = L2;L2=L2->next;}tail = head;// 尾指针指向第一个节点while (L1 != NULL && L2 != NULL){// 比较两个链表当前节点的值if (L1->val <= L2->val){// 将L1节点接入链表尾部tail->next = L1;L1 = L1->next;}else{// 将L2节点接入链表尾部tail->next = L2;L2 = L2->next;}}// 连接剩余链表if (L1 != NULL){tail->next = L1;}else{tail->next = L2;}return head;
}

  

计算时间复杂度

1.基本语句

tail->next = L1;   //或tail->next = L2;

L1 = L1->next;   //或L2 = L2->next;

2.基本语句语句频度2+4n+1=4n+3

3.时间复杂度T(n)=O(n)=O(min(m,n))

假设两链表长度m>n(反之同理)

因为不带头节点,所以需要确定第一个节点,将链表1的第一个节点并入新链表(if...else)

链表1的n项与链表2的n项,交替并入新链表(while循环)

用指针直接连接链表1的剩余部分(if...else)

由于并入操作都是2条语句,所以不用区分是链表1还是链表2

第一个节点并入时的语句频度 =2\times 1=2

交替并入时的语句频度 =2\times n+2\times n=4n

指针连接剩余部分时的语句频度 =1

所以语句频度之和 =2+4n+1=4n+3

省略系数和无关项后,数量级为 n,所以时间复杂度为 O(n)

    

因为前面假设两链表长度m>n

如果两个链表长度的最小值用 min(m,n) 表示,则

n= min(m,n)

所以时间复杂度也可以看作是 O(n)=O(min(m,n))


假设两链表长度m>n,可以得出以下结论:

合并后顺序相反(升+升→降 或 降+降→升),则时间复杂度为 O(m+n)\leqslant O(max(m,n))

合并后顺序相同(升+升→升 或 降+降→降),则时间复杂度为 O(n)=O(min(m,n))

    

     

【2014年 第 1 题】下列程序段的时间复杂度是(C

count=0; 
for(k=1; k<=n; k*=2) for(j=1; j<=n; j++) count++;

A.O(log_{2}n)    B.O(n)    C.O(nlog_{2}n)    D.O(n^{2})

本题与【例9】线性对数阶 如出一辙,只不过内外循环互换了

  

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1 \times \sum\limits_{j=1}^{n}1= \lfloor log_{2}{(n)}+1\rfloor \times n=n\times \lfloor log_{2}{(n)}+1\rfloor

3.时间复杂度T(n)=O(nlog_{2}{n})

嵌套无关循环,所以内外层循环互不干扰,语句频度独立计算

因为内层循环条件是 j<=n ,所以 j 能取的最大值(即求和上限)是:n

内层循环只有1条基本语句,所以求和表达式(通项公式)是1

所以内层循环的语句频度

\sum\limits_{j=1}^{n}1 =\overbrace{1+\cdots +1}^{n} = n

  

因为外层循环变量自增不是以往的 k++,而是 k*=2

所以循环变量 k 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 k 的变化规律,将其当作数列

循环变量初始化:k=1,则首项 a_{1}=1

循环变量自增:k*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 k 随着通项公式改变 

k=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] k=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] k=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] k=a_{x}=2^{x-1} \\[1.5ex]

所以最后一次循环(第x次循环)时

k=2^{x-1}

又因为循环条件是 k \leqslant n

所以最后一次循环时 k=n,则

k=2^{x-1}= n

等式两边同时取对数

x-1=log_{2}{(n)}

移项,得到循环执行次数x的值

x=log_{2}{(n)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor log_{2}{(n)}+1\rfloor

    

现在就可以列求和式计算了

内层循环可以看作是外层循环体的1条基本语句,则求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor log_{2}{(n)}+1\rfloor,代表循环执行 \lfloor log_{2}{(n)}+1\rfloor

所以外层循环的语句频度为:

\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(n)}+1\rfloor}=\lfloor log_{2}{(n)}+1\rfloor

根据统计学乘法原理,两式相乘得语句频度

\sum\limits_{x=1}^{\lfloor log_{2}{(n)}+1\rfloor} 1 \times \sum\limits_{j=1}^{n}1= \lfloor log_{2}{(n)}+1\rfloor \times n=n\times \lfloor log_{2}{(n)}+1\rfloor

省略系数和无关项后,数量级为 nlog_{2}{n},则时间复杂度为 O(nlog_{2}{n})

  

  

【2017年 第 1 题】下列函数的时间复杂度是(B

int func(int n)
{ int i=0,sum=0; while(sum<n) sum+=++i; return i;
}

A.O(log_{2}n)    B.O(n^{\frac{1}{2}})    C.O(n)    D.O(nlog_{2}n)

计算时间复杂度

1.基本语句:sum+=++i;

2.基本语句语句频度\sum\limits_{x=1}^{\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1} 1=\overbrace{1+\cdots +1}^{\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1}=\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1

3.时间复杂度T(n)=O(\sqrt{n})=O(n^{\frac{1}{2}})

 

因为本例的循环条件不是以往的 i<n,而是 sum<n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

while循环体【包含循环变量自增】只有1条基本语句:sum+=++i;

前置自增表达式 ++i 有两个效果:

一是让 i 自增为 i+1,二是把 i+1 作为整个自增表达式的值

所以 sum+=++i; 相当于分两步:

先执行++i; 让 i 自增为 i+1,后执行sum+=(i+1); 进行求和

所以sum始终是对 i+1 进行求和

  

为探寻 i+1 的变化规律,将其当作数列

循环变量初始化:i=0,则 i+1=1,首项 a_{1}=1

循环变量自增:++i,公差 d=1 ,即等差数列

等差数列 {i+1} 的通项公式:a_{n}=a_{1}+(n-1)d=1+(n-1)=n

则等差数列 {i+1} 随着通项公式改变 

i+1=a_{1}=0+1=1 \\[1.5ex] i+1=a_{2}=1+1=2 \\[1.5ex] i+1=a_{3}=2+1=3 \\[1.5ex] \cdots \\[1.5ex] i+1=a_{x}=(x-1)+1=x

因为sum始终是对 i+1 进行求和

所以可以将其看作是等差数列 {i+1} 的前n项和

代入前n项和公式:S_{n}=\frac{n(a_{1}+a_{n})}{2}=\frac{n(1+n)}{2}=\frac{n(n+1)}{2}

  

注意,本题是while循环,初次循环判断的是sum的初值0,而初值与 i+1 无关,所以单设一个 S_{0}

后续sum对 i+1 求和,则sum随着前n项和公式改变 循环共执行x次,首项是 S_{0},则末项是 S_{x-1}

sum=S_{0}=0 \\[1.5ex] sum=S_{1}=\frac{1(1+1)}{2}=1 \\[1.5ex] sum=S_{2}=\frac{2(2+1)}{2}=3 \\[1.5ex] sum=S_{3}=\frac{3(3+1)}{2}=6 \\[1.5ex] \cdots \\[1.5ex] sum=S_{x-1}=\frac{(x-1)(x-1+1)}{2}=\frac{x(x-1)}{2}

所以最后一次循环(第x次循环)时

sum=\frac{x(x-1)}{2}

又因为循环条件是 sum <n

不等式两边不都是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 sum=\frac{x(x-1)}{2} 代入不等式,得

sum=\frac{x(x-1)}{2}< n

不等式两边同时乘2

x(x-1)<2n

不等式左边展开得

x^{2}-x<2n

移项,得一元二次不等式

x^{2}-x-2n<0

则各项系数 a=1,b=-1,c=-2n (其中n是问题规模n\geqslant 0

 

根据一元二次函数图像、方程、不等式的对应关系

因为 a=1>0,所以函数图像开口向上

因为 \Delta =b^{2}-4ac=(-1)^{2}-4\cdot1\cdot (-2n)=8n+1>0,所以方程有两实数根

因为 x^{2}+x-2n<0,所以不等式的解集位于两根之间

  

将 a=1,b=-1,c=-2n 代入求根公式,得出两根

x_{1}=\frac{-b-\sqrt{b^{2}-4ac}}{2a}=\frac{-(-1)-\sqrt{1^{2}-4\cdot 1 \cdot(-2n)}}{2\cdot 1}=\frac{1-\sqrt{1+8n}}{2}=-\frac{\sqrt{8n+1}}{2}+\frac{1}{2}

x_{2}=\frac{-b+\sqrt{b^{2}-4ac}}{2a}=\frac{-(-1)+\sqrt{1^{2}-4\cdot 1 \cdot(-2n)}}{2\cdot 1}=\frac{1+\sqrt{1+8n}}{2}=\frac{\sqrt{8n+1}}{2}+\frac{1}{2}

所以x的取值范围为 (-\frac{\sqrt{8n+1}}{2}+\frac{1}{2})<x<(\frac{\sqrt{8n+1}}{2}+\frac{1}{2})

  

根据【例8.2】中的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入本题:

x是整数,不等式关系为 x<(\frac{\sqrt{8n+1}}{2}+\frac{1}{2}) ,则整数x的最大值 x_{max}=\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1

  

现在就可以列求和式计算了

while循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1,代表循环执行 \lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1

语句频度为:

\sum\limits_{x=1}^{\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1} 1=\overbrace{1+\cdots +1}^{\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1}=\lceil \frac{\sqrt{8n+1}}{2}+\frac{1}{2} \rceil-1

省略系数和无关项后,数量级为 \sqrt{n},则时间复杂度为 O(\sqrt{n})=O(n^{\frac{1}{2}})


其实做选择题没必要这么精确

上面已列出关于循环条件的不等式

sum=\frac{x(x-1)}{2}< n

把x-1近似为x,并将整个式子看作等式

sum=\frac{x^{2}}{2}= n

得出循环执行次数的近似值

x\approx \sqrt{2n}

语句频度近似值为

\sum\limits_{x=1}^{\sqrt{2n}} 1 \approx \overbrace{1+\cdots +1}^{\sqrt{2n}}\approx \sqrt{2n}

时间复杂度

O(\sqrt{n})=O(n^{\frac{1}{2}})

   

     

【2019年 第 1 题】设 n 是描述问题规模的非负整数,下列程序段的时间复杂度是(B

x=0; 
while(n>=(x+1)*(x+1)) x=x+1;

A.O(log_{2}n)    B.O(n^{\frac{1}{2}})    C.O(n)    D.O(n^{2})

本题与【例10.1】根号阶 相似,只不过循环变量初始化和循环条件不同

  

计算时间复杂度

1.基本语句:x=x+1;

2.基本语句语句频度\sum\limits_{k=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

3.时间复杂度T(n)=O(\sqrt{n})=O(n^{\frac{1}{2}})

 

因为本例的循环条件不是以往的 x<=n,而是 n>=(x+1)*(x+1)

但循环条件通常把循环变量表达式写在前面,如果反过来写,就是(x+1)*(x+1)<=n

所以循环变量 x 不能反映实际的循环执行次数

设循环的执行次数为 k 次

  

为探寻循环条件 (x+1)*(x+1) 的变化规律,将其当作数列

循环变量初始化:x=0

循环变量自增:x=x+1

将循环变量 x 的值依次代入循环条件 (x+1)*(x+1),探寻数列规律

(x+1)*(x+1)=a_{1}=(0+1)\times (0+1)=1\times 1=1^{2} \\[1.5ex] (x+1)*(x+1)=a_{2}=(1+1)\times (1+1)=2\times 2=2^{2} \\[1.5ex] (x+1)*(x+1)=a_{3}=(2+1)\times (2+1)=3\times 3=3^{2} \\[1.5ex] \cdots \\[1.5ex] (x+1)*(x+1)=a_{k}=((k-1)+1)\times ((k-1)+1)=k \times k=k^{2} \\[1.5ex]

所以最后一次循环(第k次循环)时

(x+1)*(x+1)=k^{2}

又因为循环条件是 (x+1)*(x+1)\leqslant n

所以最后一次循环时 (x+1)*(x+1)=n,则

(x+1)*(x+1)=k^{2}=n

等式两边同时开平方,得到循环执行次数k的值

k=\sqrt{n}

但是循环执行次数只能是整数,所以k还要向下取整

k=\lfloor \sqrt{n} \rfloor

  

现在就可以列求和式计算了

while循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 k 从1到 \lfloor \sqrt{n} \rfloor,代表循环执行 \lfloor \sqrt{n} \rfloor

语句频度为:

\sum\limits_{k=1}^{\lfloor \sqrt{n} \rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{n} \rfloor}=\lfloor \sqrt{n} \rfloor

省略系数和无关项后,数量级为 \sqrt{n},则时间复杂度为 O(\sqrt{n})=O(n^{\frac{1}{2}})

  

  

【2022年 第 1 题】下列程序段的时间复杂度是(B

int sum=0;
for(int i=1; i<n; i*=2)for(int j=0; j<i; j++)sum++;

A.O(log_{2}n)    B.O(n)    C.O(nlog_{2}n)    D.O(n^{2})

本题看着与【2014年 第 1 题】类似,但实际上不同

2014年是嵌套无关循环,而本题是 嵌套相关循环

  

计算时间复杂度

1.基本语句:sum++;

2.基本语句语句频度

\begin{matrix} \sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} \sum\limits_{j=0}^{i-1}1=\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} \sum\limits_{j=1}^{i}1 =\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} i =\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} 2^{x-1} =S_{\lceil log_{2}{(n)} \rceil} =\frac{2^{0}(1-2^{\lceil log_{2}{(n)} \rceil})}{1-2} =2^{\lceil log_{2}{(n)} \rceil}-1 \approx 2^{ log_{2}{(n)}}-1 \approx n-1 \end{matrix}

3.时间复杂度T(n)=O(n)

因为是嵌套相关循环,内外层循环相关,不能分开计算

但是可以先列出来求和式的上下限,再多重求和

  

因为内层循环条件是 j<i ,所以 j 能取的最大值(即求和上限)是:i-1

所以内层循环的求和式上下限列为

\sum\limits_{j=0}^{i-1}

 

因为外层循环变量自增不是以往的 i++,而是 i*=2

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 i 的变化规律,将其当作数列

循环变量初始化:i=1,则首项 a_{1}=1

循环变量自增:i*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 i 随着通项公式改变 

i=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] i=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] i=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] i=a_{x}=2^{x-1} \\[1.5ex]

所以最后一次循环(第x次循环)时

i=2^{x-1}

又因为循环条件是 i<n

不等式两边不都是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 i=2^{x-1} 代入不等式,得

i=2^{x-1}< n

不等式两边同时取对数

x-1<log_{2}{(n)}

移项,得到循环执行次数x的范围

x<log_{2}{(n)}+1

根据【例8.2】中的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入本题:

x是整数,不等式关系为 x<log_{2}{(n)}+1 ,则整数x的最大值 x_{max}=\lceil log_{2}{(n)}+1 \rceil -1=\lceil log_{2}{(n)} \rceil

  

令求和变量 x 从1到 \lceil log_{2}{(n)} \rceil,代表循环执行 \lceil log_{2}{(n)} \rceil

所以外层循环的求和式上下限为:

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil}

  

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} \sum\limits_{j=0}^{i-1}1

双重求和可以加括号以区分

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} \sum\limits_{j=0}^{i-1}1=\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} (\sum\limits_{j=0}^{i-1}1)

可以类比复合函数,先计算括号内的

\sum\limits_{j=0}^{i-1}1 =\sum\limits_{j=0+1}^{i-1+1}1=\sum\limits_{j=1}^{i}1=\overbrace{1+\cdots +1}^{i} = i

将括号内的求和式替换为计算结果,双重求和变为

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} (\sum\limits_{j=0}^{i-1}1)=\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} i


注意,此时求和变量 x 与求和表达式 i 是不同符号,直接展开无意义

所以需要把 i 替换为关于求和变量 x 的表达式


前面已推导出最后一次循环(第x次循环)时

i=2^{x-1}

i 替换

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} i=\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} 2^{x-1}

将其展开得

\begin{matrix} \sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} 2^{x-1}=2^{1-1}+2^{2-1}+\cdots+2^{\lceil log_{2}{(n)} \rceil-1}=2^{0}+2^{1}+\cdots+2^{\lceil log_{2}{(n)} \rceil-1} \end{matrix}

此时变为等比数列求和

首项 a_{1}=2^{0},公比 q=2

代入等比数列求和公式 

S_{n}=\frac{a_{1}(1-q^{n})}{1-q} 

得到语句频度

\sum\limits_{x=1}^{\lceil log_{2}{(n)} \rceil} 2^{x-1}\\[1.5ex] =S_{\lceil log_{2}{(n)} \rceil}\\[1.5ex] =\frac{2^{0}(1-2^{\lceil log_{2}{(n)} \rceil})}{1-2}\\[1.5ex] =2^{\lceil log_{2}{(n)} \rceil}-1\\[1.5ex] \approx 2^{ log_{2}{(n)}}-1\\[1.5ex] \approx n-1

解释一下,根据对数的定义可知

2^{log_{2}{(n)}}=n

因为 \lceil log_{2}{(n)} \rceillog_{2}{(n)} 差距很小,所以

2^{\lceil log_{2}{(n)} \rceil} \approx 2^{log_{2}{(n)}} \approx n 

同时减1得

2^{\lceil log_{2}{(n)} \rceil}-1 \approx 2^{log_{2}{(n)}} -1\approx n-1

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

  

  

【2023年 第 1 题】下列对顺序存储的有序表(长度为n)实现给定操作的算法中,平均时间复杂度为 O(1) 的是(D

A.查找包含指定值元素的算法

B.插入包含指定值元素的算法

C.删除第 i\ (1\leqslant i \leqslant n) 个元素的算法

D.获取第 i\ (1\leqslant i \leqslant n) 个元素的算法

  

题目给定的是顺序存储,则有序表使用数组实现

typedef struct
{int data[MAXSIZE];  // 存储元素的数组int n;  // 当前表长
} SeqList;

  

有序表一般采用折半查找(二分查找)算法

A.查找

// A. 查找指定值元素——折半查找(二分查找)
int search(SeqList *list, int value)
{int low = 0, high = list->n-1,mid;while (low <= high){mid = (low + high)/2;if (list->data[mid] == value)return mid;  // 找到返回下标else if (list->data[mid] < value)low = mid+1;elsehigh = mid-1;}return -1;  // 未找到
}

计算时间复杂度

1.基本语句

mid = (low + high) / 2; 

return mid;

low = mid + 1;

high = mid - 1;

2.基本语句语句频度2\times[\frac{n+1}{n}log_{2}(n+1)-1] \approx 2\times[\frac{n}{n}log_{2}(n+1)-1] \approx 2\times[log_{2}(n+1)-1]

3.时间复杂度T(n)=O(log_{2}{n})

本函数有两种结束方式:

一是查找失败,最终low>high,while循环结束,函数返回 -1

二是查找成功,list->data[mid] == value,函数返回 mid

  

再看基本语句

while循环体中,mid = (low + high) / 2; 这1条语句是必做的

其他三条语句通过if……else if……else判断后,三选一

所以每轮循环体执行1+1=2条基本语句

  

语句频度=基本语句条数 x 循环执行次数

已经得出基本语句条数为2条,如果再知道 循环执行次数 就能算出语句频度

下面只探究查找成功情况下的循环执行次数

这里需要引入 平均查找长度 的概念

从定义来看,平均查找长度其实就是平均比较次数

而每次比较,对应到代码,就是循环体中的if……else if……else判断

所以每执行一次循环体,就是一次比较

则 循环执行次数=平均比较次数=平均查找长度

    

所以求循环执行次数 转为 求平均查找长度

推导过程比较复杂,这里直接给出结果,折半查找的平均查找长度\frac{n+1}{n}log_{2}(n+1)-1

如果想了解推导过程可以看我的这篇文章——《 折半查找的平均查找长度公式推导 》

所以循环执行次数=折半查找的平均查找长度\frac{n+1}{n}log_{2}(n+1)-1

语句频度=基本语句条数 x 循环执行次数=基本语句条数 x 平均查找长度

=2\times[\frac{n+1}{n}log_{2}(n+1)-1] \approx 2\times[\frac{n}{n}log_{2}(n+1)-1] \approx 2\times[log_{2}(n+1)-1]

(注:因为对数有小括号,所以整个平均查找长度用中括号括住,并不是向上取整或向下取整)

解释一下,因为n+1与n差距很小,所以

\frac{n+1}{n}\approx \frac{n}{n} \approx 1

代入

2\times[\frac{n+1}{n}log_{2}(n+1)-1] \approx 2\times[\frac{n}{n}log_{2}(n+1)-1] \approx 2\times[log_{2}(n+1)-1]

省略系数和无关项后,数量级为 log_{2}n,则时间复杂度为 O(log_{2}n)

所以查找过程的时间复杂度O(log_{2}n)不是 O(1)

  

B.插入

// B. 插入指定值元素(保持有序)
int insert(SeqList *list, int value)
{if (list->n >= MAXSIZE){printf("表已满,无法插入\n");return 0;}int i = list->n-1;// 从后向前找到插入位置while (i >= 0 && list->data[i] > value){list->data[i+1] = list->data[i];  // 元素后移i--;}list->data[i+1] = value;  // 插入新元素list->n++;  // 表长增加return 1;  // 成功插入
}

计算时间复杂度

1.基本语句

list->data[i + 1] = list->data[i];

i--;

2.基本语句语句频度2\times (n-1-\lfloor \frac{n-1}{2} \rfloor)\approx 2\times(n-1-\frac{n-1}{2}) \approx 2\times \frac{n-1}{2} \approx n-1

3.时间复杂度T(n)=O(n)

因为是在数组中插入,需要给待插入元素留空位

所以从后往前遍历,将比它大的每个元素都向后移动一格

最后将待插入元素插到空位中,并改变表长

 

举例:插入值为5的元素

  

以待插入数据在中间为例

表长为n,表尾下标为n-1,则中间位置为 \lfloor \frac{n-1}{2} \rfloor

则向后移动的元素有 n-1-\lfloor \frac{n-1}{2} \rfloor

基本语句是循环体中的2条语句

所以语句频度为 2\times (n-1-\lfloor \frac{n-1}{2} \rfloor)\approx 2\times(n-1-\frac{n-1}{2}) \approx 2\times \frac{n-1}{2} \approx n-1

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

所以插入过程的时间复杂度是 O(n),不是 O(1)

   

C.删除

// C. 删除第i个元素(0≤i≤n-1)
int delete (SeqList *list, int i)
{if (i < 0 || i > list->n-1){printf("位置非法\n");return 0;}// 将第i+1个位置及以后的元素前移for (int j = i+1; j < list->n; j++){list->data[j-1] = list->data[j];}list->n--;  // 表长减1return 1;        // 成功删除
}

计算时间复杂度

1.基本语句:list->data[j - 1] = list->data[j];

2.基本语句语句频度n-1-\lfloor \frac{n-1}{2} \rfloor \approx n-1-\frac{n-1}{2} \approx \frac{n-1}{2}

3.时间复杂度T(n)=O(n)

删除与插入如出一辙,只不过一个向前移动,一个向后移动

因为是在数组中删除,只需要将待删除元素的位置覆盖掉

所以从前往后遍历,将比它大的每个元素都向前移动一格,并改变表长

举例:删除a[4]位置的元素

   

以待删除数据在中间为例

表长为n,表尾下标为n-1,则中间位置为 \lfloor \frac{n-1}{2} \rfloor

则向前移动的元素有 n-1-\lfloor \frac{n-1}{2} \rfloor

基本语句是循环体中的1条语句

因为此处为for循环,循环变量自增不在循环体中,所以基本语句只有1条

所以语句频度为 n-1-\lfloor \frac{n-1}{2} \rfloor \approx n-1-\frac{n-1}{2} \approx \frac{n-1}{2}

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

所以删除过程的时间复杂度是 O(n),不是 O(1)

  

D.获取

// D. 获取第i个元素(0≤i≤n-1)
int get(SeqList *list, int i)
{if (i < 0 || i > list->n-1){printf("位置非法\n");return -1;  // 返回-1表示错误(假设元素均为非负整数)}return list->data[i];  // 返回元素值
}

计算时间复杂度

1.基本语句:return list->data[i - 1];

2.基本语句语句频度1

3.时间复杂度T(n)=O(1)

获取元素仅需1条return语句,所以语句频度为 1

省略系数和无关项后,数量级为 1,则时间复杂度为 O(1)

所以获取过程的时间复杂度就是 O(1)

    

【2025年 第 1 题】下列程序段的时间复杂度是(B

int count=0;
for(int i=0; i*i<n; i++)for(int j=0; j<i; j++)count++;

A.O(log_{2}n)    B.O(n)    C.O(nlog_{2}n)    D.O(n^{2})

本例类似于将【例3】嵌套相关循环 和【例10.2】根号阶 结合起来

  

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度

\begin{matrix} \sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} \sum\limits_{j=0}^{i-1}1= \sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} \sum\limits_{j=1}^{i}1=\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} i=\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (x-1)=S_{\lceil \sqrt{n}\ \rceil} =\frac{(\lceil \sqrt{n}\ \rceil)^{2}-\lceil \sqrt{n}\ \rceil}{2} \approx \frac{(\sqrt{n}\,)^{2} -\sqrt{n}}{2} \approx \frac{n -\sqrt{n}}{2}\\[1.5ex]\end{matrix}

3.时间复杂度T(n)=O(n)

因为是嵌套相关循环,内外层循环相关,不能分开计算

但是可以先列出来求和式的上下限,再多重求和

  

因为内层循环条件是 j<i ,所以 j 能取的最大值(即求和上限)是:i-1

所以内层循环的求和式上下限为

\sum\limits_{j=0}^{i-1}

  

因为外层循环条件不是以往的 i<n,而是 i*i<n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 i*i 的变化规律,将其当作数列

循环变量初始化:i=0

循环变量自增:i++

将循环变量 i 的值依次代入循环条件 i*i,探寻数列规律

i*i=a_{1}=0\times 0=0^{2}=0 \\[1.5ex] i*i=a_{2}=1\times 1=1^{2}=1 \\[1.5ex] i*i=a_{3}=2\times 2=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] i*i=a_{x}=(x-1)\times (x-1)=(x-1)^{2} \\[1.5ex]

所以最后一次循环(第x次循环)时

i*i=(x-1)^{2}

又因为循环条件是 i*i < n

不等式两边不都是整数表达式,不好计算最后一次循环的x值

所以不追求列等式,而是直接将 i*i=(x-1)^{2} 代入不等式,得

i*i=(x-1)^{2}<n

不等式两边同时开平方

x-1<\sqrt{n}

移项,得到循环执行次数x的范围

x<\sqrt{n}+1

  

根据【例8.2】中的结论:

x是整数,不等式关系为 x<f(n) ,则整数x的最大值 x_{max}=\lceil f(n) \rceil -1

代入本题:

x是整数,不等式关系为 x<\sqrt{n}+1 ,则整数x的最大值 x_{max}=\lceil \sqrt{n}+1 \rceil -1=\lceil \sqrt{n}\ \rceil

  

令求和变量 x 从1到 \lceil \sqrt{n}\ \rceil,代表循环执行 \lceil \sqrt{n}\ \rceil

所以外层循环的求和式上下限为:

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil}

  

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} \sum\limits_{j=0}^{i-1}1

双重求和可以加括号以区分

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} \sum\limits_{j=0}^{i-1}1=\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (\sum\limits_{j=0}^{i-1}1)

可以类比复合函数,先计算括号内的

\sum\limits_{j=0}^{i-1}1 =\sum\limits_{j=0+1}^{i-1+1}1=\sum\limits_{j=1}^{i}1=\overbrace{1+\cdots +1}^{i} = i

将括号内的求和式替换为计算结果,双重求和变为 

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (\sum\limits_{j=0}^{i-1}1)=\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} i


注意,此时求和变量 x 与求和表达式 i 是不同符号,直接展开无意义

所以需要把 i 替换为关于求和变量 x 的表达式


前面已推导出最后一次循环(第x次循环)时

i*i=(x-1)^{2}

等式两边同时开平方

i=x-1

i 替换

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} i=\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (x-1)

将其展开得

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (x-1)\\[1.5ex] =(1-1)+(2-1)+\cdots+(\lceil \sqrt{n}\ \rceil-1)\\[1.5ex] =0+1+\cdots+(\lceil \sqrt{n}\ \rceil-1)

此时变为等差数列求和

首项 a_{1}=0,公差 d=1

代入等差数列求和公式 

S_{n}=\frac{n(a_{1}+a_{n})}{2}

得到语句频度

\sum\limits_{x=1}^{\lceil \sqrt{n}\ \rceil} (x-1)\\[1.5ex] =S_{\lceil \sqrt{n}\ \rceil}\\[1.5ex] =\frac{(\lceil \sqrt{n}\ \rceil)(0+\lceil \sqrt{n}\ \rceil-1)}{2}\\[1.5ex] =\frac{(\lceil \sqrt{n}\ \rceil)(\lceil \sqrt{n}\ \rceil-1)}{2}\\[1.5ex] =\frac{(\lceil \sqrt{n}\ \rceil)^{2}-\lceil \sqrt{n}\ \rceil}{2}\\[1.5ex] \approx \frac{(\sqrt{n}\,)^{2} -\sqrt{n}}{2}\\[1.5ex] \approx \frac{n -\sqrt{n}}{2}\\[1.5ex]

解释一下,因为 \lceil \sqrt{n}\ \rceil 与 \sqrt{n}  差距很小,所以

\lceil \sqrt{n}\ \rceil \approx \sqrt{n}

同时平方得

(\lceil \sqrt{n}\ \rceil)^{2} \approx (\sqrt{n}\,)^{2} \approx n 

最终

\frac{(\lceil \sqrt{n}\ \rceil)^{2}-\lceil \sqrt{n}\ \rceil}{2} \approx \frac{(\sqrt{n}\,)^{2} -\sqrt{n}}{2} \approx \frac{n -\sqrt{n}}{2}\\[1.5ex]

省略系数和无关项后,数量级为 n,则时间复杂度为 O(n)

  

  

七、变形题

(下面是我自己想出来的一些变形题)

【变形题1】下列程序段的时间复杂度是(B

int count=0;
for(int i=1; i*i<=n; i*=2)count++;

A.O(n^{\frac{1}{2}})    B.O(log_{2}n)    C.O(n)    D.O(n log_{2}n)

本题看着像是把【例8.1】对数阶 与【例10.1】根号阶 结合起来,但最终结果不是

因为平方只是将其指数变为2倍:  a^{x}\times a^{x}=a^{x+x}=a^{2x}

  

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度\sum\limits_{x=1}^{\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor}=\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor

3.时间复杂度T(n)=O(log_{2}{n})

因为本例的循环变量自增不是以往的 i++,而是 i*=2

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 i 的变化规律,将其当作数列

循环变量初始化:i=1,则首项 a_{1}=1

循环变量自增:i*=2,则公比 q=2,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 i 随着通项公式改变 

i=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] i=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] i=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] i=a_{x}=2^{x-1} \\[1.5ex]

  

为探寻循环条件 i*i 的变化规律,也将其当作数列

将上述循环变量 i 的值依次代入循环条件 i*i,探寻数列规律

i*i=b_{1}=2^{0} \times 2^{0}=2^{0+0}=2^{0}=1 \\[1.5ex] i*i=b_{2}=2^{1} \times 2^{1}=2^{1+1}=2^{2}=4 \\[1.5ex] i*i=b_{3}=2^{2} \times 2^{2}=2^{2+2}=2^{4}=16 \\[1.5ex] \cdots \\[1.5ex] i*i=b_{x}=2^{x-1} \times 2^{x-1}=2^{(x-1)+(x-1)}=2^{2(x-1)} \\[1.5ex]

所以最后一次循环(第x次循环)时

i*i=2^{2(x-1)}

又因为循环条件是 i*i\leqslant n

所以最后一次循环时 i*i=n,则

i*i=2^{2(x-1)}=n

等式两边同时取对数

2(x-1)=log_{2}{(n)}

等式两边同时除以2

x-1=\frac{1}{2} log_{2}{(n)}

移项,得到循环执行次数x的值

x=\frac{1}{2} log_{2}{(n)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor

  

现在就可以列求和式计算了

for循环只有1条基本语句,所以求和表达式(通项公式)是1

令求和变量 x 从1到 \lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor,代表循环执行 \lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor

语句频度为:

\sum\limits_{x=1}^{\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor} 1=\overbrace{1+\cdots +1}^{\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor}=\lfloor \frac{1}{2} log_{2}{(n)}+1\rfloor

省略系数和无关项后,数量级为 log_{2}{n},则时间复杂度为 O(log_{2}{n})

  

  

【变形题2】下列程序段的时间复杂度是(D

int count=0;
for(int i=1; i<=n; i++)for(int j=1; j<=i; j*=2)count++;

A.O(n^{\frac{1}{2}})    B.O(log_{2}n)    C.O(n)    D.O(n log_{2}n)

本题看着与【例9】线性对数阶类似,但内层循环条件不同

【例9】是嵌套无关循环,而本题是 嵌套相关循环

  

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度

\sum\limits_{i=1}^{n}\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1 =\sum\limits_{i=1}^{n} \lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{i=1}^{n} (log_{2}{(i)}+1) \approx \sum\limits_{i=1}^{n}log_{2}{(i)}+\sum\limits_{i=1}^{n}1\\[1.5ex] \approx log_{2}{(n!)}+n \approx n\cdot log_{2}{(n)}+(1-log_{2}{(e)})\cdot n+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

3.时间复杂度T(n)=O(nlog_{2}n)

因为是嵌套相关循环,内外层循环相关,不能分开计算

但是可以先列出来求和式的上下限,再多重求和

  

因为外层循环条件是 i<=n,所以 i 能取的最大值(即求和上限)是:n

所以外层循环的求和式上下限为

\sum\limits_{i=1}^{n}

    

因为内层循环变量自增不是以往的 j++,而是 j*=2

所以循环变量 j 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环变量 j 的变化规律,将其当作数列

循环变量初始化:j=1,则首项 a_{1}=1

循环变量自增:j*=2,则公比 q=2 ,即等比数列

等比数列通项公式:a_{n}=a_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 j 随着通项公式改变 

j=a_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] j=a_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] j=a_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] j=a_{x}=2^{x-1} \\[1.5ex]

所以最后一次循环(第x次循环)时

j=2^{x-1}

又因为循环条件是 j \leqslant i

所以最后一次循环时 j=i,则

j=2^{x-1}= i

等式两边同时取对数

x-1=log_{2}{(i)}

移项,得到循环执行次数x的值

x=log_{2}{(i)}+1

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor log_{2}{(i)}+1\rfloor

令求和变量 x 从1到 \lfloor log_{2}{(i)}+1\rfloor,代表循环执行 \lfloor log_{2}{(i)}+1\rfloor

所以内层循环的求和式上下限为:

\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}

  

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{i=1}^{n}\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1

双重求和可以加括号以区分

\sum\limits_{i=1}^{n}\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1=\sum\limits_{i=1}^{n} (\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1)

可以类比复合函数,先计算括号内的

\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(i)}+1\rfloor} = \lfloor log_{2}{(i)}+1\rfloor

将括号内的求和式替换为计算结果,双重求和变为 

\sum\limits_{i=1}^{n} (\sum\limits_{x=1}^{\lfloor log_{2}{(i)}+1\rfloor}1)=\sum\limits_{i=1}^{n}\lfloor log_{2}{(i)}+1\rfloor

向下取整不容易计算,将其近似为

\sum\limits_{i=1}^{n} \lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{i=1}^{n} (log_{2}{(i)}+1)

根据求和式的加法规则,可以拆分为

\sum\limits_{i=1}^{n} (log_{2}{(i)}+1)=\sum\limits_{i=1}^{n}log_{2}{(i)}+\sum\limits_{i=1}^{n}1=\sum\limits_{i=1}^{n}log_{2}{(i)}+n

下面将新求和式 \sum\limits_{i=1}^{n}log_{2}{(i)} 展开,可以看到是多个对数求和

\sum\limits_{i=1}^{n}log_{2}{(i)}=log_{2}{(1)}+log_{2}{(2)}+\cdots+log_{2}{(n-1)}+log_{2}{(n)}

根据对数的加法法则

log_{a}{(M)}+log_{a}{(N)}=log_{a}{(M\times N)}

则上述求和可以转为

\begin{matrix} log_{2}{(1)}+log_{2}{(2)}+\cdots+log_{2}{(n-1)}+log_{2}{(n)}=log_{2}{(1\times 2\times \cdots\times (n-1)\times n)} \end{matrix}

因为从1乘到n可以写作阶乘n!

1\times 2\times \cdots\times (n-1)\times n=n!

所以替换后

log_{2}{(1\times 2\times \cdots\times (n-1)\times n)} =log_{2}{(n!)}

则新求和式

\sum\limits_{i=1}^{n}log_{2}{(i)}=log_{2}{(n!)}

则原求和式

\sum\limits_{i=1}^{n} (log_{2}{(i)}+1)=\sum\limits_{i=1}^{n}log_{2}{(i)}+\sum\limits_{i=1}^{n}1=log_{2}{(n!)}+n

  

根据斯特林公式,阶乘n!的近似值为

n!\approx \sqrt{2\pi n}\ (\frac{n}{e})^{n}

代入得

log_{2}{(n!)}\approx log_{2}(\sqrt{2\pi n}\ (\frac{n}{e})^{n})

根据对数的加法法则

log_{a}{(M\times N)}=log_{a}{(M)}+log_{a}{(N)}

上式可拆分为

log_{2}(\sqrt{2\pi n}\ (\frac{n}{e})^{n})=log_{2}(\sqrt{2\pi n}\,)+log_{2}((\frac{n}{e})^{n})

根据对数的幂法则

log_{a}{(M^{n})}=n\cdot log_{a}{(M)}

根号是 \frac{1}{2} 次幂,上式变为

log_{2}(\sqrt{2\pi n}\,)+log_{2}((\frac{n}{e})^{n})\\[1.5ex] =log_{2}((2\pi n)^{\frac{1}{2}})+log_{2}((\frac{n}{e})^{n})\\[1.5ex] =\frac{1}{2}\cdot log_{2}{(2\pi n)}+n\cdot log_{2}{(\frac{n}{e})}

  

将两项分开计算:

根据对数的加法法则

log_{a}{(M\times N)}=log_{a}{(M)}+log_{a}{(N)}

第一项可拆分为

\frac{1}{2}\cdot log_{2}{(2\pi n)}\\[1.5ex] =\frac{1}{2}\cdot [log_{2}(2)+log_{2}(\pi)+log_{2}(n) ]\\[1.5ex] =\frac{1}{2}\cdot[1+log_{2}(\pi)+log_{2}(n) ]

  

根据对数的减法法则

log_{a}{(\frac{M}{N})}=log_{a}{(M)}-log_{a}{(N)}

第二项可拆分为

n\cdot log_{2}{(\frac{n}{e})}=n\cdot [log_{2}{(n)}-log_{2}{(e)}]

  

两项相加

\frac{1}{2}\cdot log_{2}{(2\pi n)}+n\cdot log_{2}{(\frac{n}{e})}\\[1.5ex] =\frac{1}{2}\cdot[1+log_{2}{(\pi)}+log_{2}{(n)} ]+n\cdot [log_{2}{(n)}-log_{2}{(e)}]\\[1.5ex] =\frac{1}{2}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}\cdot log_{2}{(n)}+n\cdot log_{2}{(n)}- log_{2}{(e)}\cdot n \\[1.5ex] =n\cdot log_{2}{(n)}-log_{2}{(e)}\cdot n+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

则新求和式

\sum\limits_{i=1}^{n}log_{2}{(i)}\\[1.5ex] =log_{2}{(n!)} \\[1.5ex] \approx n\cdot log_{2}{(n)}-log_{2}{(e)}\cdot n+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

则原求和式

\sum\limits_{i=1}^{n} (log_{2}{(i)}+1)\\[1.5ex] =\sum\limits_{i=1}^{n}log_{2}{(i)}+\sum\limits_{i=1}^{n}1\\[1.5ex] =log_{2}{(n!)}+n \\[1.5ex] \approx n\cdot log_{2}{(n)}-log_{2}{(e)}\cdot n+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}+n\\[1.5ex] \approx n\cdot log_{2}{(n)}+(n-log_{2}{(e)}\cdot n)+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}\\[1.5ex] \approx n\cdot log_{2}{(n)}+(1-log_{2}{(e)})\cdot n+\frac{1}{2}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

令常数 c_{1}=1-log_{2}{(e)}

令常数 c_{2} =\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

语句频度近似值为

\sum\limits_{i=1}^{n} \lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{i=1}^{n} (log_{2}{(i)}+1)\approx n\cdot log_{2}{(n)}+c_{1} n+\frac{1}{2}\cdot log_{2}{(n)}+c_{2}

省略系数和无关项后,数量级为 nlog_{2}n,则时间复杂度为 O(nlog_{2}n)

  

  

 【变形题3】下列程序段的时间复杂度是(B

int count=0;
for(int i=1; i*i<=n; i++)for(int j=1; j<=i; j*=2)count++;

A.O(log_{2}n)    B.O(\sqrt{n}\, log_{2}n)    C.O(n)    D.O(n log_{2}n)

本题看着与【变形题2】很像,但外层循环条件不同

   

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度

\begin{matrix} \sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1 =\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(i)}+1) \approx \sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1) \approx \sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} + \sum\limits_{x=1}^{ \sqrt{n} }1 \end{matrix} \\[1.5ex] \begin{matrix} \approx log_{2}{((\sqrt{n}) !)} + \sqrt{n} \approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)+ (1-log_{2}(e))\sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2} \end{matrix}

3.时间复杂度T(n)=O(\sqrt{n}\, log_{2}n)

因为是嵌套相关循环,内外层循环相关,不能分开计算

但是可以先列出来求和式的上下限,再多重求和

  

因为外层循环条件不是以往的 i<=n,而是 i*i<=n

所以循环变量 i 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 i*i 的变化规律,将其当作数列

循环变量初始化:i=1

循环变量自增:i++

将循环变量 i 的值依次代入循环条件 i*i,探寻数列规律

i*i=a_{1}=1\times 1=1^{2} \\[1.5ex] i*i=a_{2}=2\times 2=2^{3} \\[1.5ex] i*i=a_{3}=3\times 3=3^{2} \\[1.5ex] \cdots \\[1.5ex] i*i=a_{x}=x\times x=x^{2} \\[1.5ex]

所以最后一次循环(第x次循环)时

i*i=x^{2}

又因为循环条件是 i*i\leqslant n

所以最后一次循环时 i*i=n,则

i*i=x^{2}= n

等式两边同时开平方,得到循环执行次数x的值

x=\sqrt{n}

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor \sqrt{n} \rfloor

所以外层循环的求和式上下限为:

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}

    

因为内层循环变量自增不是以往的 j++,而是 j*=2

所以循环变量 j 不能反映实际的循环执行次数

设循环的执行次数为 y 次

  

为探寻循环变量 j 的变化规律,将其当作数列

循环变量初始化:j=1,则首项 b_{1}=1

循环变量自增:j*=2,则公比 q=2 ,即等比数列

等比数列通项公式:b_{n}=b_{1}q^{n-1}=1\times 2^{n-1}=2^{n-1}

则循环变量 j 随着通项公式改变 

j=b_{1}=2^{1-1}=2^{0}=1 \\[1.5ex] j=b_{2}=2^{2-1}=2^{1}=2 \\[1.5ex] j=b_{3}=2^{3-1}=2^{2}=4 \\[1.5ex] \cdots \\[1.5ex] j=b_{y}=2^{y-1} \\[1.5ex]

所以最后一次循环(第y次循环)时

j=2^{y-1}

又因为循环条件是 j \leqslant i

所以最后一次循环时 j=i,则

j=2^{y-1}= i

等式两边同时取对数

y-1=log_{2}{(i)}

移项,得到循环执行次数y的值

y=log_{2}{(i)}+1

但是循环执行次数只能是整数,所以y还要向下取整

y=\lfloor log_{2}{(i)}+1\rfloor

令求和变量 y 从1到 \lfloor log_{2}{(i)}+1\rfloor,代表循环执行 \lfloor log_{2}{(i)}+1\rfloor

所以内层循环的求和式上下限为:

\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}

  

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1

双重求和可以加括号以区分

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1=\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor} (\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1)

可以类比复合函数,先计算括号内的

\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1=\overbrace{1+\cdots +1}^{\lfloor log_{2}{(i)}+1\rfloor} = \lfloor log_{2}{(i)}+1\rfloor

将括号内的求和式替换为计算结果,双重求和变为

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor} (\sum\limits_{y=1}^{\lfloor log_{2}{(i)}+1\rfloor}1)=\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\lfloor log_{2}{(i)}+1\rfloor

向下取整不容易计算,将其近似为

\sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(i)}+1)


注意,此时求和变量 x 与求和表达式中的 i 是不同符号,直接展开无意义

所以需要把 i 替换为关于求和变量 x 的表达式


前面已推导出最后一次循环(第x次循环)时

i*i=x^{2}

等式两边同时开平方(因为 i 的初值凑巧为1,所以二者才相等)

i=x

i 替换

\sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(i)}+1)=\sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1)

根据求和式的加法规则,可以拆分为

\sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1)=\sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} + \sum\limits_{x=1}^{ \sqrt{n} }1=\sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} + \sqrt{n}

下面将新求和式 \sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} 展开,可以看到是多个对数求和

\sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)}=log_{2}{(1)}+log_{2}{(2)}+\cdots+log_{2}{(\sqrt{n})}

根据对数的加法法则

log_{a}{(M)}+log_{a}{(N)}=log_{a}{(M\times N)}

则上述求和可以转为

\begin{matrix} log_{2}{(1)}+log_{2}{(2)}+\cdots+log_{2}{(\sqrt{n})}=log_{2}{(1\times 2\times \cdots\times \sqrt{n})} \end{matrix}

因为从1乘到 \sqrt{n} 可以写作阶乘 (\sqrt{n}\, ) !

1\times 2\times \cdots \times \sqrt{n}=(\sqrt{n}\, ) !

所以替换后

log_{2}{(1\times 2\times \cdots\times \sqrt{n}\, )} =log_{2}{((\sqrt{n}\, ) !)}

则新求和式

\sum\limits_{x=1}^{\sqrt{n}}log_{2}{(x)}=log_{2}{((\sqrt{n}\, ) !)}

则原求和式

\sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1)=\sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} + \sum\limits_{x=1}^{ \sqrt{n} }1=log_{2}{((\sqrt{n}\, ) !)} + \sqrt{n}

根据斯特林公式,阶乘n!的近似值为

n!\approx \sqrt{2\pi n}\ (\frac{n}{e})^{n}

则阶乘 (\sqrt{n}\, ) ! 的近似值为

(\sqrt{n}\,) ! \approx \sqrt{2\pi \sqrt{n}}\ (\frac{\sqrt{n}}{e})^{\sqrt{n}}

代入得

log_{2}{((\sqrt{n}\, ) !)}\approx log_{2}(\sqrt{2\pi \sqrt{n}}\ (\frac{\sqrt{n}}{e})^{\sqrt{n}})

根据对数的加法法则

log_{a}{(M\times N)}=log_{a}{(M)}+log_{a}{(N)}

上式可拆分为

log_{2}(\sqrt{2\pi \sqrt{n}}\ (\frac{\sqrt{n}}{e})^{\sqrt{n}})=log_{2}(\sqrt{2\pi \sqrt{n}}\, )+log_{2}((\frac{\sqrt{n}}{e})^{\sqrt{n}})

根据对数的幂法则

log_{a}{(M^{n})}=n\cdot log_{a}{(M)}

根号是 \frac{1}{2} 次幂,上式变为

log_{2}(\sqrt{2\pi \sqrt{n}}\, )+log_{2}((\frac{\sqrt{n}}{e})^{\sqrt{n}})\\[1.5ex] =log_{2}((2\pi \sqrt{n}\, )^{\frac{1}{2}})+log_{2}((\frac{\sqrt{n}}{e})^{\sqrt{n}})\\[1.5ex] =\frac{1}{2}\cdot log_{2}(2\pi \sqrt{n}\, )+\sqrt{n}\cdot log_{2}(\frac{\sqrt{n}}{e})\\[1.5ex]

  

将两项分开计算:

根据对数的加法法则、幂法则

log_{a}{(M\times N)}=log_{a}{(M)}+log_{a}{(N)}

log_{a}{(M^{n})}=n\cdot log_{a}{(M)}

第一项可拆分为

\frac{1}{2}\cdot log_{2}(2\pi \sqrt{n}\, )\\[1.5ex] =\frac{1}{2}\cdot [log_{2}{(2)}+log_{2}{(\pi)}+log_{2}{(\sqrt{n}\, )}]\\[1.5ex] =\frac{1}{2}\cdot [1+log_{2}{(\pi)}+log_{2}{(n^{\frac{1}{2} })}]\\[1.5ex] =\frac{1}{2}\cdot [1+log_{2}{(\pi)}+\frac{1}{2} \cdot log_{2}{(n)}]

  

根据对数的减法法则、幂法则

log_{a}{(\frac{M}{N})}=log_{a}{(M)}-log_{a}{(N)}

log_{a}{(M^{n})}=n\cdot log_{a}{(M)}

第二项可拆分为

\sqrt{n}\cdot log_{2}(\frac{\sqrt{n}}{e})\\[1.5ex] =\sqrt{n}\cdot [log_{2}(\sqrt{n}\, )-log_{2}(e)]\\[1.5ex] =\sqrt{n}\cdot [log_{2}(n^{\frac{1}{2} })-log_{2}(e)]\\[1.5ex] =\sqrt{n}\cdot [\frac{1}{2}\cdot log_{2}(n)-log_{2}(e)]

  

两项相加

\frac{1}{2}\cdot log_{2}(2\pi \sqrt{n}\, )+\sqrt{n}\cdot log_{2}(\frac{\sqrt{n}}{e})\\[1.5ex] =\frac{1}{2}\cdot [1+log_{2}{(\pi)}+\frac{1}{2} \cdot log_{2}{(n)}]+\sqrt{n}\cdot [\frac{1}{2}\cdot log_{2}(n)-log_{2}(e)]\\[1.5ex] =\frac{1}{2}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)-log_{2}(e)\cdot \sqrt{n}\\[1.5ex] =\frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)-log_{2}(e)\cdot \sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

则新求和式

\sum\limits_{x=1}^{\sqrt{n}}log_{2}{(x)}\\[1.5ex] =log_{2}{((\sqrt{n}\, ) !)}\\[1.5ex] \approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)-log_{2}(e)\cdot \sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

则原求和式

\sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1)\\[1.5ex] =\sum\limits_{x=1}^{ \sqrt{n} } log_{2}{(x)} + \sum\limits_{x=1}^{ \sqrt{n} }1\\[1.5ex] =log_{2}{((\sqrt{n}\, ) !)} + \sqrt{n}\\[1.5ex] \approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)-log_{2}(e)\cdot \sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}+\sqrt{n}\\[1.5ex] \approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)+ (\sqrt{n}-log_{2}(e)\cdot \sqrt{n}\, )+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}\\[1.5ex] \approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)+ (1-log_{2}(e))\cdot \sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

令常数 c_{1}=1-log_{2}{(e)}

令常数 c_{2} =\frac{1}{2}\cdot log_{2}{(\pi)}+\frac{1}{2}

语句频度近似值为

\begin{matrix} \sum\limits_{x=1}^{\lfloor \sqrt{n} \rfloor}\lfloor log_{2}{(i)}+1\rfloor \approx \sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(i)}+1)\approx \sum\limits_{x=1}^{ \sqrt{n} } (log_{2}{(x)}+1)\approx \frac{1}{2}\cdot \sqrt{n} \cdot log_{2}(n)+ c_{1}\sqrt{n}+\frac{1}{4}\cdot log_{2}{(n)}+c_{2} \end{matrix}

省略系数和无关项后,数量级为 \sqrt{n}\, log_{2}n,则时间复杂度为 O(\sqrt{n}\, log_{2}n)

  

    

【变形题4】下列程序段的时间复杂度是(C

int count=0;
for(int i=1; i<=n; i++)for(int j=1; j*j<=i; j++)count++;

A.O(n^{\frac{1}{2}})    B.O(n)    C.O(n^{\frac{3}{2}})    D.O(n^{2})

乍一看,像是把【2025年 第 1 题】内外颠倒了,实则不然

  

计算时间复杂度

1.基本语句:count++;

2.基本语句语句频度\begin{matrix} \sum\limits_{i=1}^{n} \sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor} 1=\sum\limits_{i=1}^{n} \lfloor \sqrt{i} \rfloor \approx \sum\limits_{i=1}^{n} \sqrt{i} \approx \int_{​{1}}^{​{n}}\sqrt{x}\ dx + \frac{\sqrt{1} + \sqrt{n}}{2} \approx \frac{2}{3}n^{\frac{3}{2}}+\frac{1}{2}n^{\frac{1}{2}}-\frac{1}{6}\\[1.5ex] \end{matrix}

3.时间复杂度T(n)=O(n^{\frac{3}{2}})

因为是嵌套相关循环,内外层循环相关,不能分开计算

但是可以先列出来求和式的上下限,再多重求和

  

因为外层循环条件是 i<=n,所以 i 能取的最大值(即求和上限)是:n

所以外层循环的求和式上下限为

\sum\limits_{i=1}^{n}

  

因为内层循环条件不是以往的 j<=i,而是 j*j<=i

所以循环变量 j 不能反映实际的循环执行次数

设循环的执行次数为 x 次

  

为探寻循环条件 j*j 的变化规律,将其当作数列

循环变量初始化:j=1

循环变量自增:j++

将循环变量 j 的值依次代入循环条件 j*j,探寻数列规律

j*j=a_{1}=1\times 1=1^{2}=1 \\[1.5ex] j*j=a_{2}=2\times 2=2^{2}=4 \\[1.5ex] j*j=a_{3}=3\times 3=3^{2}=9 \\[1.5ex] \cdots \\[1.5ex] j*j=a_{x}=x\times x=x^{2} \\[1.5ex]

所以最后一次循环(第x次循环)时

j*j=x^{2}

又因为循环条件是 j*j \leqslant i

所以最后一次循环时 j*j=i,则

j*j=x^{2}=i

等式两边同时开平方,得到循环执行次数x的值

x=\sqrt{i}

但是循环执行次数只能是整数,所以x还要向下取整

x=\lfloor \sqrt{i} \rfloor

令求和变量 x 从1到 \lfloor \sqrt{i} \rfloor,代表循环执行 \lfloor \sqrt{i} \rfloor

所以内层循环的求和式上下限为:

\sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor}

  

二层循环体只有1条基本语句,所以求和表达式(通项公式)是1

二层循环(双重循环)计算语句频度需要写成双重求和

\sum\limits_{i=1}^{n} \sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor} 1

双重求和可以加括号以区分

\sum\limits_{i=1}^{n} \sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor} 1=\sum\limits_{i=1}^{n} (\sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor} 1)

可以类比复合函数,先计算括号内的

\sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor}1=\overbrace{1+\cdots +1}^{\lfloor \sqrt{i} \rfloor} = \lfloor \sqrt{i} \rfloor

将括号内的求和式替换为计算结果,双重求和变为

\sum\limits_{i=1}^{n} (\sum\limits_{x=1}^{\lfloor \sqrt{i} \rfloor} 1)=\sum\limits_{i=1}^{n} \lfloor \sqrt{i} \rfloor

向下取整不容易计算,将其近似为

\sum\limits_{i=1}^{n} \lfloor \sqrt{i} \rfloor \approx \sum\limits_{i=1}^{n} \sqrt{i}

再展开,可以看到是多个根号求和

\sum\limits_{i=1}^{n} \sqrt{i}=\sqrt{1}+\cdots +\sqrt{n}

既不是等差数列也不是等比数列,这怎么办呢?

将根号看作 \frac{1}{2} 次幂,上式变为

\sum\limits_{i=1}^{n} i^{\frac{1}{2}}=1^{\frac{1}{2}}+\cdots +n^{\frac{1}{2}}

等幂求和 可以用 欧拉—麦克劳林公式 近似计算

(欧拉—麦克劳林公式是连接 离散求和连续积分 的桥梁)

欧拉—麦克劳林公式

\begin{matrix} \sum\limits_{​{i=a}}^{​{b}}f(i) = \int_{​{a}}^{​{b}}f(x)dx + \frac{f(a) + f(b)}{2} + \sum\limits_{​{k=1}}^{​{m }}\frac{​{B_{​{2k}}}}{​{(2k)!}}\left[f^{​{(2k-1)}}(b) - f^{​{(2k-1)}}(a)\right] + R_{m} \end{matrix}


* 修正项:\sum\limits_{​{k=1}}^{m}\frac{​{B_{​{2k}}}}{​{(2k)!}}\left[f^{​{(2k-1)}}(b) - f^{​{(2k-1)}}(a)\right],其中 B_{2k} 是伯努利数(如 B_{2}=\frac{1}{6}B_{4}=-\frac{1}{30}

* 剩余项:R_{m}= \frac{(-1)^{m+1} }{(2m)!}\int_{a}^{b} B_{2m}( \{ x \} ) f^{(2m)}(x) dx ,其中 \{x \}=x-\lfloor x\rfloor, 代表取小数(保留小数部分,忽略整数部分) 

只作近似计算,忽略修正项和剩余项,上式约等于

\sum\limits_{​{i=a}}^{​{b}}f(i) \approx \int_{​{a}}^{​{b}}f(x)dx + \frac{f(a) + f(b)}{2}

将 a=1,\ b=n,\ f(i)=i^{\frac{1}{2}} 代入得语句频度

\sum\limits_{i=1}^{n} i^{\frac{1}{2}} \approx \int_{​{1}}^{​{n}}x^{\frac{1}{2}} dx + \frac{1^{\frac{1}{2}} + n^{\frac{1}{2}}}{2} \\[1.5ex] \approx \int_{​{1}}^{​{n}}x^{\frac{1}{2}} dx+\frac{n^{\frac{1}{2}}}{2}+\frac{1}{2} \\[1.5ex] \approx \frac{ x^{\frac{3}{2}} }{\frac{3}{2}} |_{1}^{n}+\frac{1}{2}n^{\frac{1}{2}}+\frac{1}{2} \\[1.5ex] \approx \frac{2}{3}x^{\frac{3}{2}}|_{1}^{n}+\frac{1}{2}n^{\frac{1}{2}}+\frac{1}{2}\\[1.5ex] \approx \frac{2}{3}(n^{\frac{3}{2}}-1^{\frac{3}{2}})+\frac{1}{2}n^{\frac{1}{2}}+\frac{1}{2}\\[1.5ex] \approx \frac{2}{3}n^{\frac{3}{2}}-\frac{2}{3}+\frac{1}{2}n^{\frac{1}{2}}+\frac{1}{2}\\[1.5ex] \approx \frac{2}{3}n^{\frac{3}{2}}+\frac{1}{2}n^{\frac{1}{2}}-\frac{1}{6}\\[1.5ex]

省略系数和无关项后,数量级为 n^{\frac{3}{2}},则时间复杂度为 O(n^{\frac{3}{2}})

    

    

八、递归题

 【递归题1 - 2026王道数据结构第11题】下列程序段的时间复杂度是(C

int Func(int n)
{if(n==1) return 1;else return 2*Func(n/2)+n;
}

A.O(n)    B.O(nlog_{2}n)    C.O(log_{2}n)    D.O(n^{2})

*为了方便描述,后续均用f(n)指代Func(n)

计算时间复杂度

1.基本语句

return 1;

return 2*f(n/2)+n;

2.基本语句语句频度log_{2}(n)+1

3.时间复杂度T(n)=O(log_{2}n)

递归函数无法按常规方式计算语句频度

但是可以转换思路,将递归过程模拟为二叉树

一个递归函数当作二叉树的一个节点

函数f(n)递归调用的是f(n/2)

从二叉树的视角来看,就是该节点连接n/2层的一个孩子

所以每个节点(不算叶子节点)都只有一个孩子,最终得到"单叉"的二叉树

其实这棵"单叉"的二叉树就是线性表(或称二叉树退化为线性表)

纵向可能看不出来是线性表,改成横向就一目了然

例如:f(8)的递归二叉树(退化为线性表)如下图

因为函数参数n定义为int类型

如果调用的实参不是整数,则会对其向下取整

所以调用 f(\frac{n}{2}) ,实际上是调用 f( \left \lfloor \frac{n}{2} \right \rfloor)

为了简化分析,仅考虑偶数情况,避免向下取整造成影响

当n为偶数时,f(n)递归过程如下

f(n)\rightarrow f(\frac{n}{2})\rightarrow f(\frac{n}{4})\rightarrow \cdots \rightarrow f(1)

从前往后看,当作数列

n,\ \frac{n}{2},\ \frac{n}{4},\ \cdots,\ 1

则为等比数列

a_{1}=n,\ a_{2}=\frac{n}{2},\ a_{3}=\frac{n}{4},\ \cdots,\ a_{m}=1

首项 a_{1}=n,公比 q=\frac{1}{2}

等比数列通项公式 a_{m}=a_{1}q^{m-1}=n\times (\frac{1}{2})^{m-1}

代入最后一项 

a_{m}=n\times (\frac{1}{2})^{m-1}=1

等式两边同时除以n

(\frac{1}{2})^{m-1}=\frac{1}{n}

倒数可以写成-1次幂

(2^{-1})^{m-1}=n^{-1}

将等式左边展开

2^{-m+1}=n^{-1}

等式两边同时取对数

-m+1=log_{2}(n^{-1})

根据对数的幂法则

log_{a}{(M^{n})}=n\cdot log_{a}{(M)}

将幂次提出

-m+1=-log_{2}(n)

等式两边同时取相反数

m-1=log_{2}(n)

移项得

m=log_{2}(n)+1

数列从a_{1} 到 a_{m} 共m项,则递归过程有m层

又因为退化为线性表,所以总节点数就是 m=log_{2}(n)+1

再看函数体,无论进 if 还是进 else,都只会执行1条语句

所以递归函数内基本语句语句频度是1

所以 语句频度之和 = 线性表总节点数 =log_{2}(n)+1

省略系数和无关项后,数量级为 log_{2}n,则时间复杂度为 O(log_{2}n)

   


或者使用主定理(Master定理)进行推导

主定理(Master定理)

一、递归函数的时间复杂度满足以下递推公式:

T(n)= \left\{\begin{matrix} a\cdot T(\frac{n}{b})+f(n)&,\ n>n_{0}\\[1.5ex] O(1)&,\ n=n_{0} \end{matrix}\right.  (其中 a\geqslant 1b>1n_{0}是递归结束条件)

  • n问题规模

  • a:每次递归调用的子问题数量(即子递归调用次数)

  • \frac{n}{b}:每个子问题的规模(必须相等)

  • f(n):非递归操作的语句频度

   

令非递归操作的时间复杂度 O(f(n))=O(n^{d})

这是简化分析,假定其时间复杂度为常见的幂函数,方便后续只比较指数 d

但如果时间复杂度确实不是幂函数,则需要整体比较

将 f(n) 用时间复杂度 O(n^{d}) 来近似估计,则递推公式变为

T(n)= \left\{\begin{matrix} a\cdot T(\frac{n}{b})+O(n^{d})&,\ n>n_{0}\\[1.5ex] O(1)&,\ n=n_{0} \end{matrix}\right.  (其中 a\geqslant 1b>1n_{0}是递归结束条件)

递归操作的时间复杂度经复杂推导(过程略)可近似为 O(n^{log_{b}\,a}) 

    

二、应用条件

1、子问题规模相等‌:原问题需被分割成规模相同的子问题(如归并排序的 \frac{n}{2} 分割)

2、非递归操作的时间复杂度O(n^{d}) 中的 d 必须为常数,不能随 n 变化

   

三、主定理判断时间复杂度的三种情况

实际上是看递归操作 O(n^{log_{b}\,a}) 和 非递归操作 O(n^{d}) 哪个占主导

1、递归主导

d<log_{b}\,a 时,递归操作耗时占主导,则递归函数的时间复杂度 T(n)=O(n^{log_{b}\,a})

2、平衡状态

d=log_{b}\,a 时,递归与非递归操作耗时相当,则递归函数的时间复杂度 T(n)=O(n^{d}\,log\,n)

3、非递归主导

d>log_{b}\,a 时,非递归操作耗时占主导

还需满足条件:存在常数 c\ (c<1),使得不等式 a\cdot f(\frac{n}{b}) \leqslant c \cdot f(n) 成立

则递归函数的时间复杂度 T(n)=O(n^{d})

再把代码贴过来,对本题进行分析

int Func(int n)
{if(n==1) return 1;else return 2*Func(n/2)+n;
}

*为了方便描述,后续均用f(n)指代Func(n)

  

肯定有人一上来就直接列式

T(n)= \left\{\begin{matrix} 2T(\frac{n}{2})+O(n)&,\ n>1\\[1.5ex] O(1)&,\ n=1\end{matrix}\right.

殊不知中了出题人的圈套,把 表达式求值时间复杂度 混为一谈了

     

下面从头开始分析

1、先看子问题数量:a

其实 子问题数量 就是 子递归的调用次数

当前递归函数f(n)只调用一次子递归f(n/2),所以子问题数量 a=1,满足条件 a \geqslant 1

 

有人看到 2*f(n/2) 可能会误认为子问题数量 a=2

这就是将 函数返回值函数调用 混淆了

2*f(n/2) 只是让函数返回值参与运算,仅调用了一次子递归f(n/2)

假设当前子递归f(n/2)的返回值是4,则 2*f(n/2)=2*4=8

如果要调用两次子递归得把f(n/2)写两遍,比如 2*f(n/2)*f(n/2)

  

2、再看子问题规模\frac{n}{b}

从子递归调用f(n/2)可以看出子问题规模是 \frac{n}{2} ,则 b=2,满足条件 b>1

   

3、最后看非递归操作的时间复杂度O(n^{d})

看函数体,无论进 if 还是进 else,都只会执行1条非递归操作语句

所以非递归操作语句频度是1,数量级为1,时间复杂度O(1)=O(n^{0}),则 d=0

 

有人看到 +n 可能会误认为非递归操作的时间复杂度是 O(n)

这就是将 变量运算 和 语句频度 混淆了

2*Func(n/2)+n 这个加法运算整体只是1条语句,其语句频度是1

如果要实现非递归操作的时间复杂度是 O(n) ,得把 +n 放到循环里面,比如

sum=2*Func(n/2);

for(i=1; i<=n; i++) { sum+=n; }

return sum;

  

通过上面的分析,得到正确的递推公式为

T(n)= \left\{\begin{matrix} T(\frac{n}{2})+O(1)&,\ n>1\\[1.5ex] O(1)&,\ n=1\end{matrix}\right.

此时 a=1,\ b=2,\ d=0 ,则 log_{b}\,a=log_{2}\,1=0 

所以 d=log_{b}\,a=0,满足第二种情况(平衡状态)

则递归函数的时间复杂度 T(n)=O(n^{d}\,log\,n)=O(n^{0}\,log\,n)=O(log\,n)

因为对数阶可以省略底数,所以两种方式推导出的时间复杂度相同: O(log_{2}n)=O(log\,n)

    

   

 【递归题2】下列程序段的时间复杂度是(C

double f(int n)
{if(n==1) return 1;else{int i;double count=0;for(i=1; i<=n; i++){count+=f(n-1);}return count; }
}

A.O(n)    B.O(n^{2})    C.O(2^{n})    D.O(n!)

计算时间复杂度

1.基本语句

return 1;

count+=f(n-1);

2.基本语句语句频度\sum\limits_{i=1}^{n} A_{n}^{i}=\sum\limits_{i=1}^{n}\frac{n!}{(n-i)!}=n!\sum\limits_{i=1}^{n}\frac{1}{(n-i)!}=n!\sum\limits_{k=0}^{n-1}\frac{1}{k!}\approx n!\cdot e \approx e\cdot n!

3.时间复杂度T(n)=O(n!)

递归函数无法按常规方式计算语句频度

但是可以转换思路,将递归过程模拟为树(这里就不是二叉树了,而是n叉树)

一个递归函数当作树的一个节点

本题递归过程为

f(n)\rightarrow f(n-1)\rightarrow f(n-2)\rightarrow \cdots \rightarrow f(2) \rightarrow f(1)

则递归树共有n层

 

f(n)是递归树的根结点,则第1层的节点数为:1

f(n)通过循环递归调用n次 f(n-1),也就是n个 f(n-1)

从树的视角来看,就是该节点连接第2层的n个孩子,则第2层节点数为:1\times n=n

对于这 n 个f(n-1) ,其中每个f(n-1)通过循环递归调用n-1次 f(n-2),也就是n-1个 f(n-2)

从树的视角来看,就是第2层的每个节点都连接第3层的n-1个孩子,则第3层节点数为: n\times (n-1)

如下图所示

      

为探寻节点数的规律,将 {节点数} 当作数列

第1层:根节点,节点数为 a_{1}=1

第2层:节点数为 a_{2}=1\times n=n

第3层:节点数为 a_{3}=n\times (n-1)

第4层:节点数为 a_{4}=n\times (n-1) \times (n-2)

第5层:节点数为 a_{5}=n\times (n-1) \times (n-2) \times (n-3)

……

第n层:节点数为 a_{n}= n\times (n-1) \times (n-2) \times (n-3)\times \cdots \times 2 

  

既不是等差数列也不是等比数列,这怎么办呢?

可以看到节点数从n逐渐累乘至2,有没有想到什么?

其实就是高中学过的排列数

A_{n}^{m}=\frac{n!}{(n-m)!}=n\times (n-1) \times (n-2)\times \cdots \times (n-m+1)

所以{节点数} 数列的规律可以用排列数表示

第1层:根节点,节点数为 a_{1}=1=\frac{n!}{(n-0)!}=A_{n}^{0}

第2层:节点数为 a_{2}=1\times n=n=\frac{n!}{(n-1)!}=A_{n}^{1}

第3层:节点数为 a_{3}=n\times (n-1)=\frac{n!}{(n-2)!}=A_{n}^{2}

第4层:节点数为 a_{4}=n\times (n-1) \times (n-2)=\frac{n!}{(n-3)!}=A_{n}^{3}

第5层:节点数为 a_{5}=n\times (n-1) \times (n-2) \times (n-3)=\frac{n!}{(n-4)!}=A_{n}^{4}

……

第n层:节点数为 \begin{matrix} a_{n}=n\times (n-1) \times (n-2) \times (n-3)\times \cdots \times 2=\frac{n!}{(n-(n-1))!}=A_{n}^{n-1} \end{matrix}

  

再看函数体

①进 if 时,对应第n层的叶子节点f(1)

只执行1条语句,节点语句频度为1

 

②进else时,对应其他层的节点

进入for循环,for循环内执行非递归的加法语句,循环几次就执行几次

所以节点语句频度=循环次数,循环次数随问题规模改变

为探寻循环次数的规律,将 {循环次数} 当作数列

第1层:对应f(n),问题规模为n,循环次数为 b_{1}=n

第2层:对应f(n-1),问题规模为n-1,循环次数为 b_{2}=n-1

第3层:对应f(n-2),问题规模为n-2,循环次数为 b_{3}=n-2

第4层:对应f(n-3),问题规模为n-3,循环次数为 b_{4}=n-3

第5层:对应f(n-4),问题规模为n-4,循环次数为 b_{5}=n-4

……

第n-1层:对应f(2),问题规模为2,循环次数为 b_{n-1}=2

(为什么不写第n层?因为第n层对应f(1),进的是if,前面已得出节点语句频度为1)

可以看出 {循环次数} 数列是等差数列

首项 b_{1}=n,公差 d=-1

等差数列通项公式 b_{m}=b_{1}+(m-1)d=n+(m-1)(-1)=n+1-m

所以节点语句频度=循环次数=n+1-m  (其中m是当前层数)

    

前面我们已经推导出 每层的节点数,这里又推导出 节点语句频度,则

当前层语句频度=当前层节点数 x 当前层节点语句频度=当前层节点数 x 当前层循环次数(即 c_{n}=a_{n}\times b_{n}

为探寻当前层语句频度的规律,将 {当前层语句频度} 当作数列

第1层:当前层语句频度

c_{1}=a_{1}\times b_{1}=1\times (n+1-1)=1\times n=n=\frac{n!}{(n-1)!}=A_{n}^{1}

第2层:当前层语句频度

c_{2}=a_{2}\times b_{2}=n \times (n+1-2) = n\ \times (n-1) =\frac{n!}{(n-2)!}=A_{n}^{2}

第3层:当前层语句频度为 

\begin{matrix} c_{3}=a_{3}\times b_{3}=n\times (n-1)\times (n+1-3)=n\times (n-1)\times (n-2)=\frac{n!}{(n-3)!}=A_{n}^{3} \end{matrix}

第4层:当前层语句频度为 

c_{4}=a_{4}\times b_{4}\\[1.5ex] =n\times (n-1) \times (n-2) \times (n+1-4)\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3)=\frac{n!}{(n-4)!}=A_{n}^{4}

第5层:当前层语句频度为 

c_{5}=a_{5}\times b_{5}\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3) \times (n+1-5)\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3) \times (n-4)=\frac{n!}{(n-5)!}=A_{n}^{5}

……

 

第n-1层:当前层语句频度

 c_{n-1}=a_{n-1}\times b_{n-1}\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3)\times (n-4)\times \cdots \times [n+1-(n-1)]\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3)\times (n-4)\times \cdots \times 2=\frac{n!}{(n-(n-1))!}=A_{n}^{n-1}

第n层:当前层语句频度为 (对应 if 情况,直接代入1)

c_{n}=a_{n}\times b_{n}\\[1.5ex] =n\times (n-1) \times (n-2) \times (n-3)\times (n-4)\times \cdots \times 2 \times 1=\frac{n!}{(n-n)!}=A_{n}^{n}

求整个递归函数的总语句频度,就是将第1层到第n层的当前层语句频度相加

相当于对 {当前层语句频度} 这个数列求和

S_{n}=\sum\limits_{i=1}^{n} A_{n}^{i}

用阶乘表示为

\sum\limits_{i=1}^{n} A_{n}^{i}=\sum\limits_{i=1}^{n}\frac{n!}{(n-i)!}

这里的 n! 与求和变量 i 无关,相当于常数,可以将其提出

\sum\limits_{i=1}^{n}\frac{n!}{(n-i)!}=n!\sum\limits_{i=1}^{n}\frac{1}{(n-i)!}

为了简化分母表示,设 k=n-i

因为 i 的变化规律是

1,2,3,\cdots ,(n-1),n

所以 k 的变化规律是

(n-1),(n-2),(n-3),\cdots ,(n-(n-1)), (n-n)\\[1.5ex] =(n-1),(n-2),(n-3),\cdots ,1,0

因为有加法交换律,所以求和变量 从n-1到0 与 从0到n-1 的求和结果相同

将上式改为用求和变量 k 表示的新求和式

n!\sum\limits_{i=1}^{n}\frac{1}{(n-i)!}=n!\sum\limits_{k=0}^{n-1}\frac{1}{k!}

《高等数学》学过自然指数函数的麦克劳林展开为

e^{x}=\frac{x^{0}}{0!}+\frac{x^{1}}{1!}+\frac{x^{2}}{2!}+\frac{x^{3}}{3!}+\frac{x^{4}}{4!}+\cdots=\sum\limits_{k=0}^{\infty}\frac{x^{k}}{k!}

当 x=1 时,即为自然对数底e

e=e^{1}=\frac{1}{0!}+\frac{1}{1!}+\frac{1}{2!}+\frac{1}{3!}+\frac{1}{4!}+\cdots=\sum\limits_{k=0}^{\infty}\frac{1}{k!}

问题规模(n) 的取值很大时,可近似为

\sum\limits_{k=0}^{n-1}\frac{1}{k!} \approx \sum\limits_{k=0}^{\infty}\frac{1}{k!} \approx e

最终递归函数的总语句频度近似为

n!\sum\limits_{k=0}^{n-1}\frac{1}{k!} \approx n!\cdot e \approx e\cdot n!

省略系数和无关项后,数量级为 n!,则时间复杂度为 O(n!)

  

O(n!) 可以称作 阶乘阶,将其与指数阶 O(2^{n}) 进行比较

可以看到 n! 和 2^{n} 这两个函数在第一象限有两个交点

一个交点在 (0,1) 

另一个交点在 n \in[3,4] 区间内

在第二次相交后,n! 的增长速率明显要比 2^{n} 快 

所以当问题规模(n)趋于无穷大时:O(n!)>O(2^{n})

  


或者尝试使用主定理(Master定理)进行推导

再把代码贴过来,对本题进行分析

double f(int n)
{if(n==1) return 1;else{int i;double count=0;for(i=1; i<=n; i++){count+=f(n-1);}return count; }
}

下面从头开始分析

1、先看子问题数量:a

其实 子问题数量 就是 子递归的调用次数

当前递归函数f(n)在for循环中调用n次子递归f(n-1),所以子问题数量 a=n,满足条件 a \geqslant 1

     

2、再看子问题规模\frac{n}{b}

从子递归调用f(n-1)可以看出子问题规模是 n-1 ,此时可以当作 b=1不满足条件 b>1

所以不能使用主定理

  

3、最后看非递归操作的时间复杂度O(n^{d})

进 if 时,对应第n层的叶子节点f(1)

只执行1条语句,节点语句频度为1,数量级为1,时间复杂度O(1)=O(n^{0}),此时 d=0

进else时,对应其他层的节点

进入for循环,for循环内执行非递归的加法语句,循环几次就执行几次

所以语句频度=循环次数,循环次数随问题规模改变

初始节点语句频度为n,数量级为n,时间复杂度O(n)=O(n^{1}),此时 d=1

  

虽然不能使用主定理,但是我们可以把递推公式写出来

T(n)= \left\{\begin{matrix} nT(n-1)+O(n)&,\ n>1\\[1.5ex] O(1)&,\ n=1\end{matrix}\right.

(下面使用递推公式进行不严谨的推导)

问题规模减小,列出时间复杂度

T(n)= nT(n-1)+O(n)\\[1.5ex] T(n-1)= (n-1)T(n-2)+O(n-1)\\[1.5ex] T(n-2)= (n-2)T(n-3)+O(n-2)\\[1.5ex] \cdots \\[1.5ex] T(2)= 2T(1)+O(2)\\[1.5ex] T(1)=O(1)

将上述时间复杂度逐个代入T(n)

T(n)= nT(n-1)+O(n)\\[1.5ex] \begin{matrix} = n((n-1)T(n-2)+O(n-1))+O(n) \end{matrix} \\[1.5ex] \begin{matrix} = n((n-1)((n-2)T(n-3)+O(n-2))+O(n-1))+O(n) \end{matrix} \\[1.5ex] \begin{matrix} \cdots \end{matrix} \\[1.5ex] \begin{matrix} = n((n-1)((n-2)((n-3)\cdots (2T(1)+O(2)) +\cdots +O(n-3))+O(n-2))+O(n-1))+O(n) \end{matrix} \\[1.5ex] \begin{matrix} = n((n-1)((n-2)((n-3)\cdots (2O(1)+O(2)) +\cdots +O(n-3))+O(n-2))+O(n-1))+O(n) \end{matrix} \\[1.5ex]

将上式拆开,改用阶乘表示

\begin{matrix} n((n-1)((n-2)((n-3)\cdots (2O(1)+O(2)) +\cdots +O(n-3))+O(n-2))+O(n-1))+O(n) \end{matrix} \\[1.5ex] \begin{matrix} = n(n-1)((n-2)((n-3)\cdots (2O(1)+O(2)) +\cdots +O(n-3))+O(n-2))+n\cdot O(n-1)+O(n) \end{matrix} \\[1.5ex] \begin{matrix} = n(n-1)(n-2)((n-3)\cdots (2O(1)+O(2)) +\cdots +O(n-3))+n\cdot (n-1)\cdot O(n-2)+n\cdot O(n-1)+O(n) \end{matrix} \\[1.5ex] \begin{matrix} = n(n-1)(n-2)(n-3)\cdots (2O(1)+O(2)) +\cdots +n\cdot (n-1)\cdot (n-2)\cdot O(n-3)+ n\cdot (n-1)\cdot O(n-2)+n\cdot O(n-1)+O(n) \end{matrix} \\[1.5ex] \begin{matrix} =\frac{n!}{1!}\cdot O(1)+ \frac{n!}{2!}\cdot O(2)+\cdots+\frac{n!}{(n-3)!}\cdot O(n-3) +\frac{n!}{(n-2)!}\cdot O(n-2) +\frac{n!}{(n-1)!}\cdot O(n-1)+\frac{n!}{n!}\cdot O(n) \end{matrix} \\[1.5ex]

假设时间复杂度可以有如下运算规则(其中n是问题规模,c是常数)

 n\cdot O(1)=O(n)\\[1.5ex] n!\cdot O(1)=O(n!)\\[1.5ex] c\cdot O(1)=O(c)=O(1)\\[1.5ex] c\cdot O(n)=O(c\cdot n)=O(n)\\[1.5ex] c\cdot O(n!)=O(c\cdot n!)=O(n!)\\[1.5ex]

则上式变为

\begin{matrix} \frac{n!}{1!}\cdot O(1)+ \frac{n!}{2!}\cdot O(2)+\cdots+\frac{n!}{(n-3)!}\cdot O(n-3) +\frac{n!}{(n-2)!}\cdot O(n-2) +\frac{n!}{(n-1)!}\cdot O(n-1)+\frac{n!}{n!}\cdot O(n) \end{matrix} \\[1.5ex] \begin{matrix} =\frac{n!}{1!}\cdot 1\cdot O(1)+ \frac{n!}{2!}\cdot 2\cdot O(1)+\cdots +\frac{n!}{(n-3)!}\cdot (n-3)\cdot O(1)+\frac{n!}{(n-2)!}\cdot (n-2)\cdot O(1) +\frac{n!}{(n-1)!}\cdot (n-1)\cdot O(1)+\frac{n!}{n!}\cdot n \cdot O(1) \end{matrix} \\[1.5ex] \begin{matrix} =\frac{n!}{0!}\cdot O(1)+ \frac{n!}{1!}\cdot O(1)+\cdots+\frac{n!}{(n-4)!}\cdot O(1) +\frac{n!}{(n-3)!}\cdot O(1) +\frac{n!}{(n-2)!}\cdot O(1)+\frac{n!}{(n-1)!}\cdot O(1) \end{matrix} \\[1.5ex] \begin{matrix} =(\frac{n!}{0!}+ \frac{n!}{1!}+\cdots+\frac{n!}{(n-4)!} ++\frac{n!}{(n-3)!}+\frac{n!}{(n-2)!}+\frac{n!}{(n-1)!} )\cdot O(1) \end{matrix}\\[1.5ex] \begin{matrix} =(\sum\limits_{k=0}^{n-1}\frac{n!}{k!})\cdot O(1) \end{matrix}\\[1.5ex] \begin{matrix} =(n!\sum\limits_{k=0}^{n-1}\frac{1}{k!})\cdot O(1) \end{matrix}\\[1.5ex] \begin{matrix} =n!\cdot O(1) \cdot \sum\limits_{k=0}^{n-1}\frac{1}{k!} \end{matrix}\\[1.5ex] \begin{matrix} =O(n!) \cdot \sum\limits_{k=0}^{n-1}\frac{1}{k!} \end{matrix}\\[1.5ex]

问题规模(n) 的取值很大时,上式可近似为

O(n!) \cdot \sum\limits_{k=0}^{n-1}\frac{1}{k!}\\[1.5ex] \approx O(n!) \cdot \sum\limits_{k=0}^{\infty}\frac{1}{k!}\\[1.5ex] \approx O(n!) \cdot e\\[1.5ex] \approx e\cdot O(n!)\\[1.5ex] \approx O(e\cdot n!)\\[1.5ex] \approx O(n!)\\[1.5ex]

自然对数底e是常数,将其省略后,时间复杂度O(n!)

http://www.dtcms.com/a/321250.html

相关文章:

  • BBH详解:面向大模型的高阶推理评估基准与数据集分析
  • 轻松实现浏览器自动化——AI浏览器自动化框架Stagehand
  • 力扣 hot100 Day69
  • 使用 PicGo 与 GitHub 搭建高效图床,并结合 Local Images Plus 备份原图
  • 杂谈 001 · VScode / Copilot 25.08 更新
  • 供电架构之供电构型分类
  • 浪漫沙迦2|浪漫沙加2 七英雄的复仇 送修改器(Romancing SaGa 2)免安装中文版
  • 机器视觉任务(目标检测、实例分割、姿态估计、多目标跟踪、单目标跟踪、图像分类、单目深度估计)常用算法及公开数据集分享
  • excel 导出
  • 【vue】Vue 重要基础知识清单
  • Numpy科学计算与数据分析:Numpy广播机制入门与实践
  • 使用FinTSB框架进行金融时间序列预测的完整指南
  • 算法提升之-启发式并查集
  • 剪映里面导入多张照片,p图后如何再导出多张照片?
  • VScode 文件标签栏多行显示
  • QML中显示二级界面的三种方式
  • 【Git】企业级使用
  • electron自定义国内镜像
  • 静电释放场景误报率↓78%!陌讯多模态融合算法在工业检测的落地优化
  • 【unity实战】用unity实现一个简易的战斗飞机控制器
  • BUG调试案例十七:ENC424J600以太网掉线问题案例
  • uniapp瀑布流最简单的实现方法
  • SonarQube 扫描多个微服务模块
  • 【51单片机2个按键控制流水灯转向】2022-10-25
  • 移动端开发中类似腾讯Bugly的产品推荐与比较-5款APP异常最终产品推荐-卓伊凡|bigniu
  • springBoot集成minio并实现文件的上传下载
  • 华为网路设备学习-28(BGP协议 三)路由策略
  • 怎么实现对三菱PLC的远程调试和PLC远程维护?
  • 【世纪龙科技】数智重构车身实训-汽车车身测量虚拟实训软件
  • 矩阵中的最长递增路径-记忆化搜索