- F5 @8 Y4 b/ d3)嵌套代码求乘积:比如递归、多重循环等。 0 M6 z6 o! B& |( ?% {9 Z! T5 P* x L 4 F9 T) k2 K/ }2 c9 ^2 n0 i, n4)多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。 3 M( K' L" y i# e" t4 W- K. s & w: t) `1 i8 i" ^/ Y3 w2 q) u4 b四、常用的复杂度级别? ) e0 [. x- n# S多项式阶:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括, O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n^2 ) (平方阶)、O( n^3)(立方阶) 4 [3 ~+ _$ K/ B7 y; T 6 ^6 n% M5 G* T# R1 r非多项式阶:随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括, O( 2^n )(指数阶)、O(n!)(阶乘阶)4 c, _& E) H1 n! ], z
% h( o" b" @; c b- G% b7 r" J
复杂度量级排序: ' S% J' a' h' R2 Q: A6 P d ; D7 N/ B) y8 x6 y/ n" y i; u ; ~' {' G `3 B8 v1. 常数阶O(1) 0 Y/ ^* W% y5 r- j; P无论代码执行了多少行,其他区域不会影响到操作,这个代码的时间复杂度都是O(1)" X' S4 T9 ^, G; M7 X+ E E. M4 {
! I) \2 w3 C( ?1 a! G* v
9 N0 |; Z, E' ~8 Z0 Q- vvoid swapTwoInts(int &a, int &b)) ^ [8 ?4 z+ a/ b- y
{& L0 a* q9 ~. U; n; J2 A. a1 b& R
int temp = a; - k0 p& o f% E+ _: Y1 M( T/ d a = b;, X6 b1 ]: b( @( z. ^ H
b = temp;) Y/ M( H: k) `
}( u, Z, h# b, m6 t& x& x9 S% s
1/ q. t' ]/ g/ o2 ?# ^1 g
2 8 C( b# F# s5 g" D( A4 P& q2 e3 ; q% R" Z. ], i- F5 Q4 4 c2 M5 F6 M% ]1 t% b8 O9 U; @: s! Y5: p% Q. q5 [1 w3 ?) y5 h
6 3 P$ [! a5 Y, b- Z2. 线性阶O(n) 6 @) [7 A: d. n! @; O* s ) Z# ^" j2 L0 v- _在下面这段代码,for循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此可以用O(n)来表示它的时间复杂度。' A( E" m; B) e C: b8 r
! G2 T/ l: Z) i2 H+ u' R
int sum ( int n )% y7 u6 ^; }$ v
{ : Y7 A+ N( |. k+ C int ret = 0; : H: ~# I# p1 L9 q( u for ( int i = 0 ; i <= n ; i ++)! w" {8 } _- l7 X. A5 d
{" r" B, Y* e- O" R
ret += i;# ~0 a' K/ d6 Q; n# z9 T) b
} & q6 z! Q& G0 f4 `" @$ T2 @ return ret; 6 S i9 M& h! e* U6 a. |3 _2 ^4 E}7 `7 b' y% I1 E4 o$ \2 \
10 d; I8 F, ^6 T, y8 K
2 + x) q1 H* T4 Q! Z: O- W3 V3 7 G' [! z" N& V9 K, t49 N/ A+ d; I8 L4 |. w# F
5 1 H$ E( d/ ?8 z, l1 j) V6! v4 D O4 P( P9 l2 f4 F
7 C5 d' N/ J: J8 b, B4 {
8: {' |* u4 t; D+ r. w
9 6 |2 M3 D7 J ]3 A3. 平方阶O(n2)6 s) }- F+ I1 L# |0 ?4 V
当存在双重循环的时候,即把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n2) 了。5 _* P' t1 }+ Y$ V2 _6 l
+ F/ T7 z" n& W0 x. i' c
9 [' X1 l# h2 k$ U+ ^
void selectionSort(int arr[],int n){" }6 u: P) P. n& T
for(int i = 0; i < n ; i++){ & R2 T' u* [7 c int minIndex = i; ) l3 w% S) G8 M! U6 O8 W for (int j = i + 1; j < n ; j++ ) 5 i. [% @' T# d1 X/ X; B if (arr[j] < arr[minIndex]) 9 U3 u! F- K4 C, Z# ` minIndex = j;, X6 d( M2 T- K8 x2 I$ n3 Z
swap ( arr, arr[minIndex]);& |8 P3 I/ I* q6 I0 Z: D s- @% k
} - y; `9 Z& n- \) `6 e# ?& D2 s }0 j. R3 ]5 ` W
1 % m3 v+ c, E) |/ o0 w& s2 6 [; c* O1 \" y' |3 G+ w, G! J# V6 _+ k7 r: W& E" ?4 e4* r7 k7 q" i: Y4 q6 P" i
5 7 h) u$ p2 G- f3 `$ A65 R2 H) o( c0 i+ n3 h6 T
72 \8 D) o- e+ J o
8 # F- [) s/ K/ \- Q' V, M! J9 6 t5 V8 t' {/ @7 k这里简单的推导一下3 V H7 ~( {! X& m- C. Z6 T3 J
. Y5 }' f. }- o
当 i = 0 时,第二重循环需要运行 (n - 1) 次0 ~3 o; C. d/ G9 y
) e d! F' Q1 ]5 o4 e
当 i = 1 时,第二重循环需要运行 (n - 2) 次! l2 w- \! K- q' y" @ \
: _- _; ]6 K% z. C! I: c
。。。。。。 1 Y; ]# X. f: e( R$ E3 a2 I$ w 4 C7 Z8 i: a, r/ v2 j5 A不难得到公式:: U1 Z4 D/ p8 w5 a/ e
! o% c' [) J. Z. G(n - 1) + (n - 2) + (n - 3) + ... + 0 * h3 _& p$ R$ o; ?" H= (0 + n - 1) * n / 2 1 s% a/ |3 p: f% S4 B) P" r6 r= O (n ^2)4 Z7 L# V+ C! H& m
1. v4 q$ y0 T* O
2% P- f* E' S- G8 U" V
3; j n. v$ `, E! t* C% t7 n6 o
4. 对数阶O(logn)8 ~, u/ A6 Y6 ~ B$ G0 \
/ ~# R4 k% R! `0 ?) r* q9 R - R0 L+ b( v$ d$ O0 r* r int binarySearch( int arr[], int n , int target){ ' \* V! ^ ~4 e% K u4 S: g/ g _ int l = 0, r = n - 1; 3 R6 U8 I0 z/ u' ?8 B4 J while ( l <= r) {2 f2 a" r5 q2 s/ j! P
int mid = l + (r - l) / 2; 3 w% H) K; j6 C5 F$ w if (arr[mid] == target) return mid;, Z6 o# J) C2 T9 ~ a' l
if (arr[mid] > target ) r = mid - 1; ( X/ [" Y! D( x+ T$ m* J: h else l = mid + 1; 6 p: }" I$ z$ L' a0 r4 j } 8 R# g- K7 J6 @3 b$ B return -1; ! x7 M$ e% _; _% H* F1 Z, D6 C. N1 S" J% ]} 6 t' h9 Z) `" v" u8 v1 * [. S" m+ O1 `3 R; I i2 % X/ O1 q% p" Y. f8 c36 m' r8 _/ \2 Q3 c3 A
48 y l8 W. a0 O+ Z
5 . E+ G1 P7 ~: N! G7 Y d4 ?) R/ d67 F/ f B% o8 M1 a. w+ H+ L/ D7 m! |# y
7. @* R$ [! v4 z' n; f8 A' o
83 ^* z' _. r8 a0 \
96 @2 Q5 V9 ^* u* h) A5 E7 k
10$ N& z2 r2 T8 C/ b7 @
在二分查找法的代码中,通过while循环,成 2 倍数的缩减搜索范围,也就是说需要经过 log2^n 次即可跳出循环。/ }0 s' G+ Y6 L6 m2 F
) Z( m9 A9 z; M3 W9 H% F五、不常见的时间复杂度; l# Z; ]5 r5 ^8 H0 D! G8 d
1. 最好情况时间复杂度(best case time complexity) 9 r% j# D' Y ^4 U K最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,这个时候对应的时间复杂度就是最好情况时间复杂度。) o# V8 z+ N) D- c
3 G) t9 Z3 w& V: A7 Q% p2. 最坏情况时间复杂度(worst case time complexity) + t! }0 b! h3 X- q最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。如果数组中没有要查找的变量 x,我们需要把整个数组都遍历一遍才行,所以这种最糟糕情况下对应的时间复杂度就是最坏情况时间复杂度。! ~+ @! U, g" {: X7 m
- w0 i( P: E, R0 L$ k0 X最好、最坏情况时间复杂度指的是特殊情况下的时间复杂度。 # _0 L3 m" B* a4 y% b: w2 X% K; T- U动图表明的是在数组 array 中寻找变量 x 第一次出现的位置,若没有找到,则返回 -1;否则返回位置下标。" Y3 s8 ^/ u" k2 @5 F
) f& o0 r* _: M# q; ?
int find(int[] array, int n, int x) {) Y$ o+ H" }# f) r$ t
for ( int i = 0 ; i < n; i++) { * b' f9 E0 A2 h1 \6 ^6 D) H if (array == x) { ' J. S/ L. Y. C) T& }* t" Z return i; ) C& H0 u" K ]/ V break; % ]. g+ `* T5 p' { Q" t }: l% B& g% O8 s" U
} 1 F8 E6 x0 _; w: b3 e return -1; ) N# c0 K" h2 M8 }- I V) u} , Q9 g( }3 U+ V' t+ h, @1- P! v( a- X- D# E( |' t7 E- n& g
2 % q1 [5 Z. _3 ^9 M3& c; @* m$ [! I- c
4/ t( A$ M3 F C$ ~# v
5# Z" d+ y% X0 n: \' c. Q
67 p/ j3 O% z+ Y( k3 @
78 Z$ d0 w3 ?* c6 P0 E
85 V0 k% R/ v- g3 w& d# s
9 % {8 L4 u* y2 X6 p0 g6 Z在这里当数组中第一个元素就是要找的 x 时,时间复杂度是 O(1);而当最后一个元素才是 x 时,时间复杂度则是 O(n)。8 L/ l. `' R- l. E
! d% |4 e( E7 V/ \$ o最好情况时间复杂度就是在最理想情况下执行代码的时间复杂度,它的时间是最短的;最坏情况时间复杂度就是在最糟糕情况下执行代码的时间复杂度,它的时间是最长的。 / W3 r1 K/ C, u7 @; r6 S% l ) a# L- c! [& ^1 s3. 平均情况时间复杂度(average case time complexity) 4 C0 K, n3 ~# @2 C最好、最坏时间复杂度反应的是极端条件下的复杂度,发生的概率不大,不能代表平均水平。那么为了更好的表示平均情况下的算法复杂度,就需要引入平均时间复杂度。 ' w) ?3 ?: X5 A. T0 A% d2 k$ K1 H: U# Y: o( x& F1 |* w
平均情况时间复杂度可用代码在所有可能情况下执行次数的加权平均值表示。 3 {! D8 a' D3 p3 w2 y) Q; Z4 [$ z/ X" u
还是以 find 函数为例,从概率的角度看, x 在数组中每一个位置的可能性是相同的,为 1 / n。那么,那么平均情况时间复杂度就可以用下面的方式计算:' C- U5 ^3 t0 u8 a( A, |6 W' s7 {
. d2 j/ r) x' G8 {((1 + 2 + … + n) / n + n) / 2 = (3n + 1) / 4 ' I( h+ F/ U9 ]" J( U8 E8 W! g1 , e, D9 |8 i6 O: U% X) I2 N1 ~. S# V# D ( N( M* x9 `. o1 T" i5 vfind 函数的平均时间复杂度为 O(n)。 2 v. [. V7 O) @7 m" _8 ] ]/ \+ Y+ S9 x0 V* S0 S
4. 均摊时间复杂度(amortized time complexity)2 j$ Y/ j* U2 {: \8 m3 _2 Q
我们通过一个动态数组的 push_back 操作来理解 均摊复杂度。/ H/ ~* c" r. o8 [
% n, Z6 b! x7 [& [* d
5 m; s5 I" ~. F! x. k* y
template <typename T> " v* d2 f3 ^3 j/ A& r* A6 K4 l class MyVector{ + A# o' n: n4 _" I& Y private: 8 l: i p! l) L P T* data;0 ?/ X( Z8 a3 }9 I2 d
int size; // 存储数组中的元素个数+ o, Z& ~/ |: z9 E% ] A
int capacity; // 存储数组中可以容纳的最大的元素个数$ _# Y8 z! S( I* q5 Z1 i
// 复杂度为 O(n) 7 B+ T7 ^3 q0 v3 U+ ]) H; Z! B( ^ void resize(int newCapacity){; ~+ l( E- G9 {! @, i
T *newData = new T[newCapacity];% q& d* s; j# K- p3 u" Q; W. ]
for( int i = 0 ; i < size ; i ++ ){3 z' E- d0 U6 F* B& [
newData = data;6 Y* _3 ?" X5 D5 c# J9 e% P
}9 c( ?% o" J2 R7 }' R. u6 ~
data = newData; . I! e' i. q" L: U capacity = newCapacity; ; c4 O! x* Q& u/ N; p }2 W" b, v: ~2 y+ w. L6 S
public:/ c8 I# K1 r5 u: W9 T2 K
MyVector(){% w ?# c2 R9 F; R( a7 ?
data = new T[100];) v7 N: m6 Y. i; _- z8 d% Z
size = 0;& R! x% T' A% w( p2 D
capacity = 100;$ ^ Z8 F, I3 K) e7 l4 U3 L3 R
}& J8 ~0 c) C* ?8 e1 X: I' I' w5 a( x! r
// 平均复杂度为 O(1)5 n8 |; [# D2 O; e3 U6 t, w
void push_back(T e){ , H% W' b& i: B) ]. W2 U if(size == capacity) # }* M2 h5 C, M+ y& S4 p. E( E8 A resize(2 * capacity);/ `1 [, X% u4 M/ b( v9 o+ Z% g
data[size++] = e;: X. i: P" l8 ^5 d3 [6 {. i
}8 X4 `' v2 W2 Y; r8 T* K5 O. x
// 平均复杂度为 O(1) ( o$ E+ K9 ?/ U! b& X/ z8 h) Z T pop_back(){ / O* O, i- @% A+ _6 G' \ size --; , m/ d) F2 O( i' Q5 a/ {5 {" m* v return data[size];' C4 n+ x. t8 x; P; f
} 4 E% {! y# G) l" k! d, W2 \; d# x: P# q6 I/ g# O; [5 O; j
}; . @" n# y3 h i0 z8 ? F: Y. c% @# r8 Z- Y) @$ n