DSA之一时间复杂度
一 问题重述
我们有以下代码片段:
int m = 0, i, j;
for (i = 1; i <= n; i++)
for (j = 1; j <= 2 * i; j++)
m++;
问题是:画线的语句(即 m++;)的执行次数是多少
理解问题
我们需要计算 m++; 语句的执行次数。这取决于两个嵌套的 for 循环的执行情况。
-
外层循环:
for (i = 1; i <= n; i++)
这个循环从i = 1到i = n,共执行n次。 -
内层循环:
for (j = 1; j <= 2 * i; j++)
对于每一个i,内层循环的执行次数取决于2 * i。即,当i = 1时,j从 1 到 2,执行 2 次;i = 2时,j从 1 到 4,执行 4 次;...;i = n时,j从 1 到2n,执行2n次。
因此,m++; 的总执行次数是内层循环在所有外层循环迭代中的执行次数的总和。
计算执行次数
让我们计算内层循环的总执行次数:
对于 i 从 1 到 n,内层循环的执行次数为 2 * i。因此,总次数 T 为:
[
T = \sum_{i=1}^{n} 2i = 2 \sum_{i=1}^{n} i = 2 \cdot \frac{n(n+1)}{2} = n(n+1)
]
这里用到了等差数列求和公式:
[
\sum_{i=1}^{n} i = \frac{n(n+1)}{2}
]
因此,m++; 的执行次数为 n(n+1)。
验证
让我们用具体的 n 值来验证:
-
当
n = 1:i = 1:内层循环执行2 * 1 = 2次。- 总次数:2。
- 根据公式:
1 * (1 + 1) = 2。匹配。
-
当
n = 2:i = 1:内层循环执行 2 次。i = 2:内层循环执行 4 次。- 总次数:2 + 4 = 6。
- 根据公式:
2 * (2 + 1) = 6。匹配。
-
当
n = 3:i = 1:2 次。i = 2:4 次。i = 3:6 次。- 总次数:2 + 4 + 6 = 12。
- 根据公式:
3 * (3 + 1) = 12。匹配。
验证通过。
结论
正确的执行次数是 ( n(n+1) ),
最终答案
( n(n+1) )
二、递归树和时间复杂度
递归树的深度计算
递归树的深度是指递归调用的层数,它可以通过分析递归的分解方式来计算。我们以你提供的代码为例来详细解释如何计算递归树的深度。
递归函数分析:
int Func(int n) {
if (n == 1) return 1;
else return 2 * Func(n / 2) + n;
}
在这个递归函数中:
- 基本情况:当 $n = 1$ 时,返回 1。
- 递归情况:当 $n > 1$ 时,递归调用函数 $\text{Func}(n / 2)$ 两次,并且还要加上 $n$ 。
如何构造递归树:
递归树是递归函数执行过程中每个调用的一个图形化表示。每一个节点代表一次递归调用,每个节点的子节点代表递归中对该节点的调用。
递归树的层次(深度):
递归树的深度与递归函数分解问题的方式密切相关。在我们的例子中,每次递归调用时,输入规模都会变为原来的一半,即 $n \to n/2$。
递归树深度的计算:
- 初始调用:假设我们开始时调用 $\text{Func}(n)$。
- 递归拆分:每次递归都会将 $n$ 变为 $n/2$,所以递归会继续直到 $n$ 变成 1。
- 递归的停止条件:递归在 $n = 1$ 时终止。
递归的深度就是我们需要多少次递归调用才能将 $n$ 减少到 1。每次调用时,参数 $n$ 被除以 2,因此递归的深度是由以下的公式给出的:
$$
\text{递归深度} = \log_2 n
$$
递归树深度的例子:
假设我们调用 $\text{Func}(8)$:
- 第 1 层,调用 $\text{Func}(8)$。
- 第 2 层,调用 $\text{Func}(4)$ 和 $\text{Func}(4)$(因为每次递归都调用两次)。
- 第 3 层,调用 $\text{Func}(2)$, $\text{Func}(2)$, $\text{Func}(2)$, $\text{Func}(2)$。
- 第 4 层,调用 $\text{Func}(1)$, $\text{Func}(1)$, $\text{Func}(1)$, $\text{Func}(1)$(递归终止条件)。
可以看到,递归的深度是 $\log_2 8 = 3$,即递归树的深度为 3。
总结:
递归树的深度表示的是问题被分解成子问题的层数。在每一层中,输入规模被减少一半,直到达到基本情况。对于这个递归函数 $\text{Func}(n)$,递归树的深度为 $\log_2 n$。
三、递归数时间复杂度分析
x = 2;
while (x < n / 2)
x = 2 * x;
时间复杂度分析:
- 初始条件:$x = 2$
- 循环条件:当 $x < n / 2$ 时,循环继续执行。
- 更新步骤:每次循环中,$x$ 的值都翻倍,即 $x = 2 \times x$。
循环的行为:
初始时 $x = 2$,然后在每次循环中,$x$ 变为 $2, 4, 8, 16, 32, \dots$。
我们希望找出循环运行的次数 $k$,即 $x \geq n / 2$ 时,循环终止。
解方程:
$$
2^k \geq \frac{n}{2}
$$
对两边取对数(以 2 为底):
$$
k \geq \log_2 \left( \frac{n}{2} \right) = \log_2 n - 1
$$
因此,循环大约运行 $O(\log n)$ 次。
结论:
该循环的时间复杂度是 $O(\log n)$。
结果:
** O(log₂n)**
四、纯减法的时间复杂度
这段代码是计算整数 $n$ 的阶乘的递归函数:
int fact(int n) {
if (n <= 1) return 1;
return n * fact(n - 1);
}
时间复杂度分析:
该递归函数的基本情况是当 $n \leq 1$ 时,直接返回 1。否则,递归调用 $fact(n - 1)$ 计算 $n$ 的阶乘。
递归树分析:
- 初始时,函数会调用 $fact(n)$。
- 然后递归调用 $fact(n - 1)$ 来计算下一个阶乘值。
- 这个过程会一直递归下去,直到 $n$ 递减到 1 为止。
递归的调用链如下:
$$
fact(n) \rightarrow fact(n-1) \rightarrow fact(n-2) \rightarrow \dots \rightarrow fact(1)
$$
每次递归调用都减少 $n$ 的值,直到递归到 $fact(1)$ 终止。
递归深度:
递归调用的深度为 $n$,因为每次调用都会将 $n$ 减少 1,直到 $n = 1$。
每层操作的时间复杂度:
每次递归调用只进行常数时间的乘法操作和一次函数调用。因此,每一层递归的时间复杂度是 $O(1)$。
总体时间复杂度:
因为递归调用的深度是 $n$,并且每一层的操作都是 $O(1)$,所以总体的时间复杂度为:
$$
O(n)
$$
结论:
该递归函数的时间复杂度是 $O(n)$,因此正确答案是 O(n)。
五、内外层时间复杂度
题目回顾
题目给出以下程序段,要求计算其时间复杂度:
count = 0;
for (k = 1; k <= n; k *= 2)
for (j = 1; j <= n; j++)
count++;
程序段分析
-
外层循环:
for (k = 1; k <= n; k *= 2)- 初始值:
k = 1。 - 更新:每次
k乘以 2(即k呈指数增长:1, 2, 4, 8, ..., 直到k > n)。 - 循环次数:( \log_2 n ) 次(因为 ( 2^{\log_2 n} = n ))。
- 初始值:
-
内层循环:
for (j = 1; j <= n; j++)- 初始值:
j = 1。 - 更新:每次
j增加 1,直到j > n。 - 循环次数:
n次(固定次数,与外层循环无关)。
- 初始值:
-
总操作:
- 外层循环执行 ( \log_2 n ) 次。
- 每次外层循环,内层循环执行
n次。 - 因此,
count++的执行次数为 ( n \times \log_2 n )。
时间复杂度
- 外层循环次数:( O(\log n) )。
- 内层循环次数:( O(n) )。
- 总时间复杂度:( O(n \log n) )。
排除其他选项
验证
假设 n = 8:
- 外层循环
k的值:1, 2, 4, 8(共 4 次,( \log_2 8 = 3 ),但实际是 4 次,因为 ( k \leq n ))。 - 每次外层循环,内层循环
j执行 8 次。 - 总操作:( 4 \times 8 = 32 ) 次。
- 根据 ( O(n \log n) ):( 8 \times \log_2 8 = 8 \times 3 = 24 ),略低于实际,但增长趋势一致。
结论
程序段的时间复杂度为 ( O(n \log n) ),对应选项 C。
最终答案
( O(n \log_2 n) )
六、
六、等差数列时间复杂度
给出以下函数,要求计算其时间复杂度:
int func(int n) {
int i = 0, sum = 0;
while (sum < n)
sum += ++i;
return i;
}
题目回顾
题目给出以下函数,要求计算其时间复杂度:
int func(int n) {
int i = 0, sum = 0;
while (sum < n)
sum += ++i;
return i;
}
选项:
- A. ( O(\log_2 n) )
- B. ( O(n^{1/2}) )
- C. ( O(n) )
- D. ( O(n \log_2 n) )
函数功能分析
- 初始化:
i = 0,sum = 0。 - 循环条件:
while (sum < n),即当sum小于n时继续循环。 - 循环操作:
sum += ++i,每次循环先将i自增 1,然后将i加到sum中。
循环行为
sum的值为累加序列:1, 3, 6, 10, 15, ...(即三角数序列)。- 第
k次循环后:i = k。sum = 1 + 2 + 3 + ... + k = \frac{k(k+1)}{2}。
- 循环终止条件:
sum >= n,即:
[
\frac{k(k+1)}{2} \geq n
]
解不等式:
[
k^2 + k - 2n \geq 0 \
k \approx \sqrt{2n} \quad (\text{忽略低阶项})
]
因此,循环次数 ( k ) 约为 ( O(\sqrt{n}) )。
时间复杂度
- 循环次数为 ( O(\sqrt{n}) )。
- 每次循环的操作(自增和加法)是 ( O(1) )。
- 因此,总时间复杂度为 ( O(\sqrt{n}) )。
排除其他选项
- A. ( O(\log_2 n) ):对数增长,远小于实际循环次数。
- C. ( O(n) ):线性增长,远大于实际循环次数。
- D. ( O(n \log_2 n) ):线性对数增长,与问题无关。
验证
假设 n = 10:
- 循环过程:
i=1,sum=1。i=2,sum=3。i=3,sum=6。i=4,sum=10(终止)。
- 循环次数:4 次。
- 根据 ( O(\sqrt{n}) ):( \sqrt{10} \approx 3.16 ),接近实际。
结论
函数的时间复杂度为 ( O(\sqrt{n}) ),对应选项 B。
最终答案
( O(n^{1/2}) )
七、开平方
计算其时间复杂度:
x = 0;
while (n >= (x + 1) * (x + 1))
x = x + 1;
程序段分析
- 初始化:
x = 0。 - 循环条件:
while (n >= (x + 1) * (x + 1)),即当n大于等于(x + 1)的平方时继续循环。 - 循环操作:
x = x + 1,每次循环将x增加 1。
循环行为
- 循环条件可以改写为:
(x + 1)^2 <= n。 - 每次循环
x增加 1,因此x的值依次为 0, 1, 2, ..., 直到(x + 1)^2 > n。 - 循环终止时:
(x + 1)^2 > n,即x + 1 > sqrt(n),因此x的最大值约为floor(sqrt(n))。
循环次数
- 循环次数
k满足:(k + 1)^2 > n,即k ≈ sqrt(n) - 1。 - 因此,循环次数为 ( O(\sqrt{n}) )。
时间复杂度
- 循环次数为 ( O(\sqrt{n}) )。
- 每次循环的操作(比较和加法)是 ( O(1) )。
- 因此,总时间复杂度为 ( O(\sqrt{n}) )。
验证
假设 n = 16:
- 循环过程:
x=0,(0+1)^2=1 <= 16。x=1,(1+1)^2=4 <= 16。x=2,(2+1)^2=9 <= 16。x=3,(3+1)^2=16 <= 16。x=4,(4+1)^2=25 > 16(终止)。
- 循环次数:4 次。
- 根据 ( O(\sqrt{n}) ):( \sqrt{16} = 4 ),与实际一致。
结论
程序段的时间复杂度为 ( O(\sqrt{n}) ),对应选项 B。
最终答案
. ( O(\sqrt{n}) )
八
int sum=0;
for(int i=1;i<n;i*=2)
for(int j=0;j<i;j++)
sum++;
好的,以下是这段代码的时间复杂度分析的详细拆解,已转换为Markdown格式:
1. 外层循环分析(for(int i=1; i<n; i*=2))
外层循环的变量 i 初始值为 1,每次循环后 i = i * 2(即按 2的幂次增长),直到 i >= n 时停止。
- 第一次循环:
i = 1 - 第二次循环:
i = 2(1*2) - 第三次循环:
i = 4(2*2) - 第四次循环:
i = 8(4*2) - ...
- 第
k次循环:i = 2^{k-1}
当 i >= n 时,循环终止。因此需要找到最大的 k 满足 2^{k-1} < n ,即:
$2^{k-1} < n \implies k-1 < \log_2 n \implies k < \log_2 n + 1$
所以外层循环的 执行次数约为 log₂n 次(严格说是 ⌊log₂n⌋ 次,复杂度分析取渐近上界,可简化为 log₂n )。
2. 内层循环分析(for(int j=0; j<i; j++))
内层循环的执行次数与当前外层循环的 i 有关:
- 当外层
i=1时,内层j从0到0(共1次 ) - 当外层
i=2时,内层j从0到1(共2次 ) - 当外层
i=4时,内层j从0到3(共4次 ) - 当外层
i=8时,内层j从0到7(共8次 ) - ...
- 当外层
i=2^{k-1}时,内层循环执行2^{k-1}次
3. 总执行次数求和
内层循环的总执行次数是 等比数列求和:
$\text{总次数} = 1 + 2 + 4 + 8 + \cdots + 2^{\log_2 n - 1}$
这是一个首项 $a=1$、公比 $q=2$、项数为 $\log_2 n$ 的等比数列,其和为:
$S = \frac{a(q^{\text{项数}} - 1)}{q - 1} = \frac{1 \cdot (2^{\log_2 n} - 1)}{2 - 1} = 2^{\log_2 n} - 1 = n - 1$
4. 时间复杂度结论
总执行次数约为 $n - 1$ ,渐近上界为 $O(n)$ 。因此,这段程序的时间复杂度是 $\boldsymbol{O(n)}$ ,对应选项 B 。
简单总结:
外层循环次数是 log₂n 级,但内层循环次数随 i 指数增长,最终总和是线性的 $O(n)$ 。核心是识别内层循环的“等比数列求和”,其结果与 $n$ 同阶。

浙公网安备 33010602011771号