04-循环实战:素数、最大公约数、级数求和与枚举法

【C语言学习笔记 04】循环实战:素数、最大公约数、级数求和与枚举法

循环语法掌握了,这一篇用四个经典问题练手------它们是所有"算法课"的第一批作业,也是理解暴力求解与效率优化的起点。

一、判断素数:标志变量的用法

我最初的版本是这样的:

c 复制代码
#include <stdio.h>

int main()
{
    int x;
    scanf("%d", &x);

    int i;
    int isprime = 1;              // 先假设 x 是素数
    for (i = 2; i < x; i++) {
        if (x % i == 0) {
            printf("不是素数\n");
        }
    }
    if (isprime == 1) {
        printf("是素数\n");
    } else {
        printf("不是素数\n");
    }
    return 0;
}

这个版本有一个隐藏 bug:发现 x % i == 0 时只打印了"不是素数",却忘了把 isprime 置 0,而且没有跳出循环。输入 9 会把"不是素数"打印一遍、结尾又打印"是素数",结果自相矛盾。

修正后的正确版本:

c 复制代码
#include <stdio.h>

int main()
{
    int x;
    scanf("%d", &x);

    int i;
    int isprime = 1;              // 标志变量:先假设成立
    for (i = 2; i < x; i++) {
        if (x % i == 0) {
            isprime = 0;          // 找到反例,推翻假设
            break;                // 已能下结论,无需再试
        }
    }
    if (isprime == 1) {
        printf("是素数\n");
    } else {
        printf("不是素数\n");
    }
    return 0;
}

标志变量(flag)模式:先设一个"假设成立"的标志,循环中一旦发现反例就推翻它并立即 break。这个模式在"找第一个满足条件的元素""验证某性质是否全程成立"类问题中通用。

效率优化:试到平方根就够

c 复制代码
for (i = 2; i * i <= x; i++) {   // i*i <= x 等价于 i <= sqrt(x)
    if (x % i == 0) { ... }
}

若 x 有大于 √x 的因子,必有一个配对因子小于 √x,所以试到 √x 即可。判断 999999937 这类大数时,循环次数从约 10 亿次降到约 3 万次------同一个算法,边界不同,性能天差地别。

二、最大公约数:枚举因数

c 复制代码
#include <stdio.h>

int main()
{
    int a, b;
    int min;
    scanf("%d %d", &a, &b);
    if (a < b) {
        min = a;
    } else {
        min = b;
    }
    int ret = 0;
    int i;
    for (i = 1; i < min; i++) {   // 从小到大枚举
        if (a % i == 0) {
            if (b % i == 0) {
                ret = i;          // 每次找到公因数就覆盖,留下最大的
            }
        }
    }
    printf("%d和%d的最大公约数是%d\n", a, b, ret);
    return 0;
}

枚举法是最朴素也最通用的算法思想:答案一定在某个范围内,就把范围内所有候选者挨个验证。注意两个细节:

  • 循环上界取两数中较小者(公因数不可能超过较小的数);
  • 从小到大枚举时用 ret = i 不断覆盖,循环结束时留下的就是最大值(若从大到小枚举,找到第一个就该 break)。

更优解:辗转相除法(欧几里得算法)

c 复制代码
while (b != 0) {
    int t = a % b;
    a = b;
    b = t;
}
// 循环结束时 a 就是最大公约数

gcd(a, b) = gcd(b, a mod b),几步就能收敛,效率远高于枚举。枚举法求 gcd(1000000, 999999) 要循环百万次,辗转相除只要一次。

方法 时间复杂度 思路
枚举因数 O(min(a,b)) 暴力试每个候选
辗转相除 O(log min(a,b)) 数学性质递归收敛

三、交错级数求和:1 - 1/2 + 1/3 - 1/4 + ...

c 复制代码
#include <stdio.h>

int main()
{
    int n;
    int i;
    double sum = 0.0;
    int sign = 1;                  // 符号变量,从 +1 开始
    scanf("%d", &n);
    for (i = 1; i <= n; i++) {
        sum += sign * 1.0 / i;     // 1.0/i 保证浮点除法
        sign = -sign;              // 每轮翻转符号
    }
    printf("f(%d)=%f\n", n, sum);
    return 0;
}

三个要点:

  1. sum 必须是 double,初值 0.0;
  2. sign * 1.0 / i 中的 1.0 强制浮点除法,写成 1/i 就变成整数除法恒为 0;
  3. sign = -sign 翻转符号,比 if (i % 2 == 0) 判断奇偶更简洁。

另一种等价写法是用 pow(-1, i),但每次调用幂函数又慢又引入浮点误差,符号变量是最优解。

四、多重循环枚举:凑硬币问题

用 1 角、2 角、5 角凑出 x 元,求一组方案:

c 复制代码
#include <stdio.h>

int main()
{
    int x;
    int one, two, five;

    scanf("%d", &x);
    for (one = 1; one < x * 10; one++) {
        for (two = 1; two < x * 10 / 2; two++) {
            for (five = 1; five < x * 10 / 5; five++) {
                if (one + two * 2 + five * 5 == x * 10) {
                    printf("可以用%d个1角和%d个2角和%d个5角得到%d元\n",
                           one, two, five, x);
                    break;          // 找到一组解即退出最内层
                }
            }
        }
    }
    return 0;
}

#mermaid-svg-wB7tBmII1B6ewFX0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wB7tBmII1B6ewFX0 .error-icon{fill:#552222;}#mermaid-svg-wB7tBmII1B6ewFX0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wB7tBmII1B6ewFX0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wB7tBmII1B6ewFX0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wB7tBmII1B6ewFX0 .marker.cross{stroke:#333333;}#mermaid-svg-wB7tBmII1B6ewFX0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wB7tBmII1B6ewFX0 p{margin:0;}#mermaid-svg-wB7tBmII1B6ewFX0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster-label text{fill:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster-label span{color:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster-label span p{background-color:transparent;}#mermaid-svg-wB7tBmII1B6ewFX0 .label text,#mermaid-svg-wB7tBmII1B6ewFX0 span{fill:#333;color:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 .node rect,#mermaid-svg-wB7tBmII1B6ewFX0 .node circle,#mermaid-svg-wB7tBmII1B6ewFX0 .node ellipse,#mermaid-svg-wB7tBmII1B6ewFX0 .node polygon,#mermaid-svg-wB7tBmII1B6ewFX0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wB7tBmII1B6ewFX0 .rough-node .label text,#mermaid-svg-wB7tBmII1B6ewFX0 .node .label text,#mermaid-svg-wB7tBmII1B6ewFX0 .image-shape .label,#mermaid-svg-wB7tBmII1B6ewFX0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-wB7tBmII1B6ewFX0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wB7tBmII1B6ewFX0 .rough-node .label,#mermaid-svg-wB7tBmII1B6ewFX0 .node .label,#mermaid-svg-wB7tBmII1B6ewFX0 .image-shape .label,#mermaid-svg-wB7tBmII1B6ewFX0 .icon-shape .label{text-align:center;}#mermaid-svg-wB7tBmII1B6ewFX0 .node.clickable{cursor:pointer;}#mermaid-svg-wB7tBmII1B6ewFX0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wB7tBmII1B6ewFX0 .arrowheadPath{fill:#333333;}#mermaid-svg-wB7tBmII1B6ewFX0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wB7tBmII1B6ewFX0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wB7tBmII1B6ewFX0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wB7tBmII1B6ewFX0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wB7tBmII1B6ewFX0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wB7tBmII1B6ewFX0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster text{fill:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 .cluster span{color:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wB7tBmII1B6ewFX0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wB7tBmII1B6ewFX0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-wB7tBmII1B6ewFX0 .icon-shape,#mermaid-svg-wB7tBmII1B6ewFX0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wB7tBmII1B6ewFX0 .icon-shape p,#mermaid-svg-wB7tBmII1B6ewFX0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wB7tBmII1B6ewFX0 .icon-shape .label rect,#mermaid-svg-wB7tBmII1B6ewFX0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wB7tBmII1B6ewFX0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wB7tBmII1B6ewFX0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wB7tBmII1B6ewFX0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
枚举 one: 1角个数
枚举 two: 2角个数
枚举 five: 5角个数
one*1 + two*2 + five*5 == x*10 ?
输出这组方案, break 退出最内层
five++ 继续试
five 用尽 → two++

单位统一 :金额以"角"为最小单位运算,避免浮点数比较(浮点数存在精度误差,0.1 + 0.2 != 0.3,用整数运算判断相等才可靠)。

注意 :这里的 break 只跳出最内层 for,外两层还会继续枚举,因此输出的是多组方案;若只要一组,可以设一个标志变量在三层都判断退出(C 语言没有"多层 break")。

枚举法的复杂度是三层循环相乘------O(n³)。当规模变大时要思考剪枝(如内层范围由外层剩余金额决定),这正是从"暴力"走向"算法"的分水岭。

五、小结

这四个问题串起来就是一条算法启蒙路线:

问题 核心思想 优化方向
素数判断 标志变量 + 试除 试到 √x 即止
最大公约数 枚举因数 辗转相除 O(log n)
交错级数求和 浮点累加 + 符号变量 ---
凑硬币 多重循环枚举 剪枝 / 贪心 / 动态规划
  • 枚举(暴力搜索)是算法的底线,先保证正确再谈优化
  • 标志变量 + break 是"提前得出结论"的标准姿势
  • 金额、时间类运算统一到最小整数单位,避免浮点比较

下一篇:数组与第一个经典排序算法------冒泡排序。

相关推荐
暖核1 小时前
kubeadm 集群运维:Worker 节点下线删除、整套集群销毁重建指南
运维·开发语言·php
tachibana22 小时前
Go 语言里的 Context 是什么?
开发语言·后端·golang·go·语言
天桥下的卖艺者2 小时前
R语言使用ggcube包轻松绘制文章中的各类3D图形
开发语言·3d·r语言
slandarer2 小时前
MATLAB | 趣味编程——动态字符串甜甜圈和爱心
数据结构·matlab·趣味编程
UIU1143 小时前
P1003 [NOIP 2011 提高组] 铺地毯
数据结构·c++·算法
爱读书的小胖3 小时前
LeetCode 88. 合并两个有序数组 C 解题分析
c语言·数据结构·算法
唐钰小宝B3 小时前
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
java·开发语言·windows
inrichtt3 小时前
第七课时:STM32时钟系统揭秘:从零理解时钟树
c语言·数据结构·stm32·单片机·嵌入式硬件
知识分享小能手4 小时前
C学习教程,从入门到精通,C语言指针 —— 详细知识点总结(10)
c语言·开发语言·学习