「PAT乙级真题解析」Basic Level 1001 (问题分析+完整步骤+伪代码描述+提交通过代码)
题目中给出了关于卡拉兹猜想的计算步骤,并要求给出按照给定的步骤执行到满足终止条件(此处为n等于1)所需要的次数。 我们知道,程序是只能执行一些重复的工作,所以程序的逻辑归根结底是条件、循环和递归的组合,最终达到按照指定步骤完成任务的目的。而此类题目直接给出了具体的步骤,与程序的本质不谋而合。程序是用来重复执行人为设定行为的,也可以说是对人行为的模拟。而解答此类题目就是直接将题设给出的步骤用编程语言语法翻译成代码逻辑,所以这种解法可以说是"模拟法"。
Table of Contents
乙级的题目训练主要用来熟悉编程语言的语法和形成良好的编码习惯和编码规范。从小白开始逐步掌握用编程解决问题。
PAT Basic Level Practice 1001 害死人不偿命的(3n+1)猜想
问题分析 & 解题思路
题目中给出了关于卡拉兹猜想的计算步骤,并要求给出按照给定的步骤执行到满足终止条件(此处为n等于1)所需要的次数。 我们知道,程序是只能执行一些重复的工作,所以程序的逻辑归根结底是条件、循环和递归的组合,最终达到按照指定步骤完成任务的目的。而此类题目直接给出了具体的步骤,与程序的本质不谋而合。程序是用来重复执行人为设定行为的,也可以说是对人行为的模拟。而解答此类题目就是直接将题设给出的步骤用编程语言语法翻译成代码逻辑,所以这种解法可以说是"模拟法"。 明白使用模拟法解答题目之后,我们需要分析题设描述的步骤如何翻译成代码逻辑(准确地说,是如何用条件、循环和递归组合出题设描述的逻辑):
- 我们需要执行一组操作,直到满足终止条件。这种动作的重复执行,使用循环和递归可以达到。递归比较复杂,而且实际工作开发中需要慎用,所以如无必要,我们不提及递归。于是,我们知道这里需要使用循环。
- 进一步,大多编程语言中,循环分为for循环和while循环。for循环用来将指定逻辑执行指定次数。while循环用来将指定逻辑执行到满足指定条件。因为题设就是要求输出所需次数,我们不知道具体要执行多少次,所以不适合使用for循环,所以使用while循环。
- 那么循环要执行的逻辑是哪些?根据题目可知,是卡拉兹猜想中数值的变换逻辑,具体为:如果当前数值n是奇数,则n的值变为3n+1的一半;如果n是偶数,则n的值变为n的一半。我们看到这种“如果……则”的关键词,可以十分坚定地选择使用条件判断,根据条件判断的不同结果来执行不同的逻辑。
- 所以用自然语言或者伪代码之类的,可以将思路表达为:
设置一个计数器, 初始次数为0
while(n不等1),执行以下逻辑:
1. 判断n的奇偶性,如果n是奇数,则执行逻辑:n变为(3n+1)/2; 计数器次数+1
如果n是偶数,则执行逻辑: n变为n/2; 计数器次数+1
循环结束后,返回计数器的次数作为答案剩下的就是把上面的思路转化(或者说翻译)成我们想用的编程语言。关于不同编程语言的语法使用,我写在文末。这里再次贴出C语言版本的解法,并对语法使用进行解释。
# include <stdio.h>
int CallatzCalculateOperationAmount(int number){
/*检查当前数字是否已经变成1, 如果不是1, 则按卡拉兹猜想执行数值变化并计算;
卡拉兹猜想:
1. 如果 n 是奇数, n = (3n + 1) / 2
2. 如果 n 是偶数, n = n / 2
*/
int count = 0;
while (number != 1){
if (number % 2 == 0){
number = number / 2;
} else{
number = (number * 3 + 1) / 2;
}
count ++;
}
return count;
}
int main(){
int number;
if (scanf("%d", &number) != 1) {
printf("Failed to read integer.\n");
return 1;
}
int amount = CallatzCalculateOperationAmount(number);
printf("%d\n", amount);
return 0;
}PAT需要我们自行读取输入并输出答案,所以需要涉及输入输出语法; C语言内置的输入函数为scanf,调用函数时,在函数名后面加上一对小括号即可(如果函数要求传入参数,则需要将参数按顺序放入括号中,并以逗号分隔)。 所以我们读取输入的代码应该写为 scanf("%d", &input_number), 并且在该语句前定义变量input_number,写为int input_number; C语言的输出函数为printf, 我们的语句写为:printf("%d", amount), 变量amount里存储的就是我们的计算结果; 【调用函数时,参数传入顺序由函数定义所决定,我们作为调用方需要做的就是遵守定义】 【关于为什么输入语句的变量前要加&符号, 我们起步阶段可以暂且不管, 记住用法即可, 后续在其他地方涉及时会详细解释】 然后是我们主要的逻辑,即按照卡拉兹猜想变换我们输入的数值; C语言中编写条件语句就是使用单词 if 后面加上小括号,括号中是要满足的条件;满足条件后要执行的逻辑由一对花括号{}括起来后跟在小括号的后面,比如判断是否是奇数的语句如下
if(number % 2 == 0) {n = (3 * n + 1) / 2}设计者设计语言的时候也是希望语言使用便利,所以循环条件语句表达条件的形式与条件语句一致, 如下:
while( n != 1 ) {按照卡拉兹猜想执行数值变换}组合之后就是如下的代码:
int count = 0;
while (number != 1){
if (number % 2 == 0){
number = number / 2;
} else{
number = (number * 3 + 1) / 2;
}
count ++;
}
return count;关于编程语言
我们可以做一个角色扮演,假设我们是一门编程语言的开发者。如果我们希望我们设计的语言被更多的人使用,那么我们在设计时会考虑便利性,而这其中就包括记忆语法的简便程度,包括if,while这些关键字以及代码块的组织形式(比如用括号来括住多行代码)。所以你会看到条件语句的关键字在编程语言中基本都是使用"if", 而不是什么"fi"、"as if"或者"suppose"。代码的组织形式也只会分成几个主要的类别,比如像c风格编程语言java, C++, C#这些使用括号来表示那些代码是一个整体,以及像python, ruby 这种使用缩进的方式, 相同缩进距离的代码属于一个整体。 进一步,编程语言为了方便重复使用一整块代码而设计的"函数"概念,其实也都是相似的。当我们要定义某些代码是一个函数时,我们需要有像if,while那些标记来说明这里是所谓的"函数"。我们刚才提到了有用括号包裹多行代码来表示代码属于一个整体的,所以就有用括号括住多行代码来定义函数的形式,比如我们上面使用的C语言。形式如下:
类型名称 函数名称( 参数1的名称, 参数2的名称) {
代码语句1;
代码语句2;
代码语句3;
}另外就是使用像if 和 while这种关键词然后配合缩进来定义函数的形式, 比如python
def 函数名称( 参数1的名称, 参数2的名称):
代码语句1
代码语句2
代码语句3而这里的"def"是define省略了后面的字母。按照我们前面说的"设计者要处于便利设计语言",所以“定义”函数的关键词离不开“定义”和“函数”这两个英文单词,比如"def", "function", "fn", "func"。 所以,就算你手生了,忘记了关键词,或者刚接触一门新的编程语句,你直接排列组合地尝试,大概率都可以试出正确的关键词。
如果你能看到这里,那么你可能会明白,对于“基础语法”,编程语言之间的差别其实没有你想的那么大。编程语言之间不同的是语言设计思想和语言特性,而这些不同,作为一个初学者/学生/目前只想或只需要通过考试的人来说,并不是你学习的重点。
题目完整提交代码
/*
方案一: 模拟
对于题目描述中给出一系列操作描述,并在达到某个条件之后停止操作的情况,
这种给定"操作步骤", 要求输出"按照描述执行一系列操作"后的结果的题目,
将题目中给的步骤描述直接翻译成程序逻辑来模拟操作, 就是模拟法。
本题中, 给出的操作是:判断数值的奇偶性, 然后执行对应的计算逻辑, 直到数值变为1.
要求输出计算逻辑的执行次数.
"逻辑的重复执行" -> 循环结构 (for-loop / while-loop) -> 因为不知道具体需要执行几次, 所以用while-loop
如何判断奇偶性: 模运算% , 根据模运算结果是1还是0判断
*/
# include <stdio.h>
int CallatzCalculateOperationAmount(int number){
/*检查当前数字是否已经变成1, 如果不是1, 则按卡拉兹猜想执行数值变化并计算;
卡拉兹猜想:
1. 如果 n 是奇数, n = (3n + 1) / 2
2. 如果 n 是偶数, n = n / 2
*/
int count = 0;
while (number != 1){
if (number % 2 == 0){
number = number / 2;
} else{
number = (number * 3 + 1) / 2;
}
count ++;
}
return count;
}
int main(){
int number;
if (scanf("%d", &number) != 1) {
printf("Failed to read integer.\n");
return 1;
}
int amount = CallatzCalculateOperationAmount(number);
printf("%d\n", amount);
return 0;
}