跳转至

C 语言无穷级数逼近:单循环与双重循环

两个 C 程序以不同循环结构计算同一无穷级数的部分和。双重循环版本每次从头累乘,时间复杂度 O(n²);单循环版本利用递推关系,时间复杂度 O(n)。两者结果一致,但运行效率差异显著。

数学背景

程序计算的级数为:

\[ S_n = 1 + \left(-\frac{1}{x}\right) + \left(-\frac{1}{x}\right)^2 + \left(-\frac{1}{x}\right)^3 + \dots + \left(-\frac{1}{x}\right)^n = \sum_{i=0}^{n} \left(-\frac{1}{x}\right)^i \]

\(|x| > 1\) 时,无穷级数收敛于:

\[ S_{\infty} = \frac{1}{1 - (-\frac{1}{x})} = \frac{x}{x + 1} \]

两个程序通过 scanf 读入 \(x\) 和项数 \(n\),输出部分和及运行耗时。


双重循环版本 O(n²)

每一项从 1 开始,内层循环逐次乘以 \(-\frac{1}{x}\)\(i\) 次,等于独立重算每一项的幂。

#include <stdio.h>
#include <sys/timeb.h>

int main()
{
    struct timeb t1, t2;
    long t;
    double x, sum = 1, sum1;
    int i, j, n;

    printf("enter x and n\n");
    scanf("%lf %d", &x, &n);
    ftime(&t1);
    for (i = 1; i <= n; i++)
    {
        sum1 = 1;
        for (j = 1; j <= i; j++)
            sum1 = -sum1 / x;
        sum += sum1;
    }
    ftime(&t2);
    t = (t2.time - t1.time) * 1000 + (t2.millitm - t1.millitm);
    printf("sum = %lf 用时 %ld 毫秒\n", sum, t);

    return 0;
}

修正后代码

原始代码缺少输入验证,且 scanf 未检查返回值。若用户输入非数字或 x = 0,程序会静默出错或除零崩溃。

#include <stdio.h>
#include <sys/timeb.h>

int main()
{
    struct timeb t1, t2;
    long t;
    double x, sum = 1, sum1;
    int n;

    printf("enter x and n\n");
    if (scanf("%lf %d", &x, &n) != 2)          // 新增:检查输入是否成功读入两个值
    {
        printf("输入格式错误\n");
        return 1;
    }
    if (x == 0)                                // 新增:防止除零错误
    {
        printf("x 不能为 0\n");
        return 1;
    }

    ftime(&t1);
    for (int i = 1; i <= n; i++)               // 修正:变量声明移至使用位置
    {
        sum1 = 1;
        for (int j = 1; j <= i; j++)           // 修正:变量声明移至使用位置
            sum1 = -sum1 / x;
        sum += sum1;
    }
    ftime(&t2);
    t = (t2.time - t1.time) * 1000 + (t2.millitm - t1.millitm);
    printf("sum = %lf 用时 %ld 毫秒\n", sum, t);

    return 0;                                  // 新增:显式返回值
}

外循环 \(n\) 次,第 \(i\) 次迭代时内循环执行 \(i\) 次,总操作次数为 \(1 + 2 + \dots + n = \frac{n(n+1)}{2}\),即 \(O(n^2)\)


单循环版本 O(n)

观察级数的递推关系:第 \(i\) 项与第 \(i-1\) 项仅差一个因子 \(-\frac{1}{x}\)。因此无需从头重算,直接在前一项基础上乘一次即可。

#include <stdio.h>
#include <sys/timeb.h>

int main()
{
    struct timeb t1, t2;
    long t;
    double x, sum = 1, sum1 = 1;
    int i, n;

    printf("enter x and n\n");
    scanf("%lf %d", &x, &n);
    ftime(&t1);
    for (i = 1; i <= n; i++)
    {
        sum1 = -sum1 / x;
        sum += sum1;
    }
    ftime(&t2);
    t = (t2.time - t1.time) * 1000 + (t2.millitm - t1.millitm);
    printf("sum = %lf 用时 %ld 毫秒\n", sum, t);

    return 0;
}

修正后代码

#include <stdio.h>
#include <sys/timeb.h>

int main()
{
    struct timeb t1, t2;
    long t;
    double x, sum = 1, sum1 = 1;
    int n;

    printf("enter x and n\n");
    if (scanf("%lf %d", &x, &n) != 2)          // 新增:检查输入
    {
        printf("输入格式错误\n");
        return 1;
    }
    if (x == 0)                                // 新增:防止除零错误
    {
        printf("x 不能为 0\n");
        return 1;
    }

    ftime(&t1);
    for (int i = 1; i <= n; i++)               // 修正:变量声明移至使用位置
    {
        sum1 = -sum1 / x;
        sum += sum1;
    }
    ftime(&t2);
    t = (t2.time - t1.time) * 1000 + (t2.millitm - t1.millitm);
    printf("sum = %lf 用时 %ld 毫秒\n", sum, t);

    return 0;                                  // 新增:显式返回值
}

循环体内只有常数次操作,总复杂度 \(O(n)\)。当 \(n\) 较大时,与双重循环版本的速度差异会非常明显。


复杂度对比

版本 循环结构 时间复杂度 说明
双重循环 内外两层 for \(O(n^2)\) 每一项独立从头计算
单循环 单层 for \(O(n)\) 利用前一项递推下一项

两种写法得到相同的数值结果,但单循环版本避免了大量重复的浮点乘除运算。这个例子展示了"找到递推关系"在数值计算中的重要性——当内层循环的逻辑可以合并到外层时,复杂度往往能从平方级降至线性级。

程序使用 ftime 记录运行前后的毫秒级时间戳,方便直接对比两种实现的耗时差异。