数据结构(邓俊辉)1-2---复杂度分析

目的

明确了算法复杂度的度量标准后,不清楚的可点击复杂度度量 ,如何分析具体算法的复杂度? 大O记号将各算法的复杂度分为若干层次级别,若干经典复杂度级别如下:

常数O(1)

这个比较简单,简单说说。 运行时间可表示和度量为T(n) = O(1)的这一类算法,统称作"常数时间复杂度算法 "。 这类算法效率最高。 此类算法通常不含循环、分支、子程序调用等,但也不能仅凭语法结构的表面形式一概而论。

cpp 复制代码
void O1( unsigned int n ) {
	for ( unsigned int i = 0; i < n; i += 1 + n/2013 ) { //循环:但迭代至多2013次,与n无关
UNREACHABLE: //无法抵达癿转向标志
		if ( 1 + 1 != 2 ) goto UNREACHABLE; //分支:条件永非,转向无效
		if ( n * n < 0 ) doSomething(n); //分支:条件永非,调用无效
		if ( (n + i) * (n + i) < 4 * n * i ) doSomething( n ); //分支:条件永非,调用无效
		if ( 2 == (n * n) % 5 ) O1( n + 1 ); //分支:条件永非,递归无效
	}
}

目前某些高级的编译器,像是C++语言,已经能够识别前一类完全由常数定义的永非式,因为常数在编译器便可确定(可了解下constexpr),并在编译过程中作相应的自动优化。 然而不幸的是,对于由变量(变量在程序运行时才可确定)参与定义的这种(以及更为复杂的)逻辑条件,编译器尚不能有效地判别和优化。 不难理解,相对于前两种情况,后两种无效的分支语句几乎无法有效地辨别。由以上可见,对于程序时间复杂度的估算,不能完全停留和依赖于其外在的流程结构;更为准确而精细的分析,必然需要以对其内在功能语义的充分理解为基础。

对数O(logn)

1. 问题与算法

对于任意非负整数,统计其二进制展开中数位1的总数。

cpp 复制代码
int countOnes ( unsigned int n ) { //统计整数二迕刢展开中数位1癿总数:O(logn)
	int ones = 0; //计数器复位
	while ( 0 < n ) { //在n缩减至0之前,反复地
		ones += ( 1 & n ); //检查最低位,若为1则计数
		n >>= 1; //右移一位
	}
	return ones; //返回计数
} 

2.复杂度

书中原话"至多经过1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|(向上取整) 次循环,n必然缩减至0,从而算法终止。" 我的理解如下: 根据右移运算的性质(非负整数右移一位,相当于/2),每右移一位,n都至少缩减一半。

∀ 正整数n(1,2,3...n),均可表示成n = <math xmlns="http://www.w3.org/1998/Math/MathML"> k x k^x </math>kx, 其中k表示右移运算的位数 k >1 ,x = <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g k n log_kn </math>logkn n右移一位,故k = 2,n可表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 x 2^x </math>2x,x= <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n n = 1 , x = <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 1 log_21 </math>log21=0;

n = 2 , x = <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 2 log_22 </math>log22=1; n = 3,1 < x= <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 3 < 2 log_23 < 2 </math>log23<2;

| <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 3 log_23 </math>log23| = 1; 所以,至多经过1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|(向上取整) 次循环,n必然缩减至0

从另一角度来看,1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|恰为n二进制展开的总位数

当n = 2时, 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 2 log_22 </math>log22| = 2 当n = 3时, 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 3 log_23 </math>log23| = 2 当n = 8时; 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 8 log_28 </math>log28| = 4 当n= 441(110111001)时; 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 441 log_2441 </math>log2441| = 1 + |8.784635| = 9

每次循环都将其右移一位,总的循环次数自然也应是1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|。 该循环体内只涉及常数次(逻辑判断、位与运算、加法、右移等)基本操作。因此,countOnes()算法的执行时间主要由循环的次数决定,亦即: O(1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|) = O(| <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|) = O( <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n) 尽管此处底数为常数2,却可直接记作O(logn) ,对数复杂度与下标无关。此类算法称作具有"对数时间复杂度"。

3. 对数多项式复杂度

凡运行时间可以表示和度量为T(n) = O( <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g c n log^cn </math>logcn)形式的这一类算法(其中常数c > 0),均统称作"对数多项式时间复杂度的算法 "(polylogarithmic-time algorithm)。上述O(logn)即c = 1的特例。 此类算法的效率虽不如常数复杂度算法理想,从多项式的角度看仍能无限接近于常数,故也是极为高效的一类算法 。 证明如下: 引入第一个重要结论: n的n次方根的极限为1,最大值为1

<math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ n 1 n \lim\limits_{n\rightarrow\infty}n^\frac{1}{n} </math>n→∞limnn1 =1 等价 <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ l n ( n 1 n ) \lim\limits_{n\rightarrow\infty}ln(n^\frac{1}{n}) </math>n→∞limln(nn1) =0 <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ l n ( n 1 n ) \lim\limits_{n\rightarrow\infty}ln(n^\frac{1}{n}) </math>n→∞limln(nn1) = <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ l n ( n ) n \lim\limits_{n\rightarrow\infty}\frac{ln(n)}{n} </math>n→∞limnln(n) 无穷大比无穷大,洛必达法则 <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ l n ( n ) n \lim\limits_{n\rightarrow\infty}\frac{ln(n)}{n} </math>n→∞limnln(n) = <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ 1 n \lim\limits_{n\rightarrow\infty}\frac{1}{n} </math>n→∞limn1 当n->∞ 极限为0 所以 <math xmlns="http://www.w3.org/1998/Math/MathML"> lim ⁡ n → ∞ n 1 n \lim\limits_{n\rightarrow\infty}n^\frac{1}{n} </math>n→∞limnn1 =1

引入第二个重要结论: 对 ∀ <math xmlns="http://www.w3.org/1998/Math/MathML"> ε \varepsilon </math>ε > 0,都有 logn = O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n ε n^\varepsilon </math>nε)。

∀ <math xmlns="http://www.w3.org/1998/Math/MathML"> ε \varepsilon </math>ε > 0 ,根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> e ε e^\varepsilon </math>eε > 1,存在M >0,使得n > M之后 <math xmlns="http://www.w3.org/1998/Math/MathML"> e ε e^\varepsilon </math>eε > 1 > <math xmlns="http://www.w3.org/1998/Math/MathML"> n 1 n n^\frac{1}{n} </math>nn1 以 e为底取对数,便得 <math xmlns="http://www.w3.org/1998/Math/MathML"> l n n n \frac{lnn}{n} </math>nlnn < <math xmlns="http://www.w3.org/1998/Math/MathML"> ε \varepsilon </math>ε 亦即:lnn < n <math xmlns="http://www.w3.org/1998/Math/MathML"> ε \varepsilon </math>ε 令N = <math xmlns="http://www.w3.org/1998/Math/MathML"> e M e^M </math>eM ,则n > N(即ln n > M)后,总有ln(ln n) < <math xmlns="http://www.w3.org/1998/Math/MathML"> ε \varepsilon </math>εln n,ln n < <math xmlns="http://www.w3.org/1998/Math/MathML"> n ε n^\varepsilon </math>nε

总结:

线性O(n)

1.问题与算法

计算给定n个整数的总和

cpp 复制代码
int sumI ( int A[], int n ) { //数组求和算法(迭代版)
	int sum = 0; //初始化累计器,O(1)
	for ( int i = 0; i < n; i++ ) //对全部共O(n)个元素,逐一
		sum += A[i]; //累计,O(1)
		return sum; //迒回累计值,O(1)
} //O(1) + O(n)*O(1) + O(1) = O(n+2) = O(n)

2. 复杂度

O(1) + O(1)*n = O(n + 1) = O(n) 凡运行时间可以表示和度量为T(n) = O(n)形式的这一类算法,均统称作"线性时间复杂度算法"。 此类算法的效率亦足以令人满意。

多项式O(polynomial(n))

若运行时间可以表示和度量为T(n) = O(f(n))的形式,而且f(x)为多项式,则对应的算法称作多项式时间复杂度算法 。 T(n) = O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2),f(n) = n 即属于此类。 多项式级的运行时间成本,在实际应用中一般被认为是可接受的或可忍受的。某问题若存在一个复杂度在此范围以内的算法,则称该问题是可有效求解的或易解的(tractable)。

指数O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 n 2^n </math>2n)

1. 问题与算法

cpp 复制代码
__int64 power2BF_I ( int n ) { //幂函数2^n算法(蛮力迭代版),n >= 0
	__int64 pow = 1; //O(1):累积器刜始化为2^0
	while ( 0 < n -- ) //O(n):迭代n轮,每轮都
		pow <<= 1; //O(1):将累积器翻倍
	return pow; //O(1):迒回累积器
} //O(n) = O(2^r),r为输入指数n癿比特位数

2.复杂度

算法power2BF_I()共需O(n)时间。若以输入指数n的二进制位数r = 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|作为输入规模,则运行时间为O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 r 2^r </math>2r)。 一般地,凡运行时间可以表示和度量为T(n) = O( <math xmlns="http://www.w3.org/1998/Math/MathML"> a n a^n </math>an)形式的算法(a > 1),均属于"指数时间复杂度算法"。

从多项式到指数

复杂度层次

利用大O记号,不仅可以定量地把握算法复杂度的主要部分,而且可以定性地由低至高将复 杂度划分为若干层次。 复杂度的典型层次:(1)~(7)依次为O(logn)、O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n \sqrt{n} </math>n )、O(n)、O(nlogn)、O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2)、O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 3 n^3 </math>n3)和O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 n 2^n </math>2n)

输入规模

不同的人在不同场合下关于"输入规模"的理解、定义和度量可能不尽相同,因此也可能导 致复杂度分析的结论有所差异。

  1. countOnes()算法的复杂度为O(logn)的结论,是相对于输入整数本身的数值n而言;而若以n二进制展开的宽度r = 1 + | <math xmlns="http://www.w3.org/1998/Math/MathML"> l o g 2 n log_2n </math>log2n|作为输入规模,则应为线性复杂度O( r )。
  2. power2BF_I()算法的复杂度为O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 r 2^r </math>2r)的结论,是相对于输入指数n的二进制数位r而言;而若以n本身的数值作为输入规模,却应为线性复杂度O(n)。

countOnes()算法和power2BF_I()算法将输入参数n二进制展开的宽度r作为输入规模更为合理。

综上,输入规模应严格定义为"用以描述输入所需的空间规模(这个需要好好理解)"。 需要笔记的朋友可留言

参考书籍《数据结构》邓俊辉编著

相关推荐
幸运超级加倍~43 分钟前
软件设计师-上午题-16 算法(4-5分)
笔记·算法
yannan201903131 小时前
【算法】(Python)动态规划
python·算法·动态规划
埃菲尔铁塔_CV算法1 小时前
人工智能图像算法:开启视觉新时代的钥匙
人工智能·算法
EasyCVR1 小时前
EHOME视频平台EasyCVR视频融合平台使用OBS进行RTMP推流,WebRTC播放出现抖动、卡顿如何解决?
人工智能·算法·ffmpeg·音视频·webrtc·监控视频接入
linsa_pursuer1 小时前
快乐数算法
算法·leetcode·职场和发展
小芒果_011 小时前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
qq_434085901 小时前
Day 52 || 739. 每日温度 、 496.下一个更大元素 I 、503.下一个更大元素II
算法
Beau_Will1 小时前
ZISUOJ 2024算法基础公选课练习一(2)
算法
XuanRanDev1 小时前
【每日一题】LeetCode - 三数之和
数据结构·算法·leetcode·1024程序员节
gkdpjj1 小时前
C++优选算法十 哈希表
c++·算法·散列表