本节书摘来自异步社区《趣题学算法》一书中的第0章0.3节算法的伪代码描述,作者徐子珊,更多章节内容可以访问云栖社区“异步社区”公众号查看。
0.3 算法的伪代码描述
上一节最后一段使用自然语言(汉语)描述了解决“计算逆序数”问题的算法。即如何将输入数据转换为输出数据的过程。在需要解决的问题很简单的情况下(例如“计算逆序数”问题),用自然语言描述解决这个问题的算法是不错的选择。但是,自然语言有一个重要特色——语义二岐性。语义二岐性在文学艺术方面有着非凡的作用:正话反说、双关语……。由此引起的误会、感情冲突……带给我们多少故事、小说、戏剧……。但是,在算法描述方面,语义二岐性却是我们必须避免的。因为,如果对数据的某一处理操作的表述上有二岐性,会使不同的读者做出不同的操作。对同一输入,两个貌似相同的算法的运行,将可能得出不同的结果。这样的情况对问题的解决可能是灾难性的。所以,自然语言不是最好的描述算法的工具。
在计算机上,算法过程是由一系列有序的基本操作描述的。不同的计算机系统,同样的操作,指令的表达形式不必相同。本书并不针对特殊的计算机平台描述解决计算问题的算法,我们需要一个通用的、简洁的形式描述算法,并且能方便地转换成各种计算机系统上特殊表达形式(计算机程序设计语言)所描述的程序。描述算法的通用工具之一叫伪代码。例如,解决上述问题数据输入/输出的伪代码过程可描述如下。
1 打开输入文件inputdata
2 创建输出文件outputdata
3 从inputdata中读取案例数据N
4 while N>0
5 do 创建数组A[1..N]
6 for i←1 to N
7 do 从inputdata中读取A[i]
8 result←GET-THE-INVERSION(A)
9 将result作为一行写入outputdata
10 从inputdata中读取案例数据N
11 关闭inputdata
12 关闭outputdata
其中,第8行调用计算序列A[1..N]的逆序数过程GET-THE-INVERSION(A)是解决一个案例的关键,其伪代码过程如下。
GET-THE-INVERSION(A) ▷A[1..N]表示一个序列
1 N←length[A]
2 count←0
3 for j←N downto 2
4 do for i←1 to j-1
5 do if A[i]>A[j] ▷检测到一个逆序
6 then count←count+1 ▷累加到计数器
7 return count
算法0-1 解决“计算逆序数”问题的一个案例的算法伪代码过程
伪代码是一种有着类似于程序设计语言的严格外部语法(用if-then-else表示分支结构,用for-do、while-do或repeat-until表示循环结构),且有着内部宽松的数学语言表述方式的代码表示方法。它既没有二歧性的缺陷(严格的外部语法),又能用高度抽象的数学语言简练地描述操作细节。
本书所使用的伪代码书写规则如下。
① 用分层缩进来指示块结构。例如,从第3行开始的for循环的循环体由第4~6行的3行组成,分层缩进风格也应用于if-then-else语句,如第5~6行的if-then语句。
② 对for循环,循环计数器在退出循环后仍然保留。因此,一个for循环刚结束时,循环计数器的值首次超过for循环上界。例如在算法0-1中,当第3~6行的for循环结束时,j = N+1;而第4~6行的for循环结束时,i=1−1=0。
③ 符号“ ▷”表示本行的注释部分。例如,算法0-1的开头对参数A的意义进行了解释,第5行说明检测到一个逆序(iA[j]),而第6行说明将此逆序累加到逆序数count(count自增1)。
④ 多重赋值形式i ← j← e对变量i和j同赋予表达式e的值;它应当被理解为在赋值操作j ← e之后紧接着赋值操作i ← j。
⑤ 变量(如i,j,及count)都局部于给定的过程。除非特别需求,我们将避免使用全局变量。
⑥ 数组元素是通过数组名后跟括在方括号内的下标来访问。例如,A[i]表示数组A的第i个元素。记号“…”用来表示数组中取值的范围。因此,A[1…i]表示数组A的取值由A[1]到A[i],i个元素构成的子序列。
⑦ 组合数据通常组织在对象中,其中组合了若干个属性。用域名[对象名]的形式来访问一个具体的域。例如,我们把一个数组A当成一个对象,它具有说明其所包含的元素个数的属性length。为访问数组A的元素个数,我们写length[A]。
表示数组或对象的变量被当成一个指向表示数组或对象的指针。对一个对象x的所有域f,设y ← x将导致f[y] = f[x]。此外,若设f[x]← 3,则不仅有f[x] = 3,且有f[y] = 3。换句话说,赋值y ← x后,x和y指向同一个对象。
有时,一个指针不指向任何对象,此时,我们给它一个特殊的值NIL。
⑧ 过程的参数是按值传递的:被调用的过程以复制的方式接收参数,若对参数赋值,则主调过程不能看到这一变化。
⑨ 布尔运算符“and”和“or”都是短回路的。也就是说,当我们计算表达式“x and y”时,先计算x。若x为FALSE,则整个表达式不可能为TRUE,所以我们不再计算y。另一方面,若x为TRUE,我们必须计算y以确定整个表达式的值。相仿地,在表达式“x or y”中,我们计算表达式y当且仅当x为FALSE。短回路操作符使得我们能够写出诸如“x ≠ NIL and f [x] = y”这样的布尔表达式而不必担心当x为NIL时去计算f [x]。