对数阶 O(logn)O(logn)O(logn)
先复习下对数底数的定义
对数底数的定义:
对数函数的一般形式为:
logbn=x<=>bx=n \log_b n = x \quad \text{<=>} \quad b^x = n logbn=x<=>bx=n
其中:
- b 是对数的底数;
- n 是输入规模。
常见的对数底数有:
- 自然对数(底数为 e):lnn=logen\ln n = \log_e nlnn=logen;
- 二进制对数(底数为 2):log2n\log_2 nlog2n;
- 常用对数(底数为 10):log10n\log_{10} nlog10n。
二分法案例
时间复杂度的定义:算法运行所需的时间,通常用输入规模 ( n ) 的函数表示。
- 时间复杂度:二分法的时间复杂度是 ( O(log2n)O(\log_2 n)O(log2n) ),这归因于查找范围每次缩小一半的特性。
- 空间复杂度:二分法的空间复杂度为 ( O(1)O(1)O(1) ),因为不需要额外的存储空间。
- 效率:二分法适用于静态、排序好的数组,是解决查找问题的经典方法,远优于线性查找。
推导步骤:
(1)每次操作的工作量
在每一步中,二分法的主要操作是:
- 计算中点索引:(mid=⌊left+right2⌋)( \text{mid} = \lfloor \frac{\text{left} + \text{right}}{2} \rfloor )(mid=⌊2left+right⌋)
- 与目标值 ( x ) 进行一次比较。
这些操作的时间复杂度是常数级别的,即 ( O(1) )。
(2)查找范围的变化
二分法每次迭代将查找范围缩小一半:
- 初始范围是 ( n )(数组长度)。
- 第一次迭代后,范围变为 (n2)( \frac{n}{2} )(2n)。
- 第二次迭代后,范围变为 (n4)( \frac{n}{4} )(4n)。
- 以此类推,每次迭代范围缩小为原来的 (12k)( \frac{1}{2^k} )(2k1),其中 ( kkk ) 是迭代次数。
(3)终止条件
二分法的查找终止条件是:
- 要么找到目标值;
- 要么查找范围缩小到 ( 1 )(即 ( n2k=1)\frac{n}{2^k} = 1 )2kn=1))。
解出 ( k ):[n2k=1 ⟹ 2k=n ⟹ k=log2n][ \frac{n}{2^k} = 1 \implies 2^k = n \implies k = \log_2 n ][2kn=1⟹2k=n⟹k=log2n]
因此,二分法的最大迭代次数是 ( log2n\log_2 nlog2n )。
(4)总时间复杂度
由于每次迭代的操作时间是 ( O(1)O(1)O(1) ),而最多需要 ( log2n\log_2 nlog2n ) 次迭代,所以二分法的时间复杂度为:
[O(log2n)][
O(\log_2 n)
][O(log2n)]
💡 O(logn)O(logn)O(logn) 的底数
在时间复杂度的表示中,底数对于 O(logn)O(\log n)O(logn) 的意义通常是无关紧要的。这是因为在计算复杂度时,我们关心的是增长的阶数,而不是精确的数值。下面从多个角度解释为什么 O(logn)O(\log n)O(logn) 的底数并不重要,以及它在分析中的具体作用。
O(logn)O(logn)O(logn) 的底数:
对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。
以下是一分为m的迭代次数的k的推导:
nmk=1 ⟹ mk=n ⟹ k=logmn \frac{n}{m^k} = 1 \implies m^k = n \implies k = \log_m n mkn=1⟹mk=n⟹k=logmn
含义:
- m:一分为m
- n:n个数据
- k:迭代次数
准确来说,“一分为 m”对应的时间复杂度是 O(logmn)O(log_mn)O(logmn) 。而通过对数换底公式,我们可以得到具有不同底数、相等的时间复杂度:
O(logmn)=O(logknlogkm)=O(logkn) O(log_m n) = O( \frac{\log_k n}{\log_k m})=O(\log_kn) O(logmn)=O(logkmlogkn)=O(logkn)
由于底数 logkm\log_k mlogkm 为常数,因此可以忽略。最后将对数阶直接记为 O(logn)O(\log n)O(logn)