跳到主要内容

算法设计与问题求解

A-Level 计算机科学 · 第 9 主题

训练
9.1

计算思维技能

大纲
Candidates should be able to: Notes and guidance
Show an understanding of abstraction Need for and benefits of using abstraction Describe the purpose of abstraction Produce an abstract model of a system by only including essential details
Describe and use decomposition Break down problems into sub-problems leading to the concept of a program module (procedure / function)

来源:剑桥国际大纲

计算思维(computational thinking)是分析一个问题并设计一个计算机能运行的解决方案的一套心智工具。两个关键的是抽象和分解。

一个部分完成的拼图
计算思维把一个大问题分成更小、更容易的部分——像解一个拼图

抽象

抽象(abstraction)意味着保留一个问题的本质特征忽略无关的细节,给出一个更简单的模型。

例子:

  • 一张铁路网络图保留车站和线路,但去掉地理。
  • 面向对象编程中的一个只保留系统需要的属性和方法。
  • 一个函数把一段工作隐藏在一个名称后面。

任何真实问题的完整模型都太大而无法推理,所以抽象是必不可少的。

考官会问抽象的目的和它的好处。目的:得到一个只包含解决问题所需细节的更简单的模型。好处:问题更容易理解和编程;程序更小,写起来和测试起来更快;同一个模型可以在类似的问题上重用。当题目要求你建立一个抽象模型时,只列出任务需要的数据和操作。对于学校课表来说,那就是班级、教室、教师和课时;而不是教室的颜色或教师的年龄。

抽象把一个杂乱的真实地理(一条蜿蜒的路线,散布着建筑)变成一张干净的地铁图——一条直线上等间距的车站圆圈,保留车站和线路而去掉地理
抽象保留本质(车站和线路)并去掉无关的细节(地理)

分解

分解(decomposition)意味着把一个大问题分成更小的子问题,每个更容易解决并一次处理一个。

  1. 找出任务的主要部分。
  2. 把每个分成更小的子任务。
  3. 继续直到每个都小到能直接设计。
  4. 解决小任务并把它们组合起来。

对于库存控制:"管理库存" → "记录销售"、"记录到货"、"生成报表" → ("记录销售")"查找产品"、"减少库存计数"、"保存交易"。分解使大问题可管理、让一个团队分担工作,并给出模块化的代码——每个模块成为一个过程(procedure)或函数。

"解释为什么使用分解"是一道有固定形状的三分题。给出三个各自独立的好处:每个子问题(sub-problem)都小到可以单独设计、编码和测试;不同的程序员可以同时做不同的模块(module);已经存在的模块(或库例程)可以重用,而且错误更容易找到,因为它只在一个模块里。结构图(主题 12)就是分解的图示:程序在顶部,它的模块在下面,以及它们之间传递的数据。

一棵树,顶部"管理库存"分支成模块"记录销售"、"记录到货"和"生成报表",而"记录销售"拆成子任务"查找产品"、"减少库存计数"和"保存交易"
把一个程序分解成模块和子模块
探索

Solving a problem the computational way

Step through the four cornerstones in the order you'd use them — break the problem down, spot what repeats, strip it to essentials, then write the steps.

词汇表 训练
英文 中文 拼音
Computational thinking 计算思维 jì suàn sī wéi
Abstraction 抽象 chōu xiàng
Decomposition 分解 fēn jiě
procedure 过程 guò chéng
sub-problem 子问题 zi wèn tí
modules 模块 mó kuài
练习卷
9.2

算法

大纲
Candidates should be able to: Notes and guidance
Show understanding that an algorithm is a solution to a problem expressed as a sequence of defined steps
Use suitable identifier names for the representation of data used by a problem and represent these using an identifier table
Write pseudocode that contains input, process and output
Write pseudocode using the three basic constructs of sequence, selection and iteration (repetition)
Document a simple algorithm using a structured English description, a flowchart or pseudocode
Write pseudocode from: • a structured English description • a flowchart
Draw a flowchart from: • a structured English description • pseudocode
Describe and use the process of stepwise refinement to express an algorithm to a level of detail from which the task may be programmed
Use logic statements to define parts of an algorithm solution

来源:剑桥国际大纲

冒泡排序,一趟接一趟

一个算法(algorithm)是表述为一系列已定义步骤的解决方案。每一步是无歧义的(unambiguous,一个意思)、确定性的(deterministic,相同输入 → 相同输出)、有限的(finite,步骤会结束),以及有效的(effective,每一步都能做)。一个算法说明做什么,与用来实现它的编程语言无关。

探索

Selection: follow the IF / ELSE branches

Drag the score and watch which branch runs. Selection tests each condition in turn and takes the FIRST one that is true — that is how IF … ELSE IF … ELSE works.

词汇表 训练
英文 中文 拼音
algorithm 算法 suàn fǎ
unambiguous 无歧义 wú qí yì
deterministic 确定性 què dìng xìng
练习卷
9.2

标识符表

当你开始一个算法时,在一个标识符表(identifier table)中列出每一份数据——它的标识符(identifier,即变量(variable)名)、数据类型(data type)和描述。考试中的表恰好就是这三列:

标识符 数据类型 描述
Category STRING 产品类别
SaleDate DATE 商品售出的时间
ItemCost REAL 商品的成本
InStock BOOLEAN 若有库存则为 TRUE
Sales ARRAY[1:30] OF REAL 最近 30 天的每日销售总额

使用描述性的名称(ItemCost,而不是 x):标识符以字母开头,不含空格,并且每次出现都写得一模一样。常见类型是 INTEGERREALSTRINGCHARBOOLEANDATE,加上数组。这个表迫使你在写代码之前命名每一份数据;"补全标识符表"的题目每个正确的数据类型或描述各给一分,所以类型要严格按伪代码指南的写法写。

一个标识符表,列出每个变量及其名称、数据类型和描述,例如 ItemCost 作为一个 REAL 表示商品的成本
一个标识符表在你写代码之前命名每一份数据
词汇表 训练
英文 中文 拼音
identifier table 标识符表 biāo shí fú biǎo
identifier 标识符 biāo shí fú
variable 变量 biàn liàng
data type 数据类型 shù jù lèi xíng
Boolean 布尔 bù ěr
9.2

伪代码 —— 三种基本结构

伪代码(pseudocode)是一种描述算法的结构化、语言中立的方式。

三种基本结构作为小流程图:顺序运行步骤 A 然后 B 然后 C;选择测试一个条件并做 X 或 Y;迭代在一个条件成立时重复一个主体,循环回去
任何算法的三个构件:顺序、选择和迭代

1. 顺序

步骤一个接一个地运行(顺序(sequence)):

INPUT Name
INPUT Age
OUTPUT "Hello", Name

2. 选择

基于一个条件选择运行哪些步骤(选择(selection)):

IF Age >= 18 THEN
    OUTPUT "Adult"
ELSE
    OUTPUT "Minor"
ENDIF

对于更多的选项,用 CASE OF ... ENDCASE

3. 迭代

重复一个块(迭代(iteration),一个循环(loop)):

FOR i ← 1 TO 10
    OUTPUT i
NEXT i

一个 WHILE 循环在每一趟之前测试条件(可能运行零次);一个 REPEAT...UNTIL 循环在每一趟之后测试(总是至少运行一次)。

WHILE Total < 100 DO
    INPUT Value
    Total ← Total + Value
ENDWHILE

REPEAT
    INPUT Mark
UNTIL Mark >= 0 AND Mark <= 100
并排的两幅流程图。WHILE 先测试条件,所以循环体可能一次都不运行:菱形在循环体上方,"否"分支离开循环。REPEAT UNTIL 先运行循环体、之后再测试,所以循环体至少运行一次:循环体在菱形上方,"否"分支回到它
WHILE 循环在循环体运行之前测试;REPEAT ... UNTIL 循环在之后测试,所以它的循环体总是至少运行一次

选对循环本身就是一分:知道重复多少次时用 FOR(计数循环(count-controlled loop));循环可能一次都不运行时用 WHILE(前测循环(pre-condition loop));必须至少运行一次时用 REPEAT ... UNTIL,例如验证一次输入(后测循环(post-condition loop))。"描述迭代结构"的答案要说出结构的名称、条件在哪里测试,以及后果(零次或至少一次)。

常见操作

  • 赋值(assignment):x ← 5(一个箭头;= 用于比较)。
  • 输入/输出:INPUT variableOUTPUT expression
  • 比较 =<><><=>=;逻辑 ANDORNOT
  • 算术 + - * /,加上 DIV(整数除法)和 MOD(余数)。
  • 字符串:LENGTHLEFTRIGHTMID,以及 & 用于拼接(concatenation,连接)。

考试要求的伪代码

每一个伪代码答案都按剑桥公布的伪代码指南评分。要严格按这些形式写:

结构 伪代码
声明变量 DECLARE Total : INTEGER
声明数组 DECLARE Marks : ARRAY[1:30] OF REAL
常量 CONSTANT MaxTries = 3
赋值 Total ← Total + Value
输入和输出 INPUT NameOUTPUT "Hello ", Name
多个选项 CASE OF Choice ... 1 : OUTPUT "Add" ... OTHERWISE OUTPUT "Error" ... ENDCASE
计数循环 FOR i ← 1 TO 10 STEP 2 ... NEXT i
前测循环 WHILE Total < 100 DO ... ENDWHILE
后测循环 REPEAT ... UNTIL Mark >= 0
整除和余数 DIVMOD:17 DIV 5 = 3,17 MOD 5 = 2
字符串函数 LENGTH(S)LEFT(S, 3)RIGHT(S, 2)MID(S, 2, 4)UCASE(S)LCASE(S)
转换 INT(3.7) = 3NUM_TO_STR(12)STR_TO_NUM("4.5")ASC('A') = 65CHR(66) = 'B'
随机数 RAND(100) 给出一个从 0 到(但不包括)100 的实数;INT(RAND(100)) + 1 给出 1 到 100 的整数

有两个习惯在每道题上都能得分:声明你用到的每个变量,类型取自你的标识符表;在改变它们的循环之前,初始化(initialise)每个计数器(counter)和总和(Count ← 0Total ← 0)。

输入 → 处理 → 输出

每个程序都遵循这个形状:

INPUT Length
INPUT Width
Area ← Length * Width
OUTPUT "Area = ", Area

先列出输入和输出使算法更清晰。

例题。 写伪代码:输入 100 个整数,输出其中落在 10 到 20(含)之间的数有多少个,以及这些数的总和。

标识符表:Count : INTEGER(循环计数器)、Value : INTEGER(刚输入的整数)、InRange : INTEGER(在范围内的个数)、Total : INTEGER(它们的和)。

DECLARE Count, Value, InRange, Total : INTEGER
InRange ← 0
Total ← 0
FOR Count ← 1 TO 100
    INPUT Value
    IF Value >= 10 AND Value <= 20 THEN
        InRange ← InRange + 1
        Total ← Total + Value
    ENDIF
NEXT Count
OUTPUT InRange, Total

如果题目接着要求你"指出两种结构并说明各自如何使用",就用同样的形状回答:迭代,即 FOR 循环,把输入重复 100 次;选择,即 IF 语句,只在数值处于范围内时才把它加上。

例题。 程序从 1 到 100 中选一个秘密整数。用户一直猜到猜对为止;每次猜错后程序说"Too low"或"Too high",最后输出一共猜了多少次。

标识符表:Secret : INTEGER(要猜的数)、Guess : INTEGER(用户的输入)、Tries : INTEGER(到目前为止猜了多少次)。

DECLARE Secret, Guess, Tries : INTEGER
Secret ← INT(RAND(100)) + 1
Tries ← 0
REPEAT
    INPUT Guess
    Tries ← Tries + 1
    IF Guess < Secret THEN
        OUTPUT "Too low"
    ELSE
        IF Guess > Secret THEN
            OUTPUT "Too high"
        ENDIF
    ENDIF
UNTIL Guess = Secret
OUTPUT "You took ", Tries, " guesses"

REPEAT ... UNTIL 循环是正确的选择,因为用户必须至少猜一次。给分点是:范围正确的随机数、猜对时结束的循环、从零开始并在循环内递增的计数器、在正确条件下输出的两条信息,以及最后的输出。

猜数游戏的流程图:开始,然后把 Secret 设为 1 到 100 的随机整数、Tries 设为 0,然后输入一个猜测,Tries 加一,判断猜测是否等于秘密数(是则输出 Tries 并停止),否则判断猜测是否更小(是则输出 Too low,否则输出 Too high),两个输出都回到输入
同一个猜数游戏的流程图:两个判断菱形就是两个 IF 语句,回去的箭头就是 REPEAT ... UNTIL 循环

例题。 输出两个不同的随机整数,每个都在 $-10$$10$(含)之间。

可能的值有 21 个,所以 INT(RAND(21)) 给出 0 到 20,减去 10 就把它移到 $-10$$10$ 的范围。第二个数必须重新生成,直到它与第一个不同:

DECLARE First, Second : INTEGER
First ← INT(RAND(21)) - 10
REPEAT
    Second ← INT(RAND(21)) - 10
UNTIL Second <> First
OUTPUT First, Second
每个程序都遵循输入、然后处理、然后输出的形状,用面积例子显示:输入长和宽,通过相乘来处理,输出面积
每个程序都遵循输入、处理、输出的形状
探索

IF … ELSE selection

Change the value and watch which branch runs — how a program makes a decision.

词汇表 训练
英文 中文 拼音
Pseudocode 伪代码 wěi dài mǎ
sequence 顺序 shùn xù
selection 选择 xuǎn zé
iteration 迭代 dié dài
loop 循环 xún huán
count-controlled loop 计数循环 jì shù xún huán
pre-condition loop 前测循环 qián cè xún huán
post-condition loop 后测循环 hòu cè xún huán
assignment 赋值 fù zhí
concatenation 拼接 pīn jiē
initialise 初始化 chū shǐ huà
counter 计数器 jì shù qì
flowchart 流程图 liú chéng tú
9.2

三种表示法

同一个算法可以用三种方式书写。

  • 结构化英语(structured English)——带缩进和固定关键字的自然语言;适合一个高层描述。
  • 流程图(flowchart)——一个带标准形状的图:
形状 意义
圆角矩形 开始 / 停止
平行四边形 输入 / 输出
矩形 处理
菱形 判断
箭头 控制流
  • 伪代码——上面的关键字记法;最接近代码。

你应当能够在任何一对之间转换:每个 IF 是一个判断菱形,每个循环是一个回箭头,一个顺序是堆叠的矩形。

把流程图转换成伪代码:从终止符开始,按顺序沿箭头走;平行四边形变成 INPUTOUTPUT;矩形变成赋值;两个出口在下方重新汇合的菱形变成 IF ... THEN ... ELSE ... ENDIF;只有一个出口且箭头向上回去的菱形是循环——如果菱形在被重复的方框之前就是 WHILE 循环,如果在之后就是 REPEAT ... UNTIL 循环。反过来转换时,每条语句恰好画一个方框、每个条件恰好画一个菱形,并给菱形的每个出口标上"是"或"否"。

一个求数字平均值的流程图:圆角的开始和停止终止符、输入/输出平行四边形、处理矩形,以及一个"count < n?"判断菱形,它的"是"分支循环回去读下一个值
一个用标准形状求一列数字平均值的流程图
词汇表 训练
英文 中文 拼音
structured English 结构化英语 jié gòu huà yīng yǔ
9.2

逐步求精

逐步求精(stepwise refinement)从一个高层大纲开始,展开每一步直到它小到能编码。对于 $n$ 个数字的平均值:

第 1 层:

Read in the numbers
Compute the average
Output the average

第 2 层:

INPUT n
total ← 0
FOR i ← 1 TO n
    INPUT value
    total ← total + value
NEXT i
average ← total / n
OUTPUT average

每次求精都保留之前的结构并添加细节。

六分的"应用逐步求精"题会给你一个高层大纲,要你把每一步展开成程序员能直接编码的具体语句。保持步骤的顺序不变,写出每一步读入或产生的数据的名称,直到每一行都是单独的一个输入、赋值、输出、循环或条件为止。例如,"验证密码"展开为:输入密码;检查它的长度至少为 8;检查它至少含一个数字;两项检查都通过则输出"accepted",否则输出"rejected"。

逐步求精:一个第 1 层大纲(读入数字、计算平均值、输出平均值)被展开成带输入循环和除法的第 2 层详细伪代码
逐步求精:把每个高层步骤展开成详细的伪代码
探索

Stepwise refinement: outline to code

Step down the levels. You start with the whole task in one line and keep expanding each step into smaller ones — until every step is simple enough to code directly.

词汇表 训练
英文 中文 拼音
Stepwise refinement 逐步求精 zhú bù qiú jīng
9.2

逻辑语句

一个逻辑语句(logic statement)是一个控制分支的布尔(Boolean)条件,由比较(x > 10)、连接词(ANDORNOT)和括号构成。把它用作 IFWHILEREPEAT...UNTIL 的条件:

WHILE attempts < 3 AND NOT loggedIn DO
    INPUT password
    IF password = correctPassword THEN
        loggedIn ← TRUE
    ELSE
        attempts ← attempts + 1
    ENDIF
ENDWHILE

优先级(precedence,从高到低):NOT,然后 AND,然后 OR。不确定时用括号。常见错误:

  • a = 1 OR 2 是错的——写 a = 1 OR a = 2
  • NOT a > 5 意味着 NOT (a > 5),即 a <= 5
  • NOT (A AND B)(NOT A) OR (NOT B) 相同(德摩根定律(De Morgan's law))——对简化条件很方便。

把一句话变成逻辑语句是试卷直接考查的技能。"5 岁以下或 65 岁以上免票"变成 Age < 5 OR Age > 65。"分数有效当且仅当它是 0 到 100 的整数"变成 Mark >= 0 AND Mark <= 100。"文件结束或已读 10 条记录时循环停止"变成 UNTIL EOF(File) OR Count = 10。每个比较都要写完整:Age > 65Age < 5,绝不能写 Age > 65 OR < 5

"attempts < 3 AND NOT loggedIn" 的一棵分析树:NOT 先应用于 loggedIn,然后 AND 把它与 attempts < 3 连接
优先级:NOT 先绑定到 loggedIn,然后 AND 组合两边

例题。 写出标识符表和伪代码:读入 10 个数并输出最大的那个。标识符表为每个变量写明数据类型和用途:Count : INTEGER(循环计数器)、Num : REAL(刚读入的数)、Max : REAL(到目前为止的最大值)。

Max ← -999999
FOR Count ← 1 TO 10
    INPUT Num
    IF Num > Max THEN
        Max ← Num
    ENDIF
NEXT Count
OUTPUT Max

承载分数的设计决定是 Max初始化:它必须从一个比任何可能的输入都低的值开始 - 或者更稳妥地,把它设成第一个读入的数。若把它初始化为 0,那么对一串负数,该算法会错误地返回 0;这个 bug 只有当你的测试数据里包含负数时,追踪才会把它暴露出来。

词汇表 训练
英文 中文 拼音
logic statement 逻辑语句 luó jí yǔ jù
Precedence 优先级 yōu xiān jí
De Morgan's law 德摩根定律 dé mó gēn dìng lǜ
9.2

考官认可的定义

定义题按固定的表述给分。把这些记准确,并且只写一个答案。

术语 定义
抽象(abstraction) 保留问题的本质细节,去掉不需要的细节
分解(decomposition) 把一个问题分成更小的子问题,每个都可以单独解决
算法(algorithm) 表述为一系列已定义步骤的问题解决方案
标识符表(identifier table) 列出算法中用到的每个标识符及其数据类型和用途描述的表
伪代码(pseudocode) 一种与语言无关的、结构化地书写算法步骤的方式
流程图(flowchart) 用箭头连接的标准符号表示算法的步骤和判断的图
顺序(sequence) 语句按书写顺序一条接一条地执行
选择(selection) 根据一个条件选择执行哪些语句
迭代(iteration) 当(或直到)某个条件成立时重复一组语句
逐步求精(stepwise refinement) 反复把大纲中的每一步分成更小的步骤,直到每一步都能直接编码
逻辑语句(logic statement) 由比较和 AND、OR、NOT 运算符构成、取值为 TRUE 或 FALSE 的条件
9.2

考试技巧

  • 把一个算法定义为一个无歧义、有限、确定性的步骤序列,与语言无关。
  • 正确地使用三种结构——顺序、选择、迭代——并保留一个带数据类型的标识符表。
  • 通过分解和抽象把一个问题分解,然后逐步求精。
  • 写实际能运行的伪代码:声明变量并遵循考试的伪代码风格。

常见错误

  • = 来赋值。赋值是 ;= 是比较。
  • 漏掉 ENDIFENDWHILEENDCASENEXT。每种结构都要闭合,而结构的分数正是在闭合词处检查的。
  • 循环之前不初始化总和或计数器,导致算法往一个从未存在的值上累加。
  • 在重复次数未知时使用 FOR 循环。读到哨兵值或猜对为止需要 WHILEREPEAT ... UNTIL
  • 写成 Age > 65 OR < 5ORAND 的每一边都必须是一个完整的比较。
  • 用同一个好处的三种说法来回答"解释为什么使用分解"。三分需要三个不同的好处。

本主题的互动课程

逐步学习,并即时检测练习。

A-Level 计算机科学历年真题

A-Level 计算机科学的更多主题

登录或创建账号

IGCSE, A-Level & AP