数据结构和算法分析


大$O$记号(big-O notation)(上界)

$T(n) = O(f(n)) \iff \exists \ c>0 ,当n >> 2 后,有T(n) < c\cdot f(n).$

与T(n)相比,f(n)更为简洁,但依然反映前者的增长趋势。

常系数可忽略:$O(f(n))\quad=\quad O(c\times f(n))$

低次项可忽略:$O(n^{a}+n^{b})\quad=\quad O(n^{a}),\quad a>b>0$

大$\Omega$记号 (下界)

$ T(n)=\Omega(f(n))\iff\exists\ c>0,当 n>>2 后,有 T(n) > c\cdot f(n).$

$\Theta$记号

$\begin{aligned}&\mathrm{T(n)=\Theta(f(n)~):}\&\exists c_{1}>c_{2}>0\text{,当 n}>>2\text{ 后,有 }c_{1}\cdotp f(n)>T(n)>c_{2}\cdotp f(n)\end{aligned}$

image-20231222202914964

$O(1)$

  • 常数复杂度:不含转向(循坏、调用、递归等)必顺序执行,即是$O(1)$

$O(\log^{\mathsf{c}}\text{n})$

  • 对数$O(\log\text{n})$$lnn,|, lgn ,|, log_{100}n ,|, log_{2013}n$

  • 常底数无所谓$\begin{array}{rcl}{\forall\mathrm{a},\mathrm{b}>0,\mathrm{log}{a}n}&{=\mathrm{log}{a}\mathrm{b}\cdot\mathrm{log}{b}n}&{=\Theta\left(\mathrm{log}{b}n\right)}\\end{array}$

换底公式:$\log_ab=\frac{\log_cb}{\log_ca}.$

  • 常数次幂无所谓$\forall c>0,\mathrm{logn}^c=c\cdot\mathrm{logn}=\Theta(\mathrm{logn})$

  • 对数多项式(ploy-log function)$123*\log^{321}n+\log^{105}(n^{2}-n+1)\quad=\Theta(\log^{321}n)$

  • 这类算法非常有效,复杂度无限接近于常数$\forall\mathrm{c}>0,\mathrm{}\mathrm{}\mathrm{}\mathrm{logn}=\mathrm{~}O(n^{\mathrm{c}})$

$O\left(n^{c}\right)$

  • 多项式(polynomial function)

    $a_{k}n^{k}+a_{k-1}n^{k-1}+\ldots+a_{1}n+a_{0}=O(n^{k}),\ a_{k}>0$

  • 线性(linear function):所有$O(n)$类函数

  • 从$O(n)$到$O(n^2)$:编程习题主要覆盖范围

  • 幂:$[(n^{2013}-24n^{2009})^{1/3}+512n^{567}-1978n^{123}]^{1/11}=0(n^{61})$

$O(2^n)$

  • 指数(exponential function)(难解)

    $\forall\ c>1,\ \mathrm{n}^{c}=O(2^{n})$

$e^{n}=1+n+n^{2}/2!+n^{3}/3!+n^{4}/4!+\ldots $

image-20231222213154490
  • 从$O\left(n^{c}\right)$到$O(2^n)$,是从有效算法到无效算法的分水岭

2-Subset

【问题描述】:$S$包含$n$个正整数,$\Sigma S = 2m $;$S$是否有子集$T$,满足$\Sigma T = 2m ?$

【选举人制】各州议会选出的选举人团投票而不是由选民直接投票50个州加1个特区,共538票获270张选举人票,即可当选;但是若共有两位候选人,是否可能恰好各得269票?

直觉算法 : 逐一枚举S的每一子集,并统计其中元素的总和

定理:$|2^S| = 2^{|S|}=2^{n}$

亦即:直觉算法需要迭代$2^n$轮,并(在最坏情况下)至少需要花费这么多的时间——不甚理想

定理:2-Subset is NP-complete

NP-complete问题:就目前的计算模型而言,不存在可在多项式时间内回答此问题的算法。

增长速度

image-20231223102013633

算法分析

复杂度分析主要方法

迭代:级数求和

递归:递归跟踪+递推方程

猜测+验证

级数

算术级数:与末项平方同阶

$T(n)=1+2+\ldots+n=n(n+1)/2=O(n^{2})$

幂方级数:比幂次高出一阶 $\boxed{\sum_{k=0}^{n}k^{d}{\approx}\int {0}^{n}x^{d+1}dx=\frac{1}{d+1}x^{d+1}\Big|{0}^{n}=\frac{1}{d+1}n^{d+1}=O(n^{d+1})}$

$T_{2}(n)=1^{2}+2^{2}+3^{2}+\ldots+n^{2}=n(n+1)(2n+1)/6=O(n^{3})$
$T_{3}(n)
=1^{3}+2^{3}+3^{3}+\ldots+n^{3}=n^{2}(n+1)^{2}/4=~O(n^{4})$
$T_4(n)=1^4+2^4+3^4+\ldots+n^4=n(n+1)(2n+1)(3n^2+3n-1)/30=O(n^5)$

几何级数(a>1):与末项同阶

$T_{a}(n)=a^{0}+a^{1}+\ldots+a^{n}=(a^{n+1}-1)/(a-1)\ =O(a^{n})$
$1+2+4+\ldots+2^{n}=2^{n+1}-1=O(2^{n+1})=O(2^{n})$

收敛级数

$\begin{aligned}
&1/1/2+1/2/3+1/3/4+\ldots+1/(n-1)/n=1-1/n=O(1) \
&{1}+1/2^{2}+\ldots+1/n^{2}<1+1/2^{2}+\ldots=\pi^{2}/6=O(1) \
&1/3+1/7+1/8+1/15+1/24+1/26+1/31+1/35+\ldots=1=0(1)
\end{aligned}$

几何分布:$(1-\lambda)\cdot[1+2\lambda+3\lambda^{2}+4\lambda^{3}+\ldots]=1/(1-\lambda)=0(1),0<\lambda<1$

可能未必收敛,然而长度有限

$\begin{aligned}
&h(n)=1+1/2+1/3+\ldots+1/n=\Theta(\log n) \ 调和级数\
&log1 + log2 + log3 + … + logn = log(n!) = O(n1ogn)\ 对数级数
\end{aligned}$

Concrete Mathematics(书籍推荐)

循环与级数

for(int i = 0; i < n; i++) {
	for(int j = 0; j < n; j++)
        O1Operation(i,j);
}

算术级数:$\sum_{i=0}^{n-1}n=n+n+…+n=n*n=0(n^2)$

image-20231223194935167
for(int i = 0; i < n; i++) {
	for(int j = 0; j < i; j++)
        O1Operation(i,j);
}

算术级数:$\sum_{i=0}^{n-1}i=0+1+…+(n-1)=\frac{n(n-1)}2=O(n^2)$

image-20231223194958733
for(int i = 0; i < n; i++) {
	for(int j = 0; j < i; j += 2013)
        O1Operation(i,j);
}

算术级数:$O(n^2)$

image-20231223195327434
for(int i = 0; i < n; i << = 1) {//i左移一位,乘以2
	for(int j = 0; j < i; j++)
        O1Operation(i,j);
}

几何级数:

$1+2+4+\ldots+2^{\lfloor\log_2(n-1)\rfloor}$
$=\sum_{k=0}^{\left\lfloor\log_2(n-1)\right\rfloor}2^k\quad(let k=1og_{2}i)$

$=2^{\lceil{log_2n}\rceil}-1=O(n)$

image-20231223200432269
for(int i = 0; i < = n; i++) {
	for(int j = 1; j < i; j+=j)
        O1Operation(i,j);
}

几何级数:$\sum_{k=0}^n\lceil\log_2\mathrm{i}\rceil= O(\mathrm{nlogn})$

迭代与递归

迭代乃人工,递归方神通

To iterate is human, to recurse, divine.

分治算法

减而治之【Decrease-and-conquer】

  • 为求解一个大规模的问题,可以将其划分为两个子问题:其一平凡,另一个规模缩减//单调性

  • 分别求解子问题,由子问题的解,得到原问题的解

image-20231224100144841

递归算法分析

  • 递归跟踪(recursion trace)分析

  • 数组求和(线性递归)

    • 检查每个递归实例
    • 累计所需时间(调用语句本身,计入对应的子实例)
    • 其总和即算法执行时间
    sum(int A[], int n) {
    	return (n < 1) ? 0 : sum(A, n-1) + A[n-1];
    }//线性递归
image-20231224101336407
    • 本例中,单个递归实例自身只需$O(1)$时间
    • $T(n)=O(1)*(n+1)=O(n)$
  • 递推角度,为求解sum(A, n),需

    • 递归求解规模为n-1的问题sum(A, n-1) // $T(n-1)$
    • 再累加上A[n-1] // $O(1)$
    • 递归基:sum(A, 0) // $O(1)$
  • 递归方程:$T(n)=T(n-1)+O(1)$ // recurrence

  • $T(0) = O(1)$ // base

  • $\begin{array}{rcl}{\mathrm{T(n)-n=}}&{\mathrm{T(n-1)-(n-1)}}&{=}&{\ldots}\{=}&{\mathrm{T(2)-2}}\{=}&{\mathrm{T(1)-1}}\{=}&{\mathrm{T(0)}}\{\mathrm{T(n)=O(1)+n=~O(n)}}\\end{array}$

  • 数组倒置

    • 任给一数组A[0, n],将其前后颠倒

    • 统一接口:void reverse(int* A, int lo, int hi);

    //递归版
    if(lo < hi) { //问题规模的奇偶性不变,需要两个递归基
    	swap(A[lo], A[hi]);
    	reverse(A, lo+1, hi - 1);
    }
    else return; //base case
    //迭代精简版
    while(lo < hi)
        swap(A[lo++], A[hi--]);

分而治之【Divide-and-conquer】

  • 为求解一个大规模问题,可以

  • 将其划分为若干(通常两个)子问题,规模大体相当

  • 分别求解子问题

  • 由子问题的解,得到原问题的解

image-20231224110024199

  • 数组求和(二分递归)
sum(int A[], int lo, int hi) { //区间范围A[lo, hi]
	if(lo == hi) return A[lo];
    int mi = (lo + hi) > 1; //右移(/2)
    return sum(A, lo, mi) + sum(A, mi+1, hi);
} //入口形式为sum(A, 0, n-1)

$T(n) = 各层递归实例所需时间之和$

$\begin{array}{rcl}=&O(1)\times(2^0+2^1+2^2+\ldots+2^{\log n})\=&O(1)\times(2^{\log n+1}-1)=O(n)\end{array}$

image-20231224111705972

每层总数构成以2为倍数的几何级数:与末项同阶

  • 递推角度,为求解sum(A, lo, hi),需

    • 递归求解sum(A, lo, mi)和sum(A, mi+1, hi) // 2*T(n/2)
    • 进而将子问题的解累加 //O(1)
    • 递归基:sum(A, lo, lo) //O(1)
  • 递推关系:

    • $\mathrm{T(n)=2^*T(n/2)+0(1)}$
    • $T(1)=O(1)$
  • 求解:

    $\begin{array}{rcl}\mathrm{T(n)}{=}&2^\mathrm{T(n/2)}+c_1\\mathrm{T(n)}+c_1{=}&2^(\mathrm{T(n/2)}+c_1){=}&2^{2*}(\mathrm{T(n/4)}+c_1)\&{=}&\ldots\{=}&2^{\log n}(\mathrm{T(1)}+c_1){=}&n^*(\mathrm{c_2}+c_1)\\mathrm{T(n)}{=}&(\mathrm{c_1}+c_2)\mathrm{n}-c_1{=}&O(\mathrm{n})\end{array}$

动态规划

fib():迭代

image-20231224173412379

解决方法A(记忆:memoization n.记忆化;备忘;记忆表)

将已计算过实例的结果制表备查

解决方法B(动态规划:dynamic programming)

颠倒计算方向:由自顶而下递归,改为自底而上迭代

image-20231224173804982
f = 0; g = 1;//fib(0),fib(1)
while(0 < n--) {
    g = g + f;
    f = g - f;
}
return g;//O(n)

最长公共子序列LCS:递归

子序列(Subsequence):由序列中若干字符,按原相对次序构成

最长公共子序列(Longest Common Subsequence):两个序列公共子序列中的最长者

image-20231224175213969 image-20231224175251962

文章作者: nusqx
文章链接: https://nusqx.top
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 nusqx !
评论
  目录