《数据结构》学习笔记

1.算法分析的两个主要任务:正确性(不变性 + 单调性) + 复杂度。

2.复杂度分析的主要方法:

  • 迭代:级数求和;
  • 递归:递归跟踪 + 递推方程
  • 猜测 + 验证

3.级数:

(1)算术级数:与末项平方同阶
T ( n ) = 1 + 2 + ⋯ + n = n ( n + 1 ) 2 = O ( n 2 ) T(n) = 1 + 2 + \cdots + n = \frac{n(n+1)}{2} = O(n^2) \notag T(n)=1+2+⋯+n=2n(n+1)=O(n2)

(2)幂方级数:比幂次高出一阶
∑ k = 0 n k d ≈ ∫ 0 n x d + 1 d x = 1 d + 1 x d + 1 ∣ 0 n = 1 d + 1 n d + 1 = O ( n d + 1 ) \sum \limits _{k=0} ^ n k^d \approx \int _0 ^n x^{d+1} \text{d} x = \frac{1}{d+1} x^{d+1} \bigg|_0^n = \frac{1}{d+1} n^{d+1} = O(n^{d+1}) \notag k=0∑nkd≈∫0nxd+1dx=d+11xd+1 0n=d+11nd+1=O(nd+1)

例如:
T 2 ( n ) = 1 2 + 2 2 + ⋯ + n 2 = n ( n + 1 ) ( 2 n + 1 ) 6 = O ( n 3 ) T 3 ( n ) = 1 3 + 2 3 + ⋯ + n 3 = n 2 ( n + 1 ) 2 4 = O ( n 4 ) T 4 ( n ) = 1 4 + 2 4 + ⋯ + n 4 = n ( n + 1 ) ( 2 n + 1 ) ( 3 n 2 + 3 n − 1 ) 30 = O ( n 5 ) \begin{aligned} & T_2(n) = 1^2 + 2 ^ 2 +\cdots + n^2 = \frac{n(n+1)(2n+1)}{6} = O(n^3) \\ & T_3(n) = 1^3 + 2 ^3 +\cdots + n^3 = \frac{n^2(n+1)^2}{4} = O(n^4) \\ & T_4(n) = 1^4 + 2^4 +\cdots + n^4 = \frac{n(n+1)(2n+1)(3n^2+3n-1)}{30} = O(n^5) \end{aligned}\notag T2(n)=12+22+⋯+n2=6n(n+1)(2n+1)=O(n3)T3(n)=13+23+⋯+n3=4n2(n+1)2=O(n4)T4(n)=14+24+⋯+n4=30n(n+1)(2n+1)(3n2+3n−1)=O(n5)

(3)几何级数:与末项同阶
T a ( n ) = a 0 + a 1 + ⋯ + a n = a n + 1 − 1 a − 1 = O ( a n ) T_a(n) = a^0 + a^1+\cdots+a^n = \frac{a^{n+1}-1}{a-1} = O(a^n) \notag Ta(n)=a0+a1+⋯+an=a−1an+1−1=O(an)

例如:
1 + 2 + 4 + ⋯ + 2 n = 2 n + 1 − 1 = O ( 2 n + 1 ) = O ( 2 n ) 1+2+4+\cdots+2^n = 2^{n+1}-1=O(2^{n+1})=O(2^n)\notag 1+2+4+⋯+2n=2n+1−1=O(2n+1)=O(2n)

(4)收敛级数:
1 1 × 2 + 1 2 × 3 + 1 3 × 4 + ⋯ + 1 ( n − 1 ) × n = 1 − 1 n = O ( 1 ) 1 + 1 2 2 + ⋯ + 1 n 2 < 1 + 1 2 2 + ⋯ + = π 2 6 = O ( 1 ) 1 3 + 1 7 + 1 8 + 1 15 + 1 24 + 1 26 + 1 31 + 1 35 + ⋯ = 1 = O ( 1 ) \begin{aligned} & \frac{1}{1\times 2} + \frac{1}{2 \times 3} + \frac{1}{3\times4} +\cdots +\frac{1}{(n-1)\times n} = 1-\frac 1 n = O(1) \\ & 1 + \frac{1}{2^2} + \cdots + \frac{1}{n^2} < 1 + \frac{1}{2^2} + \cdots + = \frac{\pi ^2}{6} = O(1) \\ & \frac{1}{3} + \frac{1}{7} + \frac{1}{8} + \frac{1}{15} + \frac{1}{24} + \frac{1}{26} + \frac{1}{31} + \frac{1}{35} + \cdots = 1 = O(1) \end{aligned} \notag 1×21+2×31+3×41+⋯+(n−1)×n1=1−n1=O(1)1+221+⋯+n21<1+221+⋯+=6π2=O(1)31+71+81+151+241+261+311+351+⋯=1=O(1)

(5)两个重要级数:

  • 调和级数:

h ( n ) = 1 + 1 2 + 1 3 + ⋯ + 1 n = Θ ( log ⁡ n ) \begin{aligned} & h(n) = 1 + \frac{1}{2} + \frac{1}{3} + \cdots + \frac{1}{n} = \Theta(\log n) \end{aligned} \notag h(n)=1+21+31+⋯+n1=Θ(logn)

  • 对数级数:

log ⁡ 1 + log ⁡ 2 + log ⁡ 3 + ⋯ + log ⁡ n = log ⁡ ( n ! ) = Θ ( n log ⁡ n ) \log 1 + \log 2 +\log 3 + \cdots + \log n = \log (n!) = \Theta(n \log n) \notag log1+log2+log3+⋯+logn=log(n!)=Θ(nlogn)

4.一些循环的复杂度估计:

(1)循环体如下:

cpp 复制代码
for (int i = 1; i < n; i <<= 1)
    for (int j = 0; j < i; j++)
        O1_Operation(i, j);

其时间复杂度的计算如下:
几何级数: 1 + 2 + 4 + ⋯ + 2 ⌊ log ⁡ 2 ( n − 1 ) ⌋ = ∑ k = 0 ⌊ log ⁡ 2 ( n − 1 ) ⌋ 2 k ( let k = log ⁡ 2 i ) = 2 ⌈ log ⁡ 2 n ⌉ − 1 = O ( n ) \begin{aligned} \text{几何级数:} \\ & 1 + 2 + 4 + \cdots + 2^{\lfloor \log_2 (n-1) \rfloor} \\ = &\sum \limits _{k=0} ^ {\lfloor \log_2 (n-1) \rfloor} 2^k \quad (\text{let}\ k = \log_2 i) \\ = & 2^{\lceil \log _2 n \rceil} - 1 \\ = & O(n) \end{aligned} \notag 几何级数:===1+2+4+⋯+2⌊log2(n−1)⌋k=0∑⌊log2(n−1)⌋2k(let k=log2i)2⌈log2n⌉−1O(n)

(2)前置知识:数列 { n ⋅ 2 n } \{n\cdot 2^n\} {n⋅2n} 的前 n n n 项和: S n = ( n − 1 ) ⋅ 2 n + 1 + 2 S_n = (n-1)\cdot 2^{n+1}+2 Sn=(n−1)⋅2n+1+2

循环体如下:

cpp 复制代码
for (int i = 0; i <= n; i++)
    for (int j = 1; j < i; j += j)
        O1_Operation(i, j);

时间复杂度的计算如下:
几何级数: ∑ k = 0 n ⌈ log ⁡ 2 i ⌉ = O ( n log ⁡ n ) ( i = 0 , 1 , 2 , 3 ∼ 4 , 5 ∼ 8 , 9 ∼ 16 , ⋯   ) = 0 + 0 + 1 + 2 × 2 + 3 × 2 2 + 4 × 2 4 + ⋯ = ∑ k = 0 ⋯ log ⁡ n ( k × 2 k − 1 ) = O ( log ⁡ n × 2 log ⁡ n ) \begin{aligned} \text{几何级数:} & \sum \limits _{k=0}^n {\lceil \log _2 i \rceil} = O(n \log n) \\ & (i = 0, 1, 2, 3\sim4, 5\sim 8, 9 \sim 16, \cdots) \\ = &\ 0 + 0 + 1 + 2\times 2 + 3 \times 2^2 + 4 \times 2^4 +\cdots \\ = & \ \sum _{k=0\cdots \log n} (k \times 2^{k-1}) \\ = & \ O(\log n \times 2^{\log n}) \end{aligned} \notag 几何级数:===k=0∑n⌈log2i⌉=O(nlogn)(i=0,1,2,3∼4,5∼8,9∼16,⋯) 0+0+1+2×2+3×22+4×24+⋯ k=0⋯logn∑(k×2k−1) O(logn×2logn)

5.算法正确性的证明:(以起泡排序为例)

  • 不变性:经 k k k 轮扫描交换后,最大的 k k k 个元素必然就位;
  • 单调性:经 k k k 轮扫描交换后,问题规模缩减至 n − k n-k n−k;
  • 正确性:经至多 n n n 趟扫描后,算法必然终止,且能给出正确解答。

6.封底估算(Back-Of-The-Envelope Calculation):

(1)1 day = 24h x 60min x 60sec ≈ \approx ≈ 25 x 4000 = 10^5 sec

(2)10^9 sec ≈ \approx ≈ 30 year

7.最长公共子序列:

(1)递归版:

cpp 复制代码
unsigned int lcs(const char* A, int n, const char* B, int m)
{
    if (n < 1 || m < 1) return 0; // recursion base
    
    else if (A[n - 1] == B[m - 1]) // decrease-and-conquer
        return 1 + lcs(A, n - 1, B, m - 1);
    
    else // divide-and-conquer
        return max(lcs(A, n, B, m - 1), lcs(A, n - 1, B, m));
}

(2)迭代版:

cpp 复制代码
unsigned int lcs(const char* A, int n, const char* B, int m)
{
    if (n < m)
    {
        swap(A, B);
        swap(n, m); // make sure m <= n
    }
    
    unsigned int* lcs1 = new unsigned int[m + 1];
    unsigned int* lcs2 = new unsigned int[m + 1];
    
    memset(lcs1, 0x00, sizeof(unsigned int) * (m + 1));
    lcs2[0] = 0;
    
    for (int i = 0; i < n; swap(lcs1, lcs2), i++)
        for (int j = 0; j < m; j++)
            lcs2[j + 1] = (A[i] == B[j]) ? 1 + lcs1[j] : max(lcs2[j], lcs1[j + 1]);
    
    unsigned int res = lcs1[m];
    delete [] lcs1; delete [] lcs2;
    
    return res;
}

8.总和最大区段:

(1) O ( n 3 ) O(n^3) O(n3) 蛮力算法:

cpp 复制代码
int gs_BF(int A[], int n)
{
    int gs = A[0]; // current max
    
    for (int i = 0; i < n; i++)
        for (int j = i; j < n; j++) // enumerate every segment
        {
            int s = 0;
            for (int k = i; k <= j; k++) s += A[k];
            if (gs < s) gs = s;
        }
    
    return gs;
}

(2) O ( n 2 ) O(n^2) O(n2) 递增策略:

cpp 复制代码
// 考查所有起点相同的区间,它们的总和之间具有相关性
int gs_IC(int A[], int n)
{
    int gs = A[0]; // current max
    
    for (int i = 0; i < n; i++)
    {
        int s = 0;
        for (int j = i; j < n; j++)
        {
            s += A[j];
            if (gs < s) gs = s;
        }
    }
    
    return gs;
}

(3) O ( n log ⁡ n ) O(n\log n) O(nlogn) 分治策略:

cpp 复制代码
// 将整个区间递归地分为三部分:前缀、后缀、跨越切分线的中间部分
int gs_DC(int A[], int lo, int hi) // 注意区间是左闭右开的:[lo, hi)
{
    if (hi - lo < 2) return A[lo]; // recursion base
    
    int mi = (lo + hi) >> 1;
    
    // 寻找跨越切分线左半边的gsL
    int gsL = A[mi - 1], sL = 0, i = mi;
    while (lo < i--)
    {
        sL += A[i];
        if (gsL < sL) gsL = sL;
    }
    
    // 寻找跨越切分线右半边的gsR
    int gsR = A[mi], sR = 0, j = mi - 1;
    while (++j < hi)
    {
        sR += A[j];
        if (gsR < sR) gsR = sR;
    }
    
    return max(gsL + gsR, max(gs_DC(A, lo, mi), gs_DC(A, mi, hi)));
}

(4) O ( n ) O(n) O(n) 减治策略:

cpp 复制代码
// 该策略基于这样一个结论:
// 记suffix(k) = A[k, hi), 其中k = max{lo<=i<hi | sum[i, hi)<=0},
// 则GS(lo, hi) = A[i, j)要么是其真后缀(k <= i < j = hi),要么与之无交(j <= k)
int gs_LS(int A[], int n) // Linear Scan: O(n)
{
    int gs = A[0], s = 0, i = n;
    while (0 < i--)
    {
        s += A[i];
        if (gs < s) gs = s;
        if (s <= 0) s = 0; // 剪除负和后缀
    }
    
    return gs;
}
相关推荐
汇能感知2 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun2 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao2 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾3 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT3 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa3 小时前
HTML和CSS学习
前端·css·学习·html
ST.J4 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin4 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全
看海天一色听风起雨落4 小时前
Python学习之装饰器
开发语言·python·学习
小憩-4 小时前
【机器学习】吴恩达机器学习笔记
人工智能·笔记·机器学习