文章目录
0x00 前言
写这篇《C语言笔记 | 数据结构入门指南》主要是为了帮助更多的编程爱好者打开数据结构的大门,同时也是为了自我编程水平能力的提升。在深奥的数据结构领域中,这篇入门指南就像海上的一叶扁舟。虽是虚无缥缈,但是总比什么都没有要好得多。我将竭尽我所能,简明扼要地解释相关案例中的知识点。其中不乏会有错误,恳请各位老师不吝指正。
0x01 百鸡百钱
0x1 题目描述
我国古代数学家张丘建在《算经》一书中提出的数学问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?
0x2 问题分析
关于百鸡百钱的不定方程组:
其中, x 表示鸡翁、 y 表示鸡母、 z 表示鸡雏。在计算机中,我们可以通过使用计算机思维对这类题目计算,而不是数学思维。可以使用穷举的方法将每个结果列举出来,直到计算机找出哪个正确的结果。也就是怎么买可以做到百鸡百钱。
0x3 代码设计
int x, y, z;
代码解析:定义三个变量, x 表示鸡翁、 y 表示鸡母、 z 表示鸡雏。
for(x = 0; x <= 20; x++)
{
for(y = 0; y <= 33; y++)
{
z = 100 - (x + y);
代码解析:这里可以使用三重循环,但是鸡雏可以直接通过 100 减去鸡翁和鸡母的和来确定。减少一个循环可以起到代码优化的效果。第一个循环用来确定鸡翁的数量,第二个循环用来确定鸡母的数量。
if((5 * x + 3 * y + z / 3) == 100)
{
printf("鸡翁:%2d | 鸡母 :%2d | 鸡雏:%2d\n", x, y, z);
}
代码解析: 使用 if 判断语句判断,如果不定方程组的第二个式子的值等于 100 ,那么就执行一次 printf 输出函数记录一次百鸡百钱的值。
0x4 完整代码
#include<stdio.h>
int main()
{
int x, y, z;
for(x = 0; x <= 20; x++)
{
for(y = 0; y <= 33; y++)
{
z = 100 - (x + y);
if((5 * x + 3 * y + z / 3) == 100)
{
printf("鸡翁:%2d | 鸡母 :%2d | 鸡雏:%2d\n", x, y, z);
}
}
}
return 0;
}
0x5 运行效果
鸡翁: 0 | 鸡母 :25 | 鸡雏:75
鸡翁: 3 | 鸡母 :20 | 鸡雏:77
鸡翁: 4 | 鸡母 :18 | 鸡雏:78
鸡翁: 7 | 鸡母 :13 | 鸡雏:80
鸡翁: 8 | 鸡母 :11 | 鸡雏:81
鸡翁:11 | 鸡母 : 6 | 鸡雏:83
鸡翁:12 | 鸡母 : 4 | 鸡雏:84
--------------------------------
Process exited after 0.08475 seconds with return value 0
请按任意键继续. . .
0x6 举一反三 [兔鸡百钱]
题目描述
河伯有财富百金,欲以百金买百禽。鸡一只计二钱,兔一只计三钱。若购三兔,则赠鸡一只。请推算河伯所购百禽之中,鸡之数量和兔之脚数,共计几何?(一只鸡足为二,一只兔脚为四)
问题分析
关于兔鸡百钱的线性方程:
其中, r 表示兔子、 c 表示鸡。解法和百鸡百钱问题一样,可以把送的鸡稍后再计算,先判断买兔子,最后在输出函数中判断即可。如果买了 3 只兔子就加 1 个鸡。我们不用特殊计算它,因为我们先计算了兔子,假如我们买了 4 只兔子,那么计算鸡的数量就可以是 4 除 3 。
代码设计
int r, c;
代码解析:定义两个变量, r 表示兔子的缩写, c 表示鸡的缩写。
for (r = 0; r <= 33; r++)
{
for (c = 0; c <= 50; c++)
{
if ((3 * r + 2 * c) == 100)
{
printf("兔脚:%4d | 鸡足:%4d\n", r * 4, (c + r / 3) * 2);
}
}
}
代码解析:使用双重 for 循环易于理解,先循环兔子的数量,接着循环鸡的数量,给一个双重循环的判断条件,如果 3r + 2c 个家禽满足 100 钱。那么则记录这次的结果。题目要求是就算鸡脚和兔脚。所以将兔子数量乘 4 就是兔脚数量。鸡脚数量是乘 2 ,不过我们还需要将赠送的鸡一同计算。赠送的鸡可以表示为兔子总数除 3 。
完整代码
#include <stdio.h>
int main()
{
int r, c;
for (r = 0; r <= 33; r++)
{
for (c = 0; c <= 50; c++)
{
if ((3 * r + 2 * c) == 100)
{
printf("兔脚:%4d | 鸡足:%4d\n", r * 4, (c + r / 3) * 2);
}
}
}
return 0;
}
运行效果
兔脚: 0 | 鸡足: 100
兔脚: 8 | 鸡足: 94
兔脚: 16 | 鸡足: 90
兔脚: 24 | 鸡足: 86
兔脚: 32 | 鸡足: 80
兔脚: 40 | 鸡足: 76
兔脚: 48 | 鸡足: 72
兔脚: 56 | 鸡足: 66
兔脚: 64 | 鸡足: 62
兔脚: 72 | 鸡足: 58
兔脚: 80 | 鸡足: 52
兔脚: 88 | 鸡足: 48
兔脚: 96 | 鸡足: 44
兔脚: 104 | 鸡足: 38
兔脚: 112 | 鸡足: 34
兔脚: 120 | 鸡足: 30
兔脚: 128 | 鸡足: 24
--------------------------------
Process exited after 0.03176 seconds with return value 0
请按任意键继续. . .
0x02 借书方案知多少
0x1 题目描述
小明有 5 本新书,要借给 A 、 B 、 C 三位小朋友,若每人每次只能借 1 本,则可以有多少种不同的借法?
0x2 问题分析
这题关于排列组合,也就是计算 5 个数中 3 个不同数的所有排列方法,那么可以先进行编号 1~5 表示这五本不同的书,接着定义三个变量 a 、 b 、 c 表示三个小朋友。那么可以使用循环嵌套的方法让每个小朋友进行选书,在 a 和 b 之间判断不能选一样的书。接着让 a 和 c 以及 b 和 c 他们之间也不能选择一样的书。
0x3 代码设计
int a, b, c, i = 0;
代码解析:定义一个变量 a 表示小朋友 A 、变量 b 表示小朋友 B 、变量 c 表示小朋友 C 。定义 i = 0 用来累计有多少种不同的借法。
for(a = 1; a <= 5; a++)
{
for(b = 1; b <= 5; b++)
{
if(a != b)
{
for(c = 1; c <= 5; c++)
{
if(a != c && b != c)
{
i++;
printf("a:%d | b:%d | c:%d\n", a, b, c);
}
}
}
}
}
代码解析:第一层循环用来计算小朋友 A 拿的书,第二层循环计算小朋友 B 拿的书。给出判断条件,如果 a 不等于 b 则执行以下代码,就是判断如果小朋友 A 和小朋友 B 拿了一样的书就是没有意义的,则不执行。接着给第三层循环,用来计算小朋友 C 。判断如果 A 和 C 拿了一样的书或者是 B 和 C 拿了一样的书,那么代码不执行。如果都不是一样的书则会执行 i++ 累加器用来计算有多少种分配的次数,并打印结果显示这次小朋友 A 、 B 、 C 是拿的什么书。
printf("借法:%d", i);
代码解析:使用 printf 函数输出变量 i 累加的值,输出的结果就是借法的次数。
0x4 完整代码
#include<stdio.h>
int main()
{
int a, b, c, i = 0;
for(a = 1; a <= 5; a++)
{
for(b = 1; b <= 5; b++)
{
if(a != b)
{
for(c = 1; c <= 5; c++)
{
if(a != c && b != c)
{
i++;
}
}
}
}
}
printf("借法:%d", i);
return 0;
}
0x5 运行效果
借法:60
--------------------------------
Process exited after 0.08041 seconds with return value 0
请按任意键继续. . .
0x6 举一反三 [领导小组方案]
题目描述
一家公司有 25 名员工。为了更好的管理公司,于是河伯决定将 4 名员工选为领导小组成员,问有多少种不同的选法?
问题分析
这题关于排列组合,也就是计算 25 个数中 4 个不同数的所有排列方法,除了用计算机思维,这题用数学思维会更便捷。
关于领导小组方案的组合公式:
代码设计
int a;
代码解析:定义一个变量 a 用来存储组合公式的结果。
a = (25 * 24 * 23 * 22) / (1 * 2 * 3 * 4);
代码解析: 直接计算组合公式保存数据给变量 a 。
printf("方案:%d", a);
代码解析: 使用 printf 函数输出变量 a 中存储的值,输出的结果就是方案的次数。
完整代码
#include <stdio.h>
int main()
{
int a;
a = (25 * 24 * 23 * 22) / (1 * 2 * 3 * 4);
printf("方案:%d\n", a);
return 0;
}
运行效果
方案:12650
--------------------------------
Process exited after 0.028 seconds with return value 0
请按任意键继续. . .
0x03 打鱼还是晒网
0x1 题目描述
中国有句俗语叫 “三天打鱼两天晒网” 。某人从2000年1月1日起便开始 “三天打鱼两天晒网” ,问这个人在以后的某一天中是“打鱼”还是“晒网”。
0x2 问题分析
先获取用户输入的年月日,使用一个函数用来验证闰年。验证方法是:四年一润,百年不润。四百年一润。根据方法建立规则并返回即可。循环遍历年份,从 2000 开始。如果是闰年就加 366 天,如果是平年加 365 天。然后计算月份,定义一个数组,里面有 1-12 月份的天数,给一个判断条件,如果最后一年是闰年,就修改数组的值为 29 。然后用一个循环将月份累加起来。注意:累加月份时,最后一月份不计算,应该在天数中进行计算。
0x3 代码设计
int L(int Y)
{
return (Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0);
}
代码解析: 定义一个函数,用来判断闰年。返回值就是判断润年的方法,如果不是润年就没有返回值。
int y, m, d, i, j = 0;
代码解析: 变量 y 表示年份,变量 m 表示月份,变量 d 表示天数。变量 i 表示循环中的计数。变量 j 则是计算天数的累加和,因为要进行计数,需要先将数值 j 归零。
int k[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
代码解析: 定义一个 k 数组,里面存放 1-12 月份的天数。
scanf("%d %d %d", &y, &m, &d);
代码解析: 获取用户输入年、月、日。
for(i = 2000; i < y; i++)
{
j += L(i) ? 366 : 365;
}
代码解析: 使用 for 循环从 2000 年开始计算,到用户输入的数之前一年结束。因为最后一年由后面月份完成。表达式使用 j 进行累加,调用润年判断函数,通过三目运算判断,如果是闰年则加 366 ,否则加 365 。
if(L(y))
{
k[1] = 29;
}
代码解析: 矫正数组中的数据,判断最后一年是否为闰年,如果是闰年则修改二月份的数据为 29 。
for (i = 1; i < m; i++)
{
j += k[i];
}
j += d;
代码解析: 使用 for 循环累加月份,到最后一月之前结束,同样的最后一月通过天数计算。表达式中使用 j 累加数组中的数据。接着后面使用 j 累加最后一个天数的值。
if (j % 5 == 1 || j % 5 == 2 || j % 5 == 3)
{
printf("今天要努力\n");
}
else
{
printf("偶尔摸会鱼\n");
}
代码解析: 使用 if 判断,三个条件中达成一个就输出 “今天要努力” 。因为三天打鱼两天晒网,所以模余运算时,结果为 1-3 时说明今天是打鱼日。否则输出偶尔摸会鱼。
0x4 完整代码
#include <stdio.h>
int L(int Y)
{
return (Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0);
}
int main()
{
int y, m, d, i, j = 0;
int k[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
scanf("%d %d %d", &y, &m, &d);
for(i = 2000; i < y; i++)
{
j += L(i) ? 366 : 365;
}
if(L(y))
{
k[1] = 29;
}
for (i = 1; i < m; i++)
{
j += k[i];
}
j += d;
if (j % 5 == 1 || j % 5 == 2 || j % 5 == 3)
{
printf("今天要努力\n");
}
else
{
printf("偶尔摸会鱼\n");
}
return 0;
}
0x5 运行效果
2023 06 05
今天要努力
--------------------------------
Process exited after 2.524 seconds with return value 0
请按任意键继续. . .
0x04 兔子生兔子
0x1 题目描述
有一对兔子,从出生后的第 3 个月起每个月都生一对兔子。小兔子长到第 3 个月后每个月又生一对兔子,假设所有的兔子都不死,问 10 个月内每个月的兔子总对数为多少?
0x2 问题分析
关于兔子生兔子得出以下规律:
month | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
rabbit | 2 | 2 | 4 | 6 | 10 | 16 | 26 | 42 | 68 | 110 |
这种题目可以使用函数递归的方法会更清晰易懂,题目的原型是斐波那契数列。
0x3 代码设计
int r(int n)
{
if(n < 3)
{
return 2;
}
else
{
return r(n - 1) + r(n - 2);
}
}
代码解析:定义一个函数用来计算兔子每个月的总数,这个函数可以判断 n 如果小于三,就说明是兔子在前三个月,那么返回 2 表示两只兔子。如果大于三,假设用户输入的数为 5 ,那么执行 (n - 1) + (n - 2) ,表示执行前一项的 n 加上前两项的 n 。注意: n 的数据可以被保留,而不是 1,2,3 。前一项表示 4 - 1,前两项表示 4 - 2 。[2,2,4,6],可以看出第四个数减一就是第三位为 4 ,第四位减去二就是第二位。相加就是 6 。
int main()
{
int n, i;
scanf("%d", &n);
for(i = 1; i <= n; i++)
{
r(i);
printf("%d\t", r(i));
}
return 0;
}
代码解析:在主函数中定义一个变量 n 用来获取用户输入,使用一个 for 循环来循环遍历用户输入的数,从 1 开始。在循环体中调用函数,并打印当前的结果。
0x4 完整代码
#include<stdio.h>
int r(int n)
{
if(n < 3)
{
return 2;
}
else
{
return r(n - 1) + r(n - 2);
}
}
int main()
{
int n, i;
scanf("%d", &n);
for(i = 1; i <= n; i++)
{
r(i);
printf("%d\t", r(i));
}
return 0;
}
0x5 运行效果
10
2 2 4 6 10 16 26 42 68 110
--------------------------------
Process exited after 0.7153 seconds with return value 0
请按任意键继续. . .
0x05 密码解锁
0x1 题目描述
有一个密码锁,需要输入四位数字密码才能打开。但是密码被人忘记了,只记得一些线索。甲说:这个密码的前两位数字是相同的;乙说:这个密码的后两位数字是相同的,但与前两位不同;丙是数学家,他说:这个密码刚好是一个整数的平方。请根据以上线索找出密码。
0x2 问题分析
根据题目描述进行以下分析:
规则一:密码前两位数值相同,设为 aa |
规则二:密码后两位数值相同,设为 bb |
规则三:aabb 的平方根是整数,且(aa ≠ bb) |
根据规则描述,这里的思路是使用双重循环分别枚举出 aa 以及 bb 所有的数值,然后计算 aabb 的平方根是否是整数,如果是整数则输出这个密码。
0x3 代码设计
int i, j, n, s;
代码解析:定义一个变量 i 和 j 用来执行双重循环,定义变量 n 用于后续将前两位数和后两位数进行相加。变量 s 用于存储计算的平方根的值。
for(i = 1; i <= 9; i++)
{
for(j = 1; j <= 9; j++)
{
...
}
}
代码解析:外循环表示前两个数的循环周期,内循环表示后两个数的循环周期。外循环执行一次,内循环执行一轮。
n = i * 1100 + j * 11;
s = sqrt(n);
if(s * s == n)
{
printf("车牌号:%d", n);
}
代码解析:在内循环中执行每一次循环的计算,计算 aabb 的值保存给变量 n 。使用 sqrt 函数计算 aabb 的平方根。使用 if 进行判断,如果 s 的平方等于 n 的值,说明这个数是整数,符合规则三的条件,那么使用 printf 函数输出这个密码。
0x4 完整代码
#include<stdio.h>
#include<math.h>
int main()
{
int i, j, n, s;
for(i = 1; i <= 9; i++)
{
for(j = 1; j <= 9; j++)
{
n = i * 1100 + j * 11;
s = sqrt(n);
if(s * s == n)
{
printf("密码:%d", n);
}
}
}
return 0;
}
0x5 运行效果
密码:7744
--------------------------------
Process exited after 0.01944 seconds with return value 0
请按任意键继续. . .
0x06 最佳存款方案
0x1 题目描述
假设银行一年整存零取的月息为 0.55% 。现在某人手中有一笔钱,他打算在今后 5 年中的每年年底取出 1000 元,到第 5 年时刚好取完,请算出他存钱时应存入多少。
0x2 问题分析
根据题目描述给出存钱公式:
存钱公式 (1000 + s) / (1 + 12 * 0.0055) 可以解释为每年存入1000 元,并在每个月的利率为0.55%的情况下计算存款总额。在每次循环中,上一年的存款总额将与新存入的 1000 元相加,然后除以年利率调整系数 (1 + 12 * 0.0055) 。重复五次循环后,计算出最终的存钱总金额。
0x3 代码设计
int i;
float s;
代码解析:定义整数型变量 i 用于循环中的执行次数,定义浮点型变量 s 用于存储循环体中的数据。
for(i = 1; i <= 5; i++)
{
s = (1000 + s) / (1 + 12 * 0.0055);
}
代码解析:循环五次表示五年,循环体中进行逆推,第五年年初就是 1000/(1+12×0.0055) ,第四年年初就是 (1000 + s) / (1 + 12 * 0.0055) ,其中 s 表示第五年年初的钱。那么第三年就是第四年年初的钱加上 1000 后并计算利润率,以此类推。
printf("存钱数:%.2f", s);
代码解析:输出存钱时要存的数量,使用 .2f 的方法保留两位小数。
0x4 完整代码
#include<stdio.h>
int main()
{
int i;
float s;
for(i = 1; i <= 5; i++)
{
s = (1000 + s) / (1 + 12 * 0.0055);
}
printf("存钱数:%.2f", s);
return 0;
}
0x5 运行效果
存钱数:4144.49
--------------------------------
Process exited after 0.03463 seconds with return value 0
请按任意键继续. . .
0x07 牛顿迭代法
0x1 题目描述
用 Newton 迭代法求方程 ,在 附近的根。
0x2 问题分析
根据牛顿迭代法,迭代公式为:
解:
令,
把 代入
对方程两边同时求导,得:
table:
0 1.41176 -0.72707
1 1.62424 -0.14549
2 1.69230 -0.01316
已知初始猜测值为 1 ,将 1 代入牛顿迭代公式:
第 1 次迭代:
第 2 次迭代:
0x3 代码设计
double f(double x, double a, double b, double c, double d)
{
return a * pow(x, 3) + b * pow(x, 2) + c * x + d;
}
代码解析:定义一个双精度的函数 f ,用于计算方程的表达式。
double F(double x, double a, double b, double c)
{
return 3 * a * pow(x, 2) + 2 * b * x + c;
}
代码解析:定义一个双精度函数 F ,用于计算这个方程表达式的导数。
int main()
{
int k;
double a, b, c, d, x = 1.0, fx; #--x的猜测值--
scanf("%lf %lf %lf %lf", &a, &b, &c, &d);
printf("k\t xk\t\t f(xk)\n");
for (k = 0; k <= 5; k++)
{
fx = f(x, a, b, c, d);
printf("%d\t %lf\t %lf\n", k, x, fx);
x = x - fx / F(x, a, b, c);
}
return 0;
}
代码解析:使用 scanf 函数获取用户输入,使用 printf 函数打印标题 k 、 xk 、f(xk) 。用 for 循环执行迭代,设 k 为 0 ,循环 6 次。将函数表达式计算的结果保存在 fx 中。使用 printf 函数输出第一次迭代的结果。根据牛顿迭代公式更新 x 的猜测值,也就是 x - fx / F(x,a,b,c) 。
0x4 完整代码
#include <stdio.h>
#include <math.h>
double f(double x, double a, double b, double c, double d)
{
return a * pow(x, 3) + b * pow(x, 2) + c * x + d;
}
double F(double x, double a, double b, double c)
{
return 3 * a * pow(x, 2) + 2 * b * x + c;
}
int main()
{
int k;
double a, b, c, d, x = 1.0, fx;
scanf("%lf %lf %lf %lf", &a, &b, &c, &d);
printf("k\t xk\t\t f(xk)\n");
for (k = 0; k <= 5; k++)
{
fx = f(x, a, b, c, d);
printf("%d\t %lf\t %lf\n", k, x, fx);
x = x - fx / F(x, a, b, c);
}
return 0;
}
0x5 运行效果
1 -7.7 19.2 -15.3
k xk f(xk)
0 1.000000 -2.800000
1 1.411765 -0.727071
2 1.623242 -0.145493
3 1.692300 -0.013168
4 1.699910 -0.000151
5 1.700000 -0.000000
--------------------------------
Process exited after 5.78 seconds with return value 0
请按任意键继续. . .
0x08 持续更新···
进制转换 |
0x09 参考文献
[1].百度百科. 百鸡百钱[EB/OL]. [2023-05-29]. https://baike.baidu.com/item/%E7%99%BE%E9%B8%A1%E7%99%BE%E9%92%B1.
[2].刘河飞, 闰凯峰. 趣学Python算法100例[M]. ISBN:978-7-111-66598-4. 北京: 机械工业出版社, 2020.9.
0x10 总结
文章内容为学习记录的笔记,由于作者水平有限,文中若有错误与不足欢迎留言,便于及时更正。