算法排版

论文和教材中常见的带框、编号为 “Algorithm 1” 的伪代码,并不是 LaTeX 的内置功能,而是由包提供的。第一个容易困惑的点,是有好几个名字相近的包。关键在于一种分工:一个包提供容器(会浮动的编号框),另一个包提供内容(伪代码本身)。本页先梳理这片生态,然后用可实际编译的代码介绍经典组合——用 algorithm 浮动体包住 algpseudocode 正文——以及自成一体的对手 algorithm2e。这里没有实时渲染器,所以正文会说明各示例会产生怎样的输出。

容器与内容:两层结构

伪代码排版分成两层,各有职责。第一层是 容器:它像 figuretable 一样在页面上浮动,是一个带有 “Algorithm 1” 标题、连续编号、交叉引用,甚至可生成算法目录的框。提供这一层的是 algorithm 包(\usepackage{algorithm}),它增加一个环境:\begin{algorithm}\end{algorithm}

第二层是 内容:用 \State(一条语句)、\While(循环)、\If(条件分支)等命令,带着缩进和行号排出伪代码本身。选择在这里分叉:老牌 algorithmic 包;更易定制的后继 algorithmicx(实际使用时通常用建立在它之上的 algpseudocode 布局);以及独立的另一套 algorithm2e

关键点是:正文层原则上只能选一个包algpseudocodealgorithm2e 在语法和理念上完全不同,混用会冲突。相对地,容器 algorithm 本来就是要与 algpseudocode 配套使用algorithm2e 自带容器和正文,因此不需要 algorithm(也不建议并用)。下表给出整体图景。

作用
algorithm容器浮动框 + “Algorithm N” 标题/编号;与正文包配套使用
algorithmic正文(旧)最初的伪代码环境;全大写命令(\STATE 等);几乎不能定制
algpseudocode正文(现代)algorithmicx 的标准布局;外观像 algorithmic,但灵活得多
algorithm2e两者独立自成一体;自有语法(\KwIn、\eIf、行末 \\;);单独使用

注意,algorithm 来自 algorithms bundle,同一 bundle 也包含 algorithmicalgorithmicx 是单独发布的,但加载 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 循环;输出以 “fordo” 开始,以 “end for” 结束。也有 \ForAll{cond}
  • \While{cond}\EndWhile — while 循环;“whiledo” / “end while”。
  • \If{cond}\ElsIf{cond}\Else\EndIf — 条件分支;“ifthen”、“else ifthen”、“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ⁿ 的经典算法。

document.tex
\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} 也是同样。

还有一条需要特别注意:每条语句都必须以 \\; 结尾(反斜杠加分号)。忘记它,下一条语句会接在同一行。下面是一个简短示例。

document.tex
\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] 是类似图表的位置指定,表示固定在当前位置而不让它浮动(也可以使用 htbp)。注意 algorithm2e 自己实现了 [H],所以不需要额外加载 float(与旧 algorithm 包中使用 [H] 时不同,不需要 \usepackage{float})。框开头是粗体 “Input:”“Output:”\For 行排成 for i ← 1 to n do,正文缩进;\eIfifthen 的真分支放在 else 的假分支上方。每个 \; 处换行,最后一行输出 return s。

还有一些命令可以调整外观。\SetAlgoLined(旧名 \SetLine)会在块边界画竖线来显示结构;\DontPrintSemicolon 会让行末 \\; 不在输出中显示。\KwTo 输出 “to”,\Return 输出 “return”,它们都是内置关键字,也可以重新定义成日文等。注释可用 \tcc{…}(/* … */ 形式)或 \tcp{…}(// 形式)加入。

该用哪一个

两者都被广泛使用,更多是风格差异而不是优劣。algorithm + algpseudocode 提供以 \State 为基调的直接命令体系,适合喜欢传统伪代码外观(行首关键字,用 dothenend … 围住)的用户,或会议模板默认采用它的情况。algorithm2e 则受喜欢显式输入/输出声明、用括号传递块正文、带竖线版式,以及大量使用多语言关键字和细节定制的人欢迎。

再重复一次:铁则是 正文包只选一个。同时加载 algpseudocodealgorithm2e 会在 \For\If 等同名命令上冲突并报错。整个文档请统一使用其中之一。使用 algorithm2e 时,不需要容器包 algorithm,因为 algorithm2e 自己定义 algorithm 环境;两者同时加载会在环境名上冲突。