实验三
实验三
任务1
task1.c源代码
点击查看代码
#include <stdio.h>
char score_to_grade(int score); // 函数声明
int main() {
int score;
char grade;
while(scanf("%d", &score) != EOF) {
grade = score_to_grade(score); // 函数调用
printf("分数: %d, 等级: %c\n\n", score, grade);
}
return 0;
}
// 函数定义
char score_to_grade(int score) {
char ans;
switch(score/10) {
case 10:
case 9: ans = 'A'; break;
case 8: ans = 'B'; break;
case 7: ans = 'C'; break;
case 6: ans = 'D'; break;
default: ans = 'E';
}
return ans;
}
运行测试截图

回答问题
问题1:函数score_to_grade的功能是什么?形参类型、返回值类型是什么?
函数score_to_grade的功能是判断分数对应的等级并返回该等级对应的值。形参类型为int型(整型),返回值类型为char型(字符型)。
问题2:如果line21-28以下形式,代码存在哪些问题,请逐一指出。
点击查看代码
switch(score/10) {
case 10:
case 9: ans = "A";
case 8: ans = "B";
case 7: ans = "C";
case 6: ans = "D";
default: ans = "E";
}
源代码line23-26的语句后都跟随一个break语句,在执行完ans的赋值语句之后,跳出整个switch结构。而修改后的代码缺少break语句,会导致执行完ans的赋值语句后,会无视后续case的条件,直接继续执行下一个case中的语句,直到switch结束。在task1.c中,修改后会导致ans被多次赋值,最终赋值为字符E,运行测试如下图。

任务2
task2.c源代码
点击查看代码
#include <stdio.h>
int sum_digits(int n); // 函数声明
int main() {
int n;
int ans;
while(printf("Enter n: "), scanf("%d", &n) != EOF) {
ans = sum_digits(n); // 函数调用
printf("n = %d, ans = %d\n\n", n, ans);
}
return 0;
}
// 函数定义
int sum_digits(int n) {
int ans = 0;
while(n != 0) {
ans += n % 10;
n /= 10;
}
return ans;
}
运行测试截图
回答问题
问题1:函数sum_digits的功能是什么?
计算各位数字之和。
问题2:如果把函数sum_digits定义成如下实现方式,能实现同样的输出吗?如果能,说明两种实现方式的算法思维区别;如果不能,分析原因。
点击查看代码
int sum_digits(int n) {
if(n < 10)
return n;
return sum_digits(n/10) + n%10;
}
两段代码能实现相同的输出,但实现方式不同。
原代码(迭代法):采用循环结构以 n自身作为边界判定条件,循环累加。
新代码(递归法):采用函数自身调用,将整数通过n/10逐位拆解,并以if(n<10)作为递归边界条件,最终将各数位(n%10)累加返回。”
任务3
task3.c源代码
点击查看代码
#include <stdio.h>
int power(int x, int n); // 函数声明
int main() {
int x, n;
int ans;
while(printf("Enter x and n: "), scanf("%d%d", &x, &n) != EOF) {
ans = power(x, n); // 函数调用
printf("n = %d, ans = %d\n\n", n, ans);
}
return 0;
}
// 函数定义
int power(int x, int n) {
int t;
if(n == 0)
return 1;
else if(n % 2)
return x * power(x, n-1);
else {
t = power(x, n/2);
return t*t;
}
}
运行测试截图
回答问题
问题1:函数power的功能是什么?
计算x的n次幂。
问题2:函数power是递归函数吗?如果是,找出递归模式,写出其对应的数学公式模型。(公式中须体现递归出口)
函数power是递归函数。对应的数学公式如下:
问题3:如果对line26-27做改动:t=power(x,n/2);return t*t;,改成:return power(x,n/2) * power(x,n/2);
① 算法逻辑上是否仍然正确?为什么?
仍然正确。原函数将power(x,n/2)的值赋给变量t,返回值返回t*t,与新函数中power(x,n/2) * power(x,n/2)是等价的,虽然求值过程不同,但返回值完全相同。
② 与原写法相比,从性能角度分析有何不同?请从"子问题是否重复计算"的角度分析,并估算两种写法的递归调用次数(以计算 power(3,16) 为例)。
原写法比新写法更加高效。原算法power(x,n/2)的结果被保存到变量t中,后续直接复用t*t,每个子问题仅计算一次,递归调用形成一条单链;新写法每次偶数分支都会独立地触发两次power(x,n/2)调用,产生大量冗余计算。递归调用展开为满二叉树结构。原算法的时间复杂度更低。
以power(3,16)为例估算两种写法发递归调用次数:
原写法:power(3,16)→power(3,8)→power(3,4)→power(3,2)→power(3,1)
共调用5次。
新写法:共调用31次,调用方式如下图:

任务4
task4.c源代码
点击查看代码
#include<stdio.h>
int a,b,c;
int classify_triangle(int a,int b,int c)
{
if(a+b>c&&a+c>b&&b+c>a) //判断是不是三角形
{
if(a==b&&b==c) //判断是不是等边三角形
return 2;
else
{
if(a==b||b==c||a==c) //判断是不是等腰三角形
return 3;
else
{
if(a*a+b*b==c*c||a*a+c*c==b*b||a*a==b*b+c*c) //判断是不是直角三角形
return 4;
else
return 1; //上面的情况都不满足,一般三角形
}
}
}
else
return 0;
}
int main()
{
while( scanf("%d%d%d",&a,&b,&c)!=EOF)
{
switch(classify_triangle(a,b,c))
{
case 0:printf("不能构成三角形\n");break;
case 1:printf("普通三角形\n");break;
case 2:printf("等边三角形\n");break;
case 3:printf("等腰三角形\n");break;
case 4:printf("直角三角形\n");
}
}
return 0;
}
运行测试截图

任务5
task5_1.c源代码(迭代方式)
点击查看代码
#include <stdio.h>
int func(int n, int m); // 函数声明
int main() {
int n, m;
int ans;
while(scanf("%d%d", &n, &m) != EOF) {
ans = func(n, m); // 函数调用
printf("n = %d, m = %d, ans = %d\n\n", n, m, ans);
}
return 0;
}
int func(int n,int m) // 函数定义
{
int ans,s1=1,s2=1,i;
for(i=n;i>=n-m+1;i--)
s1*=i;
for(i=2;i<=m;i++)
s2*=i;
ans=s1/s2;
return ans;
}
运行测试截图

task5_2.c源代码(递归函数)
点击查看代码
#include <stdio.h>
int func(int n, int m); // 函数声明
int main() {
int n, m;
int ans;
while(scanf("%d%d", &n, &m) != EOF) {
ans = func(n, m); // 函数调用
printf("n = %d, m = %d, ans = %d\n\n", n, m, ans);
}
return 0;
}
int func(int n, int m) // 函数定义
{
if(m>n)
return 0;
else if(m==n||m==0)
return 1;
else
return func(n-1,m)+ func(n-1,m-1);
}
运行测试截图

任务6
task6.c源代码
点击查看代码
#include <stdio.h>
int gcd(int a,int b,int c);// 函数声明
int main() {
int a, b, c;
int ans;
while(scanf("%d%d%d", &a, &b, &c) != EOF) {
ans = gcd(a, b, c); // 函数调用
printf("最大公约数: %d\n\n", ans);
}
return 0;
}
int gcd(int a,int b,int c)// 函数定义
{
int m;
if(a>=b&&c>=b)
m=b;
if(b>=a&&c>=a)
m=a;
if(a>=c&&b>=c)
m=c;
for(;m>=1;m--)
{
if(a%m==0&&b%m==0&&c%m==0)
return m;
}
}
运行测试截图

任务7
task7.c源代码
点击查看代码
#include <stdio.h>
#include <stdlib.h>
void print_charman(int n);// 函数声明
int main() {
int n;
printf("Enter n: ");
scanf("%d", &n);
print_charman(n); // 函数调用
return 0;
}
void print_charman(int n)// 函数print_charman定义
{
int flag=1,i,j,k=n*2-1;
for(i=1;i<=n;i++)
{
if(i==1)
k--;
if(i==1)
printf(" O");
for(j=2;j<=i-1;j++)
printf("\t");
for(j=1;j<=k;j++)
printf("\t O");
printf("\n");
if(i==1)
printf("<H>");
for(j=2;j<=i-1;j++)
printf("\t");
if(i!=1)
printf(" ");
for(j=1;j<=k;j++)
printf(" <H>");
printf("\n");
if(i==1)
printf("I I");
for(j=2;j<=i-1;j++)
printf("\t");
if(i!=1)
printf(" ");
for(j=1;j<=k;j++)
printf(" I I");
printf("\n");
if(i!=1)
k-=2;
else
k--;
}
return ;
}
运行测试截图




实验总结
关于递归算法和迭代算法
| 对比维度 | 迭代 | 递归 |
|---|---|---|
| 定义 | 通过循环结构(for / while)反复执行代码块,直到条件不成立 | 函数直接或间接调用自身,把问题分解为结构相同的子问题 |
| 核心思想 | 逐步逼近,由初始状态反复更新到目标状态 | 分而治之,自顶向下分解+回溯合并结果 |
| 控制结构 | 循环语句(for/while/do-while) | 选择语句(if-else)+函数自身调用 |
| 终止条件 | 循环条件为false时退出 | 递归出口被触发时返回 |
| 状态传递 | 通过局部变量在循环中就地更新 | 通过函数参数向下一层传递,返回值向上传递 |
| 时间复杂度 | 通常较低,无函数调用开销 | 一般略高;会存在重复子问题 |
| 空间复杂度 | 仅占用少量循环变量 | 与递归深度成正比 |
| 栈溢出风险 | 无 | 有 |
| 代码可读性 | 逻辑线性,但状态多时较冗长、易出错 | 简洁优雅,与数学定义/树形结构高度贴合 |
| 主要优点 | 效率高、空间省、无溢出风险、易被底层优化 | 代码简洁、逻辑清晰、天然匹配递归定义的问题 |
| 主要缺点 | 部分问题实现繁琐 | 调用开销大、受栈深度限制、重复子问题性能差 |
两种算法在某些情况下可以相互转换(例如任务5)。
关于转义字符
任务7中使用到转义字符\t水平制表符。常用转义字符如下表:
| 转义字符 | 名称 | 十进制值 | 作用说明 |
|---|---|---|---|
\n |
换行(LF) | 10 | 光标移到下一行行首,最常用的换行符 |
\t |
水平制表符(HT) | 9 | 跳到下一个制表位(默认间隔 8 列),用于对齐 |
\r |
回车(CR) | 13 | 光标回到当前行行首,不换行 |
\b |
退格(BS) | 8 | 光标左移一格,不删除字符 |
\f |
换页(FF) | 12 | 换页,终端中常表现为清屏或翻页 |
\v |
垂直制表(VT) | 11 | 垂直制表,多数终端无可见效果 |
\a |
响铃(BEL) | 7 | 触发蜂鸣或闪屏,不移动光标 |
\\ |
反斜杠本身 | 92 | 输出一个字面的 \ |
\' |
单引号 | 39 | 在字符常量中表示单引号本身 |
\" |
双引号 | 34 | 在字符串中表示双引号本身 |
\0 |
空字符(NUL) | 0 | 字符串结束标志,不显示任何内容 |

浙公网安备 33010602011771号