【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;
}
三个要点:
sum必须是double,初值 0.0;sign * 1.0 / i中的1.0强制浮点除法,写成1/i就变成整数除法恒为 0;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 是"提前得出结论"的标准姿势
- 金额、时间类运算统一到最小整数单位,避免浮点比较
下一篇:数组与第一个经典排序算法------冒泡排序。