论文和教材中常见的带框、编号为 “Algorithm 1” 的伪代码,并不是 LaTeX 的内置功能,而是由包提供的。第一个容易困惑的点,是有好几个名字相近的包。关键在于一种分工:一个包提供容器(会浮动的编号框),另一个包提供内容(伪代码本身)。本页先梳理这片生态,然后用可实际编译的代码介绍经典组合——用 algorithm 浮动体包住 algpseudocode 正文——以及自成一体的对手 algorithm2e。这里没有实时渲染器,所以正文会说明各示例会产生怎样的输出。
容器与内容:两层结构
伪代码排版分成两层,各有职责。第一层是 容器:它像 figure 或 table 一样在页面上浮动,是一个带有 “Algorithm 1” 标题、连续编号、交叉引用,甚至可生成算法目录的框。提供这一层的是 algorithm 包(\usepackage{algorithm}),它增加一个环境:\begin{algorithm} … \end{algorithm}。
第二层是 内容:用 \State(一条语句)、\While(循环)、\If(条件分支)等命令,带着缩进和行号排出伪代码本身。选择在这里分叉:老牌 algorithmic 包;更易定制的后继 algorithmicx(实际使用时通常用建立在它之上的 algpseudocode 布局);以及独立的另一套 algorithm2e。
关键点是:正文层原则上只能选一个包。algpseudocode 和 algorithm2e 在语法和理念上完全不同,混用会冲突。相对地,容器 algorithm 本来就是要与 algpseudocode 配套使用;algorithm2e 自带容器和正文,因此不需要 algorithm(也不建议并用)。下表给出整体图景。
| 包 | 层 | 作用 |
|---|---|---|
algorithm | 容器 | 浮动框 + “Algorithm N” 标题/编号;与正文包配套使用 |
algorithmic | 正文(旧) | 最初的伪代码环境;全大写命令(\STATE 等);几乎不能定制 |
algpseudocode | 正文(现代) | algorithmicx 的标准布局;外观像 algorithmic,但灵活得多 |
algorithm2e | 两者 | 独立自成一体;自有语法(\KwIn、\eIf、行末 \\;);单独使用 |
注意,algorithm 来自 algorithms bundle,同一 bundle 也包含 algorithmic。algorithmicx 是单独发布的,但加载 algpseudocode 会自动引入它,因此用户不必自己直接 \usepackage algorithmicx。
algpseudocode 命令
algpseudocode 的伪代码写在 \begin{algorithmic} … \end{algorithmic} 环境中。注意环境名不同于包名(algpseudocode):环境始终叫 algorithmic。开头的 可选参数 [1] 控制行号:0 表示不编号,1 表示每行编号,n 表示每 n 行编号一次。所有命令都是 首字母大写(\State、\While 等),这是与全大写旧 algorithmic(\STATE、\WHILE)最容易区分的地方。
核心命令是 \State,它表示伪代码 每一行的开头。赋值或过程调用等每条语句都放一个 \State。另一方面,在 \While 或 \If 这样的 打开块的命令前不需要 \State(块命令本身会开启新行)。块内内容会 自动缩进,源代码中的缩进不会影响输出。主要命令如下:
\State— 一条语句(一行)的开始;例如\State $x \\gets 1$。\For{cond}…\EndFor— for 循环;输出以 “for … do” 开始,以 “end for” 结束。也有\ForAll{cond}。\While{cond}…\EndWhile— while 循环;“while … do” / “end while”。\If{cond}…\ElsIf{cond}…\Else…\EndIf— 条件分支;“if … then”、“else if … then”、“else”、“end if”。\ElsIf和\Else可省略。\Function{name}{args}…\EndFunction— 函数;“function name(args)” / “end function”。过程形式\Procedure{name}{args}…\EndProcedure形状相同。\Return— 返回值,以直立(粗体)关键字 “return …” 排版。\Comment{...}— 行末注释,排在右三角 ▷ 之后。\Require/\Ensure— 前置条件和后置条件,前面分别带有粗体 “Require:” / “Ensure:” 标签。
这些 关键字可以替换。如果希望把英文 “Require:” 和 “Ensure:” 改成表示输入/输出的 “Input:” 和 “Output:”,可在导言区重新定义:\renewcommand{\algorithmicrequire}{\textbf{Input:}} 和 \renewcommand{\algorithmicensure}{\textbf{Output:}}。同样,改写 \algorithmicwhile、\algorithmicdo 等,就能自由改变循环和分支的词语。
完整示例:algorithm + algpseudocode
两层组合起来就是这样:外层用 algorithm 浮动体包住,用 \caption{…} 给标题,用 \label{…} 给引用标签,再在内层 algorithmic 环境中写伪代码。下面是计算幂 y = xⁿ 的经典算法。
\documentclass{article}
\usepackage{algorithm}
\usepackage{algpseudocode}
\begin{document}
\begin{algorithm}
\caption{Calculate $y = x^n$}\label{alg:power}
\begin{algorithmic}[1]
\Require $n \geq 0$
\Ensure $y = x^n$
\State $y \gets 1$
\State $X \gets x$
\State $N \gets n$
\While{$N \neq 0$}
\If{$N$ is even}
\State $X \gets X \times X$
\State $N \gets N / 2$ \Comment{even case}
\Else
\State $y \gets y \times X$
\State $N \gets N - 1$
\EndIf
\EndWhile
\State \Return $y$
\end{algorithmic}
\end{algorithm}
アルゴリズム~\ref{alg:power} は累乗を計算する。
\end{document}编译后,会得到一个上下带横线、在页面上浮动的框,顶部标题为 “Algorithm 1 Calculate y = xⁿ”(编号自动生成)。框内每行左侧有 1, 2, 3 … 的行号,前两行在粗体 “Require:” 和 “Ensure:” 后显示前提条件和结果。\While 行会以粗体 while 和末尾 do 排版,其内容缩进一级。\Comment 注释放在行末的右三角之后,最后一行为 return 后接 y。正文中的 \ref{alg:power} 会解析为框编号(这里是 1),显示为“Algorithm 1 is…”。
伪代码中的数学内容(如 $N \\neq 0$、$y \\gets 1$)与普通数学模式一样用 $…$ 包住。\gets 是左箭头(←),是表示赋值的惯用记法。与往常一样,使用 algorithm 的文档需要编译两次,以确定编号和交叉引用。
对手:algorithm2e
另一个主要选择是 algorithm2e。它是独立体系,把容器和内容 自成一体地放在一个包中,在 \begin{document} 前用 \usepackage[...]{algorithm2e} 加载即可。它的 algorithm 环境本身就是浮动体,因此不需要再嵌套内部正文环境。它的语法也与 algpseudocode 大不相同。
有两个差异最明显。第一,输入和输出使用专用命令 \KwIn{…}、\KwOut{…}(或 \KwData{…}、\KwResult{…}),分别排成粗体 “Input:”、“Output:”、“Data:”、“Result:”。第二,条件分支和循环把正文作为花括号参数传入。例如 if–then–else 写成 \eIf{cond}{then part}{else part},把块内容放入 { }(e 表示 “with else”)。\For{cond}{body} 和 \While{cond}{body} 也是同样。
还有一条需要特别注意:每条语句都必须以 \\; 结尾(反斜杠加分号)。忘记它,下一条语句会接在同一行。下面是一个简短示例。
\documentclass{article}
\usepackage[ruled,linesnumbered]{algorithm2e}
\begin{document}
\begin{algorithm}[H]
\caption{Sum of positive entries}
\KwIn{an array $a[1..n]$}
\KwOut{the sum $s$ of its positive entries}
$s \gets 0$\;
\For{$i \gets 1$ \KwTo $n$}{
\eIf{$a[i] > 0$}{
$s \gets s + a[i]$\;
}{
skip\;
}
}
\Return $s$\;
\end{algorithm}
\end{document}这个例子中,ruled 选项画出上下横线并在顶部放标题行,linesnumbered 给每行编号。[H] 是类似图表的位置指定,表示固定在当前位置而不让它浮动(也可以使用 h、t、b、p)。注意 algorithm2e 自己实现了 [H],所以不需要额外加载 float 包(与旧 algorithm 包中使用 [H] 时不同,不需要 \usepackage{float})。框开头是粗体 “Input:” 和 “Output:”;\For 行排成 for i ← 1 to n do,正文缩进;\eIf 把 if … then 的真分支放在 else 的假分支上方。每个 \; 处换行,最后一行输出 return s。
还有一些命令可以调整外观。\SetAlgoLined(旧名 \SetLine)会在块边界画竖线来显示结构;\DontPrintSemicolon 会让行末 \\; 不在输出中显示。\KwTo 输出 “to”,\Return 输出 “return”,它们都是内置关键字,也可以重新定义成日文等。注释可用 \tcc{…}(/* … */ 形式)或 \tcp{…}(// 形式)加入。
该用哪一个
两者都被广泛使用,更多是风格差异而不是优劣。algorithm + algpseudocode 提供以 \State 为基调的直接命令体系,适合喜欢传统伪代码外观(行首关键字,用 do、then、end … 围住)的用户,或会议模板默认采用它的情况。algorithm2e 则受喜欢显式输入/输出声明、用括号传递块正文、带竖线版式,以及大量使用多语言关键字和细节定制的人欢迎。
再重复一次:铁则是 正文包只选一个。同时加载 algpseudocode 和 algorithm2e 会在 \For、\If 等同名命令上冲突并报错。整个文档请统一使用其中之一。使用 algorithm2e 时,不需要容器包 algorithm,因为 algorithm2e 自己定义 algorithm 环境;两者同时加载会在环境名上冲突。