算法分析与设计

算法概述

算法复杂度

五个渐近分析记号

注:以下的"情况"是指输入 \(n\) 具有的某种特性,比如下面例子里举的输入 \(n\) 为偶数的情况即是最坏情况,输入 \(n\) 为非2质数的情况即为最坏情况

1.1 渐近上界记号 \(\Omicron\)

即该算法在最坏情况下的时间复杂度

$\exists c, n_0, \forall n \geq n_0, 0 \leq f(n) \leq cg(n) $

\(f(n) = \Omicron(g(n))\)

\(\Leftrightarrow\)

\(\exists c > 0, \lim\limits_{n \rightarrow \infty} \frac{f(n)}{cg(n)} \leq 1^{-}\)

1.2 渐近下界记号 \(\Omega\)

即该算法在最好情况下的时间复杂度

$\exists c, n_0, \forall n \geq n_0, 0 \leq cg(n) \leq f(n) $

\(f(n) = \Omega(g(n))\)

\(\Leftrightarrow\)

\(\exists c > 0, \lim\limits_{n \rightarrow \infty} \frac{f(n)}{cg(n)} \geq 1^{+}\)

1.3 非紧上界记号 \(\omicron\)

即该算法在最坏情况下的时间复杂度小于该时间复杂度

$\forall \epsilon > 0, \exists n_0, \forall n \geq n_0, f(n) / g(n) < \epsilon $

\(f(n) = \omicron (g(n))\)

\(\Leftrightarrow\)

\(\lim\limits_{n \rightarrow \infty} \frac{f(n)}{g(n)} = 0\)

1.4 非紧下界记号 \(\omega\)

即该算法在最好情况下的时间复杂度大于该时间复杂度

$\forall \epsilon > 0, \exists n_0, \forall n \geq n_0, g(n) / f(n) < \epsilon $

\(f(n) = \omega (g(n))\)

\(\Leftrightarrow\)

\(\lim\limits_{n \rightarrow \infty} \frac{f(n)}{g(n)} = \infty\)

1.5 紧渐近界记号 \(\theta\)

无论什么情况,时间复杂度都相同

\(f(n) = \Omicron(g(n)) , f(n) = \Omega(g(n))\)

\(f(n) = \theta (g(n))\)

发现当 \(f(n)\) 为一确定的表达式时,\(\Omicron(f(n)) = \Omega(f(n))\)
那么什么情况下 \(\Omicron\)\(\Omega\) 才不同呢?

关注下面素性检验(判断是否为质数)算法

bool isPrime(ll n) {
    for (ll i = 2; i * i <= n; ++ i) {
        if (n % i == 0) return 0;
    }
    return n > 1;
}

显然是一个根号算法 即\(\Omicron(f(n)) = O(n^{\frac{1}{2}})\)
但是当 n 是 偶数时,代码只有一次基本运算
\(\Omega(f(n)) = \Omega(1)\)

综上:大 \(\Omicron\) 关注“最多需要多少时间”,大 \(\Omega\) 关注“至少需要多少时间”

NP 完备性理论

判定问题:仅需回答YESNO的问题

P 问题:所有可在多项式时间内求解判定问题
NP 问题:能在多项式时间内验证解判定问题
根据定义,显然有: \(P \subseteq NP\)

NPC问题:

递归和分治策略

递归

递归实现全排列

#include <bits/stdc++.h>
using namespace std;
void perm(vector<int> &a, int k, int n) {
    if (k == n) {
        for (int i = 0; i < n; ++ i) {
            cout << a[i] << " \n"[i == n - 1];
        }    
    } else {
        for (int i = k; i < n; ++ i) {
            swap(a[k], a[i]);
            perm(a, k + 1, n);
            swap(a[k], a[i]);
        }
    }
}
int main() {
    int n;
    cin >> n;
    vector<int> a(n);
    iota(a.begin(), a.end(), 1);
    perm(a, 0, n);
}

整数划分

以下为动态规划实现

int DP(int n) { // O(n^2)
    vector<vector<ll>> dp(n + 1, vector<ll>(n + 1));
    dp[1][1] = 1;
    for (int i = 2; i <= n; ++ i) {
        dp[i][1] = 1;
        for (int j = 2; j <= i - 1; ++ j) {
            int x = i - j;
            int y = min(j, i - j);
            dp[i][j] += dp[x][y];
            dp[i][j] += dp[i][j - 1];
        }
        dp[i][i] += dp[i][i - 1] + 1;
    }
    return dp[n][n];
}

以下为递归实现

int dfs0(int n, int m) {
    if (n == 0 || m == 0) {
        return 0;
    } else if (n == 1 || m == 1) {
        return 1;
    } else if (n == m) {
        return dfs0(n, m - 1) + 1;
    } else {
        int x = n - m;
        int y = min(m, n - m);
        return dfs0(x, y) + dfs0(n, m - 1);
    }
}
int dfs(int n) { // O
    return dfs0(n, n);
}

分治

二分搜索

int binsearch(vector<int> &a, int x) {
    int l = 1, r = (int)a.size() - 1;
    while (l <= r) {
        int m = (l + r) >> 1;
        if (x == a[m]) return m;
        if (x < a[m]) {
            r = m - 1;
        } else {
            l = m + 1;
        }
    }
    return -1;
}

大整数乘法

这里直接封装了一个多项式类,其中base是进制,即该算法算法可以用于任意进制的大整数

下面讲解一下大整数乘法

\(x = a*10^{n / 2} + b\)
\(y = c*10^{n / 2} + d\)
\(x * y = ac*10^{n / 2 * 2} + ((a+b)(c+d) - ac - bd) * 10^{n / 2} + bd\)

(注意有的会写成 \(x * y = ac*10^{n} + ((a+b)(c+d) - ac - bd) * 10^{n / 2} + bd\) ,这是不严谨的)

然后递归进行乘法即可

注意当当前递归到的x, y的位数都为1时,需要直接计算出值作为递归的边界

/**
 *    title: bigmul.cpp
 *    Author: Proaes Meluam
 *    Date: 2025-06-03 21:50:56
 *    Describe: 
 *    Link: 
**/
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i32 = int;
using i64 = long long;
using i128 = __int128;
const double pi = acos(-1);
const double e = exp(1);
constexpr ll mod = 1e9 + 7;
// constexpr int inf = 0x3f3f3f3f;
constexpr ll inf = 0x3f3f3f3f3f3f3f3f;
class polynomial {
public:
    ll n = 1, base = 10;
    vector<ll> a;
    polynomial() {
        this-> n = 1;
        a.resize(n);
    }
    polynomial(int n) { 
        this-> n = n;
        a.resize(n);
    }
    void init(string s) {
        for (int i = 0; i < n; ++ i) {
            a[i] = s[n - i - 1] - '0';
        }
    }
    void pad(int len) {
        int cn = 1;
        while (cn < len) cn <<= 1;
        for (int i = n; i < cn; ++ i) a.emplace_back(0);
        n = cn;
    }
    void clear0() {
        while (n > 1 && a[n - 1] == 0) {
            a.pop_back();
            n--;
        }
    }
    polynomial shift(int k) {
        if (n + k < 0) return polynomial();
        polynomial res(n + k);
        for (int i = n - 1; i >= 0; -- i) {
            res.a[i + k] = a[i];
        }
        res.clear0();
        return res;
    }
    polynomial get(int l, int r) {
        int d = r - l;
        polynomial res(d + 1);
        for (int i = 0; i + l <= r; ++ i) {
            res.a[i] = a[l + i];
        }
        res.clear0();
        return res;
    }
    static polynomial add(polynomial a, polynomial b) {
        if (b.n > a.n) swap(a, b);
        int cn = a.n;
        polynomial res(cn);
        ll carry = 0;
        for (int i = 0; i < cn; ++ i) {
            ll x = a.a[i], y = 0;
            if (b.n > i) y = b.a[i];
            res.a[i] += (x + y + carry) % res.base;
            carry = (x + y + carry) / res.base;
        }
        if (carry) {
            res.n ++;
            res.a.emplace_back(carry);
        }
        res.clear0();
        return res;
    }
    static polynomial sub(polynomial a, polynomial b) {
        int cn = a.n;
        polynomial res(cn);
        ll carry = 0;
        for (int i = 0; i < cn; ++ i) {
            int cur = a.a[i];
            if (b.n > i) cur -= b.a[i];
            res.a[i] += cur;
            if (res.a[i] < 0) {
                res.a[i] += res.base;
                res.a[i + 1] --;
            }
        }
        res.clear0();
        return res;
    }
    static polynomial mul3(polynomial x, polynomial y) {
        if (x.n == 1 && y.n == 1) {
            int num = x.a[0] * y.a[0];
            int cn = (num >= 10 ? 2 : 1);
            polynomial res(cn);
            res.init(to_string(num));
            return res;
        }
        int cn = max(x.n, y.n);
        polynomial xp = x, yp = y;
        xp.pad(cn); yp.pad(cn);
        cn = max(xp.n, yp.n);
        int m = cn >> 1;
        polynomial x_low = xp.get(0, m - 1);
        polynomial y_low = yp.get(0, m - 1);
        polynomial x_high = xp.get(m, cn - 1);
        polynomial y_high = yp.get(m, cn - 1);

        polynomial z0 = mul3(x_low, y_low);
        polynomial z2 = mul3(x_high, y_high);

        polynomial x_sum = add(x_low, x_high);
        polynomial y_sum = add(y_low, y_high);

        polynomial z1 = mul3(x_sum, y_sum);
        z1 = sub(z1, z0);
        z1 = sub(z1, z2);

        polynomial res;
        res = add(res, z2.shift(2*m));
        res = add(res, z1.shift(m));
        res = add(res, z0);
        res.clear0();
        return res;
    }
    void show() {
        for (int i = n - 1; i >= 0; -- i) {
            cout << a[i];
        }
        cout << "\n";
    }
};
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cout.tie(nullptr)->sync_with_stdio(false);

    int n;
    cin >> n;
    polynomial x(n);
    cin >> n;   
    polynomial y(n);
    string s1, s2; cin >> s1 >> s2;
    x.init(s1);
    y.init(s2);
    x.show();   
    y.show();
    polynomial a = polynomial::add(x, y);
    a.show();
    polynomial b = polynomial::sub(x, y);
    b.show();
    polynomial c = polynomial::mul3(x, y);
    c.show();
    polynomial d;
    d.show();
}

Strassen 矩阵乘法

若矩阵的维度不是2的幂次,那么先将矩阵维度改成2的幂次再应用该算法,具体见书本

归并排序

posted @ 2025-06-05 23:41  Proaes  阅读(26)  评论(0)    收藏  举报