CC++ & Algorithm

复数与单位根

极难6
语言版本:通用
概述:复数就像二维平面上的旋转小精灵,单位根是它们排成圆形的舞蹈队形,这是理解FFT的关键魔法。

复数和单位根:旋转的数学魔法,FFT的基石

想象一下,你有一个神奇的罗盘,指针每次转动固定的角度,就能把不同方向的力叠加起来。复数和单位根就是这样的工具——它们让我们能用数学描述旋转,并在计算机里高效地处理信号和多项式。对于理解快速傅里叶变换(FFT)来说,这两个概念就像搭积木的第一块,非常重要。

1. 为什么要把“数”从直线拉到平面?

在数学课上,我们熟悉的数都是实数,它们排在一条直线上,比如温度、零花钱的金额、考试分数。但是,有些问题无法用实数直接解决:比如方程 x2=1x^2 = -1,在实数范围内没有解,因为任何实数的平方都是非负数。数学家们于是发明了一个新的数——虚数单位 ii,并规定 i2=1i^2 = -1

把实数和虚数组合起来,就得到了复数:z=a+biz = a + b i,其中 aa 是实部,bb 是虚部。我们可以把复数想象成平面上的一个点:横坐标是 aa,纵坐标是 bb。这样,复数就拥有了方向长度——就像你在操场上,从起点出发,先向东走 aa 步,再向北走 bb 步,到达一个位置。

更妙的是,复数可以表示旋转。我们有一种更洋气的写法:

z=r(cosθ+isinθ)z = r(\cos\theta + i\sin\theta)

这里 r=a2+b2r = \sqrt{a^2+b^2} 是复数到原点的距离(模长),θ\theta 是从正实轴逆时针旋转的角度(幅角)。比如,ii 就是模长为1、幅角90°的点:i=1(cos90+isin90)i = 1\cdot(\cos 90^\circ + i\sin 90^\circ)

生活例子:你在玩旋转木马,转一圈是360°,每次旋转90°就换个方向。复数相乘时,模长相乘、幅角相加,就像把两次旋转合在一起。比如,先转30°,再转45°,总旋转就是75°。这个性质让复数天然适合处理旋转问题——比如音频处理、图像滤波、多项式求值。

2. 单位根:均匀站在圆上的“小卫兵”

单位根就是满足 zn=1z^n = 1 的所有复数解。一个最简单的例子:n=4n=4 时,方程 z4=1z^4 = 1 的解有哪些?

  • 14=11^4 = 1(没问题)
  • i4=(i2)2=(1)2=1i^4 = (i^2)^2 = (-1)^2 = 1
  • (1)4=1(-1)^4 = 1
  • (i)4=(i)2)2=(1)2=1(-i)^4 = (-i)^2)^2 = (-1)^2 = 1

所以四个单位根是 1,i,1,i1, i, -1, -i。把它们画在复平面上,正好是一个正方形的四个顶点——它们均匀分布在半径为1的圆上,把圆周等分成4份。

对于任意正整数 nnnn 次单位根可以写成:

ωnk=e2πik/n=cos(2πkn)+isin(2πkn),k=0,1,,n1\omega_n^k = e^{2\pi i k / n} = \cos\left(\frac{2\pi k}{n}\right) + i\sin\left(\frac{2\pi k}{n}\right), \quad k = 0, 1, \dots, n-1

其中 ωn=e2πi/n\omega_n = e^{2\pi i / n} 称为主n次单位根。当 kk 从0取到 n1n-1 时,就得到了圆上均匀分布的 nn 个点。

生活例子:想想切披萨。你把一个圆形披萨切成 nn 等份,每份的尖角就是 360n\frac{360^\circ}{n}。单位根正好对应每块披萨中心点的角度。如果 n=8n=8,那么单位根就对应八边形的八个顶点。FFT 利用这些均匀分布的点,就像用一把刻好角度的尺子快速量出多项式的值。

单位根有哪些魔法性质?

这些性质很漂亮,也是FFT能够“快速”的关键:

  1. 周期性ωnk+n=ωnk\omega_n^{k+n} = \omega_n^k。绕一圈回到原点,就像钟表上12点之后又是12点。
  2. 对称性(共轭)ωnk=ωnk\omega_n^{-k} = \overline{\omega_n^k},即实部相同、虚部相反。比如 ω81\omega_8^1ω87\omega_8^7 关于实轴对称。
  3. 折半定理ω2n2k=ωnk\omega_{2n}^{2k} = \omega_n^k。这意味着,如果你有两倍数量的点,跳着取其中一半,就得到了原来数量的点。想象一个64格的圆盘,每隔一格取一个点,就变成32格的圆盘。
  4. 相乘性质ωnkωnj=ωnk+j\omega_n^k \cdot \omega_n^j = \omega_n^{k+j}。不同单位根相乘,指数相加(模 nn)。

正是这些性质,让FFT可以用“分治”策略:把一个大问题分解成两个小问题,再递归求解,从而把 O(n2)O(n^2) 的复杂度降低到 O(nlogn)O(n\log n)

3. 用代码验证单位根:让魔法成真

我们可以在C++或Python中生成单位根,并验证它们的性质。下面是两个语言的示例,代码中使用了标准库中的复数类型。

C++ 实现(包含更多验证)

#include <iostream>
#include <complex>
#include <cmath>
#include <vector>
using namespace std;
using Complex = complex<double>;  // 定义复数类型

// 生成n次单位根(k从0到n-1)
vector<Complex> generateUnitRoots(int n) {
    vector<Complex> roots(n);
    double angle = 2.0 * M_PI / n; // 每个相邻单位根之间的角度差
    for (int k = 0; k < n; ++k) {
        // 欧拉公式:e^(i*theta) = cos(theta) + i*sin(theta)
        roots[k] = Complex(cos(angle * k), sin(angle * k));
    }
    return roots;
}

int main() {
    int n = 8; // 测试8次单位根
    auto roots = generateUnitRoots(n);

    cout << n << "次单位根:" << endl;
    for (int k = 0; k < n; ++k) {
        // 输出实部+虚部,例如 (0.707107,0.707107)
        cout << "k=" << k << ": " << roots[k] << endl;
    }

    // 验证性质1:每个根的n次幂等于1(浮点误差下接近1+0i)
    cout << "\n验证 (omega^k)^8 = 1:" << endl;
    for (int k = 0; k < n; ++k) {
        Complex power = pow(roots[k], n); // 用pow函数
        cout << "k=" << k << ": " << power << endl;
    }

    // 验证性质2:折半定理 omega_{2n}^{2k} = omega_n^k
    int n2 = 2 * n;
    auto roots2n = generateUnitRoots(n2);
    cout << "\n验证折半定理:" << endl;
    for (int k = 0; k < n; ++k) {
        Complex left = roots2n[2 * k]; // omega_{2n}^{2k}
        Complex right = roots[k];      // omega_n^k
        // 计算两个复数的模差,应该接近0
        double diff = abs(left - right);
        cout << "k=" << k << ": omega_{2n}^{2k} = " << left
             << ", omega_n^k = " << right
             << ", 差=" << diff << endl;
    }

    // 额外验证:对称性 omega_n^{-k} = conj(omega_n^k)
    cout << "\n验证对称性 omega_n^{n-k} = conj(omega_n^k):" << endl;
    for (int k = 1; k < n; ++k) {
        Complex w_k = roots[k];
        Complex w_n_minus_k = roots[n - k]; // omega_n^{n-k} 等价于 omega_n^{-k}
        Complex conj_w_k = conj(w_k);       // 共轭
        double diff = abs(w_n_minus_k - conj_w_k);
        cout << "k=" << k << ": diff=" << diff << endl;
    }

    return 0;
}

Python 实现(附带周期性验证)

import cmath
import math

def generate_unit_roots(n):
    """生成n次单位根,返回复数列表"""
    roots = []
    angle = 2 * math.pi / n
    for k in range(n):
        # cmath.rect(r, phi) 将极坐标转为直角坐标
        root = cmath.rect(1, angle * k)  # 模长为1,幅角为 angle*k
        roots.append(root)
    return roots

def test_unit_roots():
    n = 8
    roots = generate_unit_roots(n)
    print(f"{n}次单位根:")
    for k, r in enumerate(roots):
        print(f"k={k}: {r:.4f}")  # 保留4位小数

    # 验证性质1:每个根的n次幂接近1
    print("\n验证 (omega^k)^8 = 1:")
    for k, r in enumerate(roots):
        power = r ** n
        print(f"k={k}: {power:.4f}")

    # 验证折半定理
    n2 = 2 * n
    roots2n = generate_unit_roots(n2)
    print("\n验证折半定理:")
    for k in range(n):
        left = roots2n[2 * k]
        right = roots[k]
        diff = abs(left - right)
        print(f"k={k}: left={left:.4f}, right={right:.4f}, diff={diff:.2e}")

    # 验证周期性 omega_n^{k+n} = omega_n^k
    print("\n验证周期性:")
    for k in range(1, n):
        w_k_plus_n = roots[(k + n) % n]  # 因为周期,取模
        diff = abs(roots[k] - w_k_plus_n)
        print(f"k={k}: diff={diff:.2e}")

if __name__ == "__main__":
    test_unit_roots()

运行代码,你会看到所有验证的差值都非常小(比如 101610^{-16} 级别),证明单位根的性质成立。

4. 新手容易犯的常见错误

错误1:忘记角度单位是弧度

编程时,sin\sincos\cos 的参数默认是弧度,但很多人习惯用度数。记得把 360360^\circ 换算为 2π2\pi 弧度。

错误2:索引越界或混淆指数

单位根的指数 kk 是从0到 n1n-1。在验证折半定理时,如果用 2k2k 访问 2n2n 次单位根数组,必须确保索引不越界 2n2n。上面代码中 2*k 最大为 2(n1)2(n-1),小于 2n12n-1,安全。

错误3:浮点误差导致验证失败

计算机表示小数有误差,比如 cos(π/2)\cos(\pi/2) 可能得到 6.12×10176.12\times 10^{-17} 而不是0。在比较两个复数是否相等时,不要用 ==,而应该用差的绝对值是否小于一个很小的阈值(比如 101210^{-12})。

错误4:混淆“n次单位根”和“主单位根”

nn 次单位根一共有 nn 个,主单位根 ωn\omega_n 只是其中一个(k=1k=1)。FFT 中常用的是所有根,而不是单独一个。

错误5:在FFT中直接用实数点替代单位根

有些同学想偷懒,用均匀分布在圆上的实点(比如角度值)代替复数。但这样就不能利用复数的相乘旋转性质,FFT的核心分治需要复数乘法中的“幅角相加”才能成立。

5. 单位根在FFT中的核心作用

想象你有一群小朋友(多项式的系数)在操场上按圆形排列,每个小朋友代表一个频率成分。单位根就是让小朋友按不同速度旋转的指令:对第 kk 个单位根,你让他们都旋转 kk 份角度。傅里叶变换就是让全体小朋友按照单位根的节奏转一圈,得到新的位置(点值表示)。这就是DFT(离散傅里叶变换)。FFT通过巧妙地利用单位根的对称性和折半定理,把一次大旋转拆成两半,一半用偶数位置的小朋友,一半用奇数位置的小朋友,然后递归求解。这样就把 O(n2)O(n^2) 的运算加速到了 O(nlogn)O(n\log n)

生活比喻:你有一叠试卷要批改,直接每张试卷一道题一道题地看要花很多时间。但如果你把试卷分成两半,一半按偶数学号,一半按奇数学号,分别批改,结果再合并,速度就快多了。这就是分治思想,单位根提供了分治的“切分点”。

6. 完整可运行示例(已集成在上述代码中)

上面的C++和Python代码都是完整可运行的,可以直接复制到编译器或IDE中运行。它们不仅生成了单位根,还验证了周期性、折半定理和对称性,帮助你确认单位根的性质。

7. 相关指引

掌握了复数与单位根,你就可以进入更精彩的世界:

  • FFT快速傅里叶变换:学习如何用单位根分治计算多项式乘法或信号处理。
  • 欧拉公式eiθ=cosθ+isinθe^{i\theta} = \cos\theta + i\sin\theta 是连接指数和三角函数的桥梁,理解它能帮你更深入理解单位根。
  • NTT(数论变换):当模数是质数时,可以用原根替代单位根,在整数域实现快速变换。
  • 复数在几何中的应用:旋转、缩放、分形图形(如曼德勃罗集)都离不开复数。

现在你已经有了这把“魔法钥匙”,下一站就是FFT的奇妙世界!

例题精讲

1单选题

在FFT中,n次单位根的数量是多少?

An-1个
Bn个
C2n个
Dn/2个
2判断题

n次单位根中,任意一个单位根的模长总是1。

3填空题
以下代码计算n次单位根ω_n = e^(2πi/n)。请补全:
#include <complex>
std::complex<double> unit_root(int n) {
    double angle = ___;
    return std::complex<double>(cos(angle), sin(angle));
}