1、请画出例5.6中给出的3个程序段的流程图

解题思路:主要考察 breakcontinue 的使用判断。

  1. // 程序1:完全输出
  2. #include <stdio.h>
  3. int main () {
  4. int n = 0;
  5. for (int i = 1; i <= 4; i++) {
  6. for (int j = 1; j <= 5; j++, n++) {
  7. if (0 == n % 5) { // 5个数一换行
  8. printf("\n");
  9. }
  10. printf("%d\t", i * j);
  11. }
  12. }
  13. printf("\n");
  14. return 0;
  15. }
  16. // 程序2:除了第三行,其它都输出
  17. #include <stdio.h>
  18. int main () {
  19. int n = 0;
  20. for (int i = 1; i <= 4; i++) {
  21. // if (3 == i) continue;
  22. for (int j = 1; j <= 5; j++, n++) {
  23. if (0 == n % 5) { // 5个数一换行
  24. printf("\n");
  25. }
  26. if (3 == i && 1 == j) {
  27. break; // 不打印第三行,等价上面continue
  28. }
  29. printf("%d\t", i * j);
  30. }
  31. }
  32. printf("\n");
  33. return 0;
  34. }
  35. // 程序3:除了第三行第一列不输出(第三行其他列输出),其它都输出
  36. #include <stdio.h>
  37. int main () {
  38. int n = 1; // 让首行不空
  39. for (int i = 1; i <= 4; i++) {
  40. for (int j = 1; j <= 5; j++, n++) {
  41. if (0 == n % 5) { // 5个数一换行
  42. printf("\n");
  43. }
  44. if (3 == i && 1 == j) {
  45. continue; // 不打印第三行第一列
  46. }
  47. printf("%d\t", i * j);
  48. }
  49. }
  50. printf("\n");
  51. return 0;
  52. }

流程图:

image.png image.png image.png

分别编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/matrix1.c -o ./bin/matrix1
b12@PC:~/chapter5$ ./bin/matrix1 # 第一行空白是因为 n = 0

1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
b12@PC:~/chapter5$ gcc -Wall ./src/matrix2.c -o ./bin/matrix2
b12@PC:~/chapter5$ ./bin/matrix2 # 第一行空白是因为 n = 0

1 2 3 4 5
2 4 6 8 10
4 8 12 16 20

b12@PC:~/chapter5$ gcc -Wall ./src/matrix3.c -o ./bin/matrix3
b12@PC:~/chapter5$ ./bin/matrix3
1 2 3 4
5 2 4 6 8
10 6 9 12
15 4 8 12 16
20 :::

2、请分别统计当 课后习题 - 图15课后习题 - 图16时,执行循环体的次数

解题思路:课后习题 - 图17公式求 课后习题 - 图18 的近似值,直到发现某一项 t 的绝对值小于课后习题 - 图19为止。本题就是在统计 while 循环共执行多少次,使用一个 count = 0 变量进行统计即可。
代码也可以使用define epsilon 1e-6 分别进行计算,下面只是 IO

  1. #include <stdio.h>
  2. #include <math.h> // fabs 函数
  3. int main () {
  4. double epsilon, pi = 0.0, denominator = 1.0, item = 1.0;
  5. printf("Please input the epsilon: ");
  6. scanf("%lf", &epsilon); // double 输入要 %lf
  7. int sign = 1, count = 0; // 符号,计数器
  8. while (fabs(item) >= epsilon) {
  9. pi += item; // 注意先加还是后加?
  10. denominator += 2;
  11. sign *= -1;
  12. item = sign / denominator;
  13. count++;
  14. }
  15. pi *= 4;
  16. printf("PI:%10.8f\n", pi);
  17. printf("count:%d\n", count);
  18. return 0;
  19. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/calPi.c -o ./bin/calPi
b12@PC:~/chapte./bin/calPi
Please input the epsilon: 1e-6
PI:3.14159065
count:500000

b12@PC:~/chapter5$ ./bin/calPi
Please input the epsilon: 1e-8
PI:3.14159263
count:50000000 :::

3、输入两个正整数mn,求其最大公约数和最小公倍数。

解题思路:求 gcd(a, b) 是非常常见的问题,书中没有给出辗转相除法(欧几里得算法)的证明,但是我们可以使用暴力算法进行破解,两数最大公因数就是两数因子交集部分最大的那个数。而最小公倍数就是两数乘积除去最大公因数(数学问题,分母通分问题等)
课后习题 - 图20
代码1:暴力枚举

  1. #include <stdio.h>
  2. int main () {
  3. int m, n;
  4. printf("Please input two digits: ");
  5. scanf("%d %d", &m, &n);
  6. // 暴力枚举任一数的因子的同时判断是否是另一个数因子
  7. int gcd = 1; // 最小就是 1
  8. for (int i = 2; i < m; i++) {
  9. if (0 == m % i && 0 == n % i) { // 两者因子交集元素
  10. gcd = i; // 逐渐更新为最大公因数
  11. }
  12. }
  13. printf("gcd(%d, %d):%d\n", m, n, gcd);
  14. printf("lcm(%d, %d):%d\n", m, n, m * n / gcd); // 有溢出风险
  15. return 0;
  16. }

辗转相除法:

课后习题 - 图21

代码2:
更相减损术

  1. #include <stdio.h>
  2. int main () {
  3. int m, n;
  4. printf("Please input two digits: ");
  5. scanf("%d %d", &m, &n);
  6. // 更相减损术:1.可半者半之
  7. int gcd = 1, a = m, b = n; // 半的乘积
  8. while (0 == a % 2 && 0 == n % 2) {
  9. gcd *= 2;
  10. a /= 2;
  11. b /= 2;
  12. }
  13. // 2.不可半者,副置分母、子之数,以少减多,更相减损,求其等也
  14. while (a != b) {
  15. if (a > b) {
  16. a -= b;
  17. } else {
  18. b -= a;
  19. }
  20. }
  21. gcd *= a;
  22. printf("gcd(%d, %d):%d\n", m, n, gcd);
  23. printf("lcm(%d, %d):%d\n", m, n, m * n / gcd); // 有溢出风险
  24. return 0;
  25. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/gcdLcm.c -o ./bin/gcdLcm
b12@PC:~/chapter5$ ./bin/gcdLcm
Please input two digits: 35 49
gcd(35, 49):7
lcm(35, 49):245 :::

4、输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数

解题思路:由于没有学习数组,但是本例也可以直接使用 gerchar() 从终端进行读取单个字符,只需要判断当前读入字符是否是 \n 就结束(这就意味着如果字符中出现换行是不行的!解决办法有点麻烦)

  1. #include <stdio.h>
  2. int main () {
  3. int letters = 0, space = 0, digits = 0, other = 0;
  4. printf("Please input oneline string: ");
  5. char ch = getchar();
  6. while ('\n' != ch) {
  7. if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z')) {
  8. letters++;
  9. } else if ('0' <= ch && ch <= '9') {
  10. digits++;
  11. } else if (' ' == ch) {
  12. space++;
  13. } else {
  14. other++;
  15. }
  16. ch = getchar(); // 再次输入
  17. }
  18. printf("letters:%d, digits:%d, space:%d, other:%d\n",
  19. letters, digits, space, other);
  20. return 0;
  21. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/countChar.c -o ./bin/countChar
b12@PC:~/chapter5$ ./bin/countChar
Please input oneline string: C language is shit!
letters:15, digits:0, space:3, other:1 :::

5、求 课后习题 - 图22 的值,其中 a 是一个数字,n 表示 a 的位数,n由键盘输入,2+22+222+2222+22222(n=5)

解题思路:对于任意 课后习题 - 图23其规律是 课后习题 - 图24,但是由于 课后习题 - 图25,因此通项公式就是 课后习题 - 图26
但是需要注意的就是第 0 项应该如何处理,这是小细节,虽然题目没说出,但是默认其就是 0 ,即只能从第一项开始。

  1. #include <stdio.h>
  2. int main () {
  3. int a, n;
  4. printf("Please input a and n (eg: 2 5): ");
  5. scanf("%d %d", &a, &n);
  6. double sum = 0, item = 0; // 注意初始化
  7. for (int i = 1; i <= n; i++) {
  8. item = item * 10 + a; // 上一项的只保留在item中
  9. sum += item; // 前后相加顺序?
  10. }
  11. printf("sum:%.0f\n", sum);
  12. return 0;
  13. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/seqSum.c -o ./bin/seqSum
b12@PC:~/chapter5$ ./bin/seqSum
Please input a and n (eg: 2 5): 2 5
sum:24690

b12@PC:~/chapter5$ ./bin/seqSum
Please input a and n (eg: 2 5): 11 3 # 尝试两位数?
sum:1353 ::: 从上面输入两位数开始看到,居然不行了?因为 课后习题 - 图27啊!为什么出现这种情况,因为位权×10,而不是×100,因为是两位数,其该“左移两位”然后再加上新的 11 才是该项结果。

因此对于输入任意数字而不是只有个位数,只需要计算该数字长度就可知道前一个数字“左移多少位”。

  1. #include <stdio.h>
  2. #include <math.h>
  3. int main () {
  4. int a, n;
  5. printf("Please input a and n (eg: 2 5): ");
  6. scanf("%d %d", &a, &n);
  7. if (0 == a || 0 == n) { // 判断原因就是 log10(a) 要求真数大于0
  8. printf("sum:0\n");
  9. return -1; // 异常
  10. }
  11. int bitpower = (int)pow(10, (int)log10(a) + 1); // 1:10,11:100
  12. double sum = 0, item = 0; // double 防止溢出
  13. for (int i = 1; i <= n; i++) {
  14. item = item * bitpower + a; // 上一项的只保留在item中
  15. printf("item:%f\n", item);
  16. sum += item;
  17. }
  18. printf("sum:%.0f\n", sum);
  19. return 0;
  20. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/seqSum.c -o ./bin/seqSum -lm
b12@PC:~/chapter5$ ./bin/seqSum
Please input a and n (eg: 2 5): 11 6
item:11.000000
item:1111.000000
item:111111.000000
item:11111111.000000
item:1111111111.000000
item:111111111111.000000
sum:112233445566 # 早就超过int范围 :::

6、求阶乘和 课后习题 - 图28

解题思路:通项公式已给出,课后习题 - 图29,而相邻两项之间有特殊关系 课后习题 - 图30。下面分别尝试暴力法和使用相邻两项迭代方式验证程序。

  1. #include <stdio.h>
  2. double factorial(int a) {
  3. double fact = 1; // 0!,或大数int溢出
  4. for (int i = 2; i <= a; i++) {
  5. fact *= i;
  6. }
  7. return fact;
  8. }
  9. int main () {
  10. int n;
  11. printf("Please input n: ");
  12. scanf("%d", &n);
  13. double sum = 0; // 注意可能溢出
  14. if (0 == n) {
  15. printf("sum:%22.15e\n", sum);
  16. return 0;
  17. }
  18. for (int i = 1; i <= n; i++) { // 从第一项开始
  19. sum += factorial(i);
  20. }
  21. // 优化,使用前一项值
  22. // double sum = 0, item = 1; // double 防止溢出
  23. // for (int i = 1; i <= n; i++) {
  24. // item = item * i; // 上一项的值保留在item中
  25. // sum += item;
  26. // }
  27. printf("sum:%22.15e\n", sum);
  28. return 0;
  29. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/factorialSum.c -o ./bin/factorialSum
b12@PC:~/chapter5$ ./bin/factorialSum
Please input n: 20
sum: 2.561327494111820e+18
b12@PC:~/chapter5$ ./bin/factorialSum
Please input n: 0
sum: 0.000000000000000e+00 :::

7、求公式和 课后习题 - 图31

解题思路:没有规律,三次 for 暴力求解。 :::tips 请不要在一层 for 里面使用 if 判断,这样开销还不如三次 for 小。 :::

  1. #include <stdio.#include <stdio.h>
  2. #define N1 100
  3. #define N2 50
  4. #define N3 10
  5. int main () {
  6. double sum = 0;
  7. for (int i = 1; i <= N1; i++) {
  8. sum += i;
  9. }
  10. for (int i = 1; i <= N2; i++) {
  11. sum += i * i; // double防止溢出
  12. }
  13. for (int i = 1; i <= N3; i++) {
  14. sum += 1 / i; // 实数除法而不是整数除数
  15. }
  16. printf("sum:%15.6f\n", sum);
  17. return 0;
  18. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/getSum.c -o ./bin/getSum
b12@PC:~/chapter5$ ./bin/getSum
sum: 47976.000000 # 输出与机器有关,精度问题 :::

8、输出所有的“水仙花数”。所谓“水仙花数”是指一个3位数,其各位数字立方和等于该数本身。例如,153是一水仙花数,因为 课后习题 - 图32

解题思路:首先三位数分别拆分,然后进行 n 次幂计算得到和即可。如下程序是对原来答案的扩展,可以接受人任意位数,并在区间 课后习题 - 图33 进行枚举。

  1. #include <stdio.h>
  2. #include <math.h>
  3. int main () {
  4. int n;
  5. printf("Please input a number: ");
  6. scanf("%d", &n);
  7. // 1.开始进行验证
  8. for (int start = pow(10, n - 1), end = pow(10, n); start < end; start++) {
  9. int x = start, sum = 0;
  10. do {
  11. int remainder = x % 10, product = 1;
  12. for (int i = 0; i < n; i++) {
  13. product *= remainder;
  14. }
  15. sum += product;
  16. x /= 10;
  17. } while (0 != x);
  18. if (sum == start) {
  19. printf("%d ", start);
  20. }
  21. }
  22. printf("\n");
  23. return 0;
  24. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/Daffodils.c -o ./bin/Daffodils -lm
b12@PC:~/chapter5$ ./bin/Daffodils
Please input a number: 3
153 370 371 407
b12@PC:~/chapter5$ ./bin/Daffodils
Please input a number: 4
1634 8208 9474
b12@PC:~/chapter5$ ./bin/Daffodils
Please input a number: 5
54748 92727 93084 :::

9、一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如,6 的因子为 1,2,3,而 6=1+2+3,因此 6 是“完数”。编程序找出1000之内的所有完数,并按下面格式输出其因子: 6 its factors are 1 2 3

解题思路:一个数因子就是可以这个数能被其因子整除。因此最简单的方式就是暴力枚举所有的因子,然后累加和是否等于 n 即可,所有的过程可以使用递归实现,但是其结果就是与上述要求相反,因为递归在退栈的时候进行打印(本质就是两次枚举)。

  1. #include <stdio.h>
  2. int factorSum(int factor, int n, int sum) {
  3. // factor从小到大枚举到n,并累加到sum
  4. if (sum > n) { // 当因子和大于n肯定不是完数
  5. return 0;
  6. } else if (factor == n) { // 当枚举到自身即是递归终止判断
  7. if (n == sum) { // 根据因子和是否等于n判断返回
  8. printf("%d its factors are ", n);
  9. return 1;
  10. } else {
  11. return 0;
  12. }
  13. } else if (0 == n % factor) { // 对于当前是n的因子,需要根据最后结果判断进行打印
  14. if (factorSum(factor + 1, n, sum + factor)) {
  15. printf("%d ", factor);
  16. return 1;
  17. }
  18. } else { // 否则当前不是n的因子,继续加大factor进行枚举,一定要将最后的状态返回
  19. return factorSum(factor + 1, n, sum);
  20. }
  21. return 0;
  22. }
  23. int main () {
  24. int n;
  25. printf("Please input a number(2->1000): ");
  26. scanf("%d", &n);
  27. for (int i = 2; i <= n; i++) {
  28. if (factorSum(1, i, 0)) {
  29. printf("\n");
  30. }
  31. }
  32. return 0;
  33. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/perfectNum.c -o ./bin/perfectNum
b12@PC:~/chapter5$ ./bin/perfectNum
Please input a number(1->1000): 1000
6 its factors are 3 2 1
28 its factors are 14 7 4 2 1
496 its factors are 248 124 62 31 16 8 4 2 1 ::: 至于书本上第一个使用 switch-case 和如何获知 1000 范围内怎么就有 10 个因子的变量,其实就是他自己亲自试出来的,相比之下,其使用 for 循环暴力枚举过程更加清晰易懂。

  1. #include <stdio.h>
  2. int main () {
  3. int n;
  4. printf("Please input a number(2->1000): ");
  5. scanf("%d", &n);
  6. for (int i = 2; i <= n; i++) { // 1.枚举2->n以内的数
  7. int sum = 0;
  8. for (int j = 1; j < i && sum <= i; j++) { // 当因子大于i可以终止
  9. if (0 == i % j) {
  10. sum += j;
  11. }
  12. }
  13. if (sum == i) {
  14. printf("%d, its factors are ", i);
  15. for (int j = 1; j < i; j++) {
  16. if (0 == i % j) {
  17. printf("%d ", j);
  18. }
  19. }
  20. printf("\n");
  21. }
  22. }
  23. return 0;
  24. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/perfectNum.c -o ./bin/perfectNum
b12@PC:~/chapter5$ ./bin/perfectNum
Please input a number(2->1000): 600
6, its factors are 1 2 3
28, its factors are 1 2 4 7 14
496, its factors are 1 2 4 8 16 31 62 124 248 :::

10、有一个分数序列:课后习题 - 图34,求出前 20 项和。

解题思路:照题意可以发规律,第 n+1 项的分子是第 n 项分子分母之和,第 n+1 项的分母是第 n 项分子,因此照着写出程序即可。

  1. #include <stdio.h>
  2. int main () {
  3. int n;
  4. printf("Please input a number: ");
  5. scanf("%d", &n);
  6. double numerator = 1, denominator = 2, sum = 0, tmp;
  7. for (int i = 1; i <= n; i++) {
  8. sum += denominator /numerator; // 为什么在这个位置?而不是在最后?与初始化有关
  9. tmp = numerator; // 先保存前一项(n-1)的分子
  10. numerator = numerator + denominator; // 更新第n项分子
  11. denominator = tmp; // 更新第n项分母
  12. }
  13. printf("sum = %16.10f\n", sum);
  14. return 0;
  15. }

编译运行:(因为浮点数原因,与书本结果有误差很正常) :::success b12@PC:~/chapter5$ gcc -Wall ./src/seqSum.c -o ./bin/seqSum
b12@PC:~/chapter5$ ./bin/seqSum
Please input a number: 20
sum = 32.4386015793
b12@PC:~/chapter5$ ./bin/seqSum
Please input a number: 100
sum = 161.8813206723 :::

11、一个球从 100m 高度自由落下,每次落地后反跳回原高度的一半,再落下,再反弹。求它在第10次落地时,共经过多少米,第10次反弹多高

解题思路:明确初始状态和每次落地经过的路程问题是理顺整个循环的关键。往往整个循环不在于难,而在于初始和结束状态的细节问题上。11、小球落地.png
如上图所示,初始状态就是高度 h = 100 ,路程因为还没掉落所以是 s = 0 ,当第一次掉落后每个状态对应的就是上下两个过程(路程是两倍的高度,落地次数比弹起高度多一次),即是说后面的都是重复过程,唯独第一次下落比较特殊。

  1. #include <stdio.h>
  2. #define Height 100
  3. int main () {
  4. int n;
  5. printf("Please input a number: ");
  6. scanf("%d", &n);
  7. double sn = Height, hn = Height / 2; // hn已经是第二次开始状态
  8. for (int i = 2; i <= n; i++) {
  9. sn += 2 * hn;
  10. hn /= 2;
  11. }
  12. printf("S%d = %fm\n", n, sn);
  13. printf("%d bounce %fm\n", n, hn);
  14. return 0;
  15. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/bounce.c -o ./bin/bounce
b12@PC:~/chapter5$ ./bin/bounce
Please input a number: 10
S10 = 299.609375m
10 bounce 0.097656m

b12@PC:~/chapter5$ ./bin/bounce
Please input a number: 4
S4 = 275.000000m
4 bounce 6.250000m :::

12、猴子吃桃问题。猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第2天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,就只剩一个桃子了。求第1天共摘了多少个桃子。

解题思路:同上题解题思路,本题是倒推过程,给你末状态让你推出初始状态。需要注意的就是第 n 天吃之前的桃子数是第 n-1 天吃了后剩下的。不妨设猴子中午吃桃子,那么最后一天上午之前的桃子就是前一天下午留的。最重要的是题目所说的终状态是 f(9) = 1 即第 9 天没有吃,只剩下一个,让求第一天早上在它吃刚摘桃子之前的个数,而不是第一题它吃完剩下的个数)共摘了多少个。(很多人会多算一倍
即有如下公式:课后习题 - 图36
然后现在给定状态为第 10 天早上剩下 1 个,即是第 9 天晚上剩余 1 状态。根据上述公式设第 n-1 天早上共有 x 个,则有方程为 课后习题 - 图37,如此得到第 n-1 天早上有课后习题 - 图38,如此递推即可得到第 1 天早上剩余多少个。这也是为何程序执行 9 次而不是 10 次。

第一天 第二天 第三天 第四天 第五天 第六天 第七天 第八天 第九天 第十天
早上 1534 766 382 190 94 46 22 10 4 1
开吃 768 384 192 96 48 24 12 6 3
晚上 766 382 190 94 46 22 10 4 1

对原文的题目进行拓展,设输入第 n 天,剩余 k 个,求第一天(早上)共摘了多少个。

  1. #include <stdio.h>
  2. int main () {
  3. int n, k;
  4. printf("Please input day, rest peaches: ");
  5. scanf("%d %d", &n ,&k);
  6. if (0 >= k || 0 >= n || 0 == k % 2) {
  7. printf("Invalid input\n");
  8. return -1;
  9. }
  10. int morning, night = k;
  11. while (n > 1) { // 循环执行 n-1 次原因?
  12. morning = 2 * (night + 1); // 今天早上
  13. printf("Day %d ate %d peaches\n", n - 1, morning / 2 + 1);
  14. night = morning; // 前一天晚上(为什么?)
  15. n--;
  16. }
  17. printf("First day : %d\n", morning);
  18. return 0;
  19. }

编译运行:(不要太大数字,会导致整型溢出;且 k 必定为奇数) :::success b12@PC:~/chapter5$ gcc -Wall ./src/peaches.c -o ./bin/peaches
b12@PC:~/chapter5$ ./bin/peaches
Please input day, rest peaches: 10 1
Day 9 ate 3 peaches
Day 8 ate 6 peaches
Day 7 ate 12 peaches
Day 6 ate 24 peaches
Day 5 ate 48 peaches
Day 4 ate 96 peaches
Day 3 ate 192 peaches
Day 2 ate 384 peaches
Day 1 ate 768 peaches
First day : 1534

b12@PC:~/chapter5$ ./bin/peaches
Please input day, rest peaches: 15 3
Day 14 ate 5 peaches
Day 13 ate 10 peaches
Day 12 ate 20 peaches
Day 11 ate 40 peaches
Day 10 ate 80 peaches
Day 9 ate 160 peaches
Day 8 ate 320 peaches
Day 7 ate 640 peaches
Day 6 ate 1280 peaches
Day 5 ate 2560 peaches
Day 4 ate 5120 peaches
Day 3 ate 10240 peaches
Day 2 ate 20480 peaches
Day 1 ate 40960 peaches
First day : 81918 :::

13、用迭代法求 课后习题 - 图39 求平方根的迭代公式为 课后习题 - 图40

69.x的平方根(简单)
上图有个公式 课后习题 - 图41就是泰勒级数的简化,其定义为:如果在点 课后习题 - 图42具有任意阶导数,则幂级数
课后习题 - 图43
称为在点 课后习题 - 图44 处的泰勒级数。(为什么可以拿来求根?我菜不知道)

14、用牛顿迭代法求下面方程在 1.5 附近的根:

课后习题 - 图45
解题思路:本题就是上一题一样,无非就是前者泰勒级数的一阶导数 课后习题 - 图46而已。即有 课后习题 - 图47成立。令 课后习题 - 图48,求得其切线与 x 轴交点即 课后习题 - 图49

  1. 设方程的第一个迭代点 课后习题 - 图50,不断进行迭代获得 课后习题 - 图51,直到两点之间距离小于 课后习题 - 图52 即可得到答案。
  2. 关键是现在如何求出 课后习题 - 图53 呢?求导脑子算很容易,但是计算机恐怕难了,如何给它一个方程让它自动给我们求出导数呢?(提示:多项式的表达方式以及用链表实现求导)。但是本题很简单的方程,我们可以直接写出导数 课后习题 - 图54,也就是硬编码,只对当前这个方程管用,其它不管。
  3. 关于书中出现 课后习题 - 图55

是怎么来的?为什么可以这样?原因是减少多项式乘法次数,简单说就是「秦九算法」,也可以不需要这样算,直接用 pow(x, n) 或者暴力循环求 x 的幂计算。同样该乘法也只针对本函数进行处理,也是硬编码

  1. #include <stdio.h>
  2. #include <math.h>
  3. #define EPS 1e-5
  4. int main () {
  5. // x0表示前一个与x轴交点,x1后一个与x轴交点,fx表示f(x),f1表示f'(x)
  6. double x0 = 1.5, x1, fx, f1;
  7. while (1) { // 注意为什么要参考答案用do-while,区别在哪里?
  8. fx = ((2 * x0 - 4) * x0 + 3) * x0 - 6;
  9. f1 = (6 * x0 - 8) * x0 + 3;
  10. x1 = x0 - fx / f1;
  11. if (fabs(x0 - x1) < EPS) break;
  12. x0 = x1; // 记录x1
  13. }
  14. printf("The root of equation is %5.2f\n", x1);
  15. return 0;
  16. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/root.c -o ./bin/root -lm
b12@PC:~/chapter5$ ./bin/root
The root of equation is 2.00 :::

15、用二分法求下面方程在(-10,10)之间的根:

课后习题 - 图56

零点定理:如果函数 课后习题 - 图57 在区间 课后习题 - 图58上的图象是连续不断的一条曲线,并且有 课后习题 - 图59,那么函数课后习题 - 图60 在区间 课后习题 - 图61 内有零点,即至少存在一个 课后习题 - 图62 使得 课后习题 - 图63 ,这个c也就是方程 课后习题 - 图64 的根。


1. 函数图像如图,可知其是单调递增的,证明:课后习题 - 图65课后习题 - 图66,由 a = 6 > 0 可知 课后习题 - 图67 恒成立,因此函数单调递增。
1. 由零点定理,只需要判断区间端点是否异号得知区间是否有效,题目给定区间 课后习题 - 图68
1. 判断输入区间内是否存在零点 课后习题 - 图69 成立?是则执行 4
1. 设 课后习题 - 图70 ,中心点 课后习题 - 图71
1. 当 课后习题 - 图72 成立,收缩区间为 课后习题 - 图73
1. 当 课后习题 - 图74 成立,收缩区间为 课后习题 - 图75
image.png

N-S流程图
image.png

  1. #include <stdio.h>
  2. #include <math.h>
  3. #define EPS 1e-5
  4. int main () {
  5. // 1.区间端点值检测,为什么用do-while?
  6. double x1, x2, x0, fx1, fx2, fx0;
  7. do {
  8. printf("Please input x1 & x2:");
  9. scanf("%lf %lf", &x1, &x2);
  10. fx1 = ((2 * x1 - 4) * x1 + 3) * x1 - 6;
  11. fx2 = ((2 * x2 - 4) * x2 + 3) * x2 - 6;
  12. } while (fabs(fx1 * fx2 > 0));
  13. // 2.二分收缩区间,因为一定存在f(x1)*f(x2)>0可知目标区间
  14. while (fabs(x1 - x2) >= EPS) { // 注意与书本f(x0)<EPS?
  15. x0 = (x2 + x1) / 2; // double 不考虑溢出
  16. fx0 = ((2 * x0 - 4) * x0 + 3) * x0 - 6;
  17. if (fx0 * fx1 < 0) { // 证明零点在[x1,x0]
  18. x2 = x0;
  19. fx2 = fx0;
  20. } else { // 证明零点在[x0,x2]
  21. x1 = x0;
  22. fx1 = fx0;
  23. }
  24. }
  25. printf("x = %6.2f\n", x0);
  26. return 0;
  27. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/bisectRoot.c -o ./bin/bisectRoot -lm
b12@PC:~/chapter5$ ./bin/bisectRoot
Please input x1 & x2:-10 1.5
Please input x1 & x2:-10 10
x = 2.00 :::

16、输出菱形

PTA编程—循环2
上面是综合成一循环处理,使用的就是中心对称思想,但是书本上给出分为两部分处理,上面 4 行,下面 3 行单独输出。(教材上答案是相邻星星是没有空格的,并且很多关系不清楚,从 0 开始数很容易让人迷惑

  1. #include <stdio.h>
  2. int main () {
  3. // 1.共4行
  4. for (int row = 0; row < 4; row++) {
  5. // 2.3-row前置空格
  6. for (int i = 0; i < 4 - 1 - row; i++) {
  7. printf(" ");
  8. }
  9. // 3.2 * row + 1 个星星 == [0,2*row]
  10. for (int j = 0; j < 2 * row + 1; j++) {
  11. printf("*");
  12. }
  13. // 4.每行换行
  14. printf("\n");
  15. }
  16. // 5.后打印 3 行
  17. for (int row = 0; row < 3; row++) {
  18. // 6.row+1前置空格
  19. for (int i = 0; i < 1 + row; i++) {
  20. printf(" ");
  21. }
  22. // 7.5 - 2 * row 个星星 == [0,2*row]
  23. for (int j = 0; j < 5 - 2 * row; j++) {
  24. printf("*");
  25. }
  26. // 8.每行换行
  27. printf("\n");
  28. }
  29. }

编译运行: :::success b12@PC:~/chapter5$ gcc -Wall ./src/asterisk.c -o ./bin/asterisk
b12@PC:~/chapter5$ ./bin/asterisk

**

*
*
*
**
:::

17、两个乒乓球队进行比赛,各出3人。甲队为A,B,C,乙队为X,Y,Z。已抽签决定比赛名单。有人向队员打听比赛的名单,A说他不和X比,C说他不和X,Z比,请编程序找出 3 对赛手的名单。

解题思路:逻辑推理题

  1. X 既不与 A 比赛,又不与 C 比赛,必然与 B 比赛。
  2. C 既不与 X 比赛,又不与 Z 比赛,必然与 Y 比赛。
  3. 剩下的只能是 A 与 Z 比赛

乒乓比赛2.png
使用计算机思维就是枚举:根据排列共有 课后习题 - 图79种可能,对应下图右侧 6 个枚举可能,最后只要验证即可。

A对X枚举:后面全错 image.png
A对Y枚举:
- B对X,则C对Z(错误)
- B对Z,则C对X(错误)
image.png
A对Z枚举:
- B对X,则C对Y(成功)
- B对Y,则C对X(错误)
image.png

但是我们怎么模拟这个过程,找到一种组合就开始验证是否成立,否则就错误。被选择

回溯法:暴力枚举所有状态,不行就撤销,也就是当前选择的状态会影响到之后的选择的时候就用回溯法。 与用 for 或者 while 枚举最大区别就是需要知道之前从哪里来要回哪里去,即必须存储之前的选择,而函数递归就是栈实现的。

  1. #include <stdio.h>
  2. char team1[3] = {'A', 'B', 'C'}; // 甲队共三人,用字符表示
  3. char team2[3] = {'X', 'Y', 'Z'}; // 乙队共三人,用字符表示
  4. int team2PalyerisChosen[3] = {0, 0, 0}; // 标志乙队对手是否被选,默认没选
  5. // 记录两队比赛信息,甲队第 i 个对手 VS 乙队第 matchInfo[i] 个对手
  6. int matchInfo[3] = {-1, -1, -1}; // -1 只是初始化表示都没选择
  7. void backtrace(int p) {
  8. /* 回溯函数:枚举甲队第 team1[p] 队员对战乙队队员的所有可能 */
  9. // 1.递归终止条件,p == 3
  10. if (3 == p) {
  11. // 此 if 语句就是上面逻辑判断,如果匹配信息合法,那么就打印比赛信息
  12. if (matchInfo[0] != 0 && matchInfo[2] != 0 && matchInfo[2] != 2) {
  13. printf("%c VS %c\n", team1[0], team2[matchInfo[0]]);
  14. printf("%c VS %c\n", team1[1], team2[matchInfo[1]]);
  15. printf("%c VS %c\n", team1[2], team2[matchInfo[2]]);
  16. }
  17. return;
  18. }
  19. // 2.回溯枚举:甲队第 p 个队员可能与乙队 X,Y,Z 都 PK
  20. for (int i = 0; i < 3; i++) {
  21. // team1[p] 和 乙队第 i 个队员 pk 前提是 乙队第 i 个队员还没和别人配对
  22. if (!team2PalyerisChosen[i]) {
  23. // 选择状态:乙队第 i 个队员被选中; 记录 甲队第 p 个选手 VS 乙队第 i 个对手
  24. team2PalyerisChosen[i] = 1;
  25. matchInfo[p] = i;
  26. backtrace(p + 1); // 开始甲队下一个队员匹配情况
  27. // 撤销状态:甲队第 p 个选手 VS 乙队第 i 个选手 导致之后比赛不合法
  28. team2PalyerisChosen[i] = 0; // 表示乙队第 i 个选手尚未匹配
  29. matchInfo[p] = -1; // 表示甲队第 p 个选手比赛信息尚未确定
  30. }
  31. }
  32. }
  33. int main () {
  34. backtrace(0);
  35. return 0;
  36. }

但是其实可以直接写成书本上的 for 嵌套枚举,这样更容易理解,但是上述「回溯法」是理解递归之必须的,且是最常用的暴力枚举法,也是动态规划的前奏。实在不清楚可以看下面状态的清理过程。
乒乓球比赛.pptx

  1. #include <stdio.h>
  2. int main () {
  3. for (int A = 'X'; A <= 'Z'; A++) {
  4. if ('X' == A) continue; // 剪枝1:A<->X
  5. for (int B = 'X'; B <= 'Z'; B++) {
  6. if (B == A) continue; // 剪枝:一对一
  7. for (int C = 'X'; C <= 'Z'; C++) {
  8. if (C == B || C == A) continue; // 剪枝:一对一
  9. if ('X' != C && 'Z' != C) { // 剪枝2:C<->X,C<->Z
  10. printf("A vs %c\nB vs %c\nC vs %c\n", A, B, C);
  11. }
  12. }
  13. }
  14. }
  15. return 0;
  16. }

编译运行:(注意:以上 continue 都可以使用 if 替换为相反条件) :::success b12@PC:~/chapter5$ gcc -Wall ./src/pingpangTest.c -o ./bin/pingpangTest
b12@PC:~/chapter5$ ./bin/pingpangTest
A vs Z
B vs X
C vs Y :::