算法排版

排版伪代码的 LaTeX 宏包名字相近得令人困惑——algorithmalgorithmicalgorithmicx(实际使用的是 algpseudocode)、algorithm2e——第一道坎就是搞清楚哪些会互相打架。然而真正危险的组合 并不是会报错的那个。在 TeX Live 2024 上把所有组合都试过之后发现,把 algorithm2ealgpseudocode 一起加载 完全不会在加载时报错,而只要写下 \For,输出就会悄无声息地损坏。本页先厘清双层结构(外框与内容),再给出附有实际复现错误信息的冲突表,讲解 \State\Procedure 的写法,展示如何自行重新声明关键词,并给出面向今天入门者的推荐。

双层结构:造外框的宏包与写内容的宏包

排版伪代码分为职责不同的两层。第一层是 外框:一个像 figuretable 一样在页面上浮动的盒子,带有编号的 “Algorithm 1” 标题,可以交叉引用,甚至可以汇成一份列表。这由 algorithm 提供,它在内部调用 float 宏包的 \newfloat 来创建一个新浮动体(algorithm.sty 第 31 行是 \RequirePackage{float},第 82–94 行是 \newfloat{algorithm}{htbp}{loa})。它的默认外观是 \floatstyle{ruled}——也就是上下各一条横线的那种样式。

第二层是 内容——\State(一行)、\While(循环)、\If(分支)等命令,它们带着缩进和行号排出伪代码本身。这里的选择分成三支:老旧的 algorithmic、其自由度更高的后继 algorithmicx(实务中加载建立在它之上的布局 algpseudocode),以及独立自成一界的 algorithm2e。让人困惑的不只是名字:加载 algpseudocode 之后,正文里要用的环境名依然是 algorithmic。宏包名与环境名并不一致。

宏包角色
algorithm外框基于 float、用 \newfloat 创建的浮动体。负责 \caption\label\listofalgorithms。需与内容宏包搭配使用
algorithmic内容(旧)最早的伪代码环境;命令全大写(\STATE);几乎无法定制。与 algorithm 同属 algorithms 套件
algpseudocode内容(现行)建立在 algorithmicx 之上的标准布局;命令首字母大写(\State);环境名仍为 algorithmicalgorithmicx 会被自动加载
algorithm2e两者兼具自带外框与内容的独立体系;有自己的语法 \KwIn\eIf 以及行末的 \\;。请单独使用

哪些组合会坏:在 TeX Live 2024 上逐一试过

直接说结论:安全的组合只有 algorithm + algpseudocode 一种。其余组合会以三种不同的方式出错。下面照原样列出实际运行得到的消息。

reproduced on TeX Live 2024
% SAFE — the intended pairing, compiles cleanly
\usepackage{algorithm}\usepackage{algpseudocode}

% two body packages: the environment name collides
\usepackage{algorithmic}\usepackage{algpseudocode}
! LaTeX Error: Command \algorithmic already defined.

% algorithm2e already owns a container — order changes only which name trips first
\usepackage{algorithm2e}\usepackage{algorithm}
! LaTeX Error: Command \listofalgorithms already defined.

\usepackage{algorithm}\usepackage{algorithm2e}
! LaTeX Error: Command \algorithm already defined.
! LaTeX Error: Command \algorithm* already defined.

以上都属于容易辨认的失败。名字一冲突 LaTeX 就会停下,读日志第一行即可定位原因。问题出在第四种组合——algorithm2ealgpseudocode两种加载顺序都试过,加载时的错误数为零。 更糟的是,如果只是各自老老实实地使用各自的环境,两者看上去都能工作。可一旦在 algorithmic 环境里写下 \For,就会崩掉。原因是两者都定义了 \For\If 这样 同名却不会报错的 命令。

the silent one
\usepackage{algorithm2e}
\usepackage{algpseudocode}   % loads fine. no error. no warning.
...
\begin{algorithmic}[1]
  \State $x \gets 0$
  \For{$i=1$ to $n$} \State $x \gets x+i$ \EndFor
\end{algorithmic}

% output is scrambled — lines merge and reorder — and only then:
! Missing number, treated as zero.
<to be read again> \ALG@b@2@EndFor@0
l.9 \EndFor

错误出现在 \EndFor 那一行,措辞还是“缺少数字”——离真正的原因几乎再远不过。没有任何线索指向导言区那两行 \usepackage这正是它在四种组合中最糟糕的原因。 另外要注意,这与两次 \usepackage 选项不一致导致的“选项冲突”是 不同的失败。选项冲突基于子集判定触发——第二次加载请求了第一次没有的选项——并给出专用消息 Option clash for package。这里发生的只是 命令名重复:两个宏包定义了同一个 \For

algpseudocode 的写法:从 \State\Procedure

伪代码写在 \begin{algorithmic}\end{algorithmic} 之内,可选参数控制行号[0] 不编号,[1] 每行都编号,[n] 每 n 行编一次。核心是 \State每条语句放一个——赋值、过程调用等。像 \While\If 这类开启代码块的命令前面 不要\State(它们本身就另起一行)。块内内容会自动缩进,源文件中的空白不影响输出。命令采用首字母大写(\State),这是与全大写的旧 algorithmic\STATE\WHILE)区分开来的最快线索。

  • \State — 一条语句(一行)的开始;写作 \State $x \gets 1$
  • \For{cond}\EndFor — 循环;输出以 “fordo” 开始,以 “end for” 结束。另有 \ForAll{cond}
  • \While{cond}\EndWhile — “whiledo”/“end while”。也可使用 \Repeat\Until{cond}
  • \If{cond}\ElsIf{cond}\Else\EndIf — 分支;“ifthen”“else ifthen”“else”“end if”。\ElsIf\Else 可省略。
  • \Procedure{name}{args}\EndProcedure — 过程;“procedure name(args)”/“end procedure”。函数形式 \Function{name}{args}\EndFunction 结构相同。
  • \Return — 返回值;排为粗体 “return” 后接该值。
  • \Comment{...} — 行末注释;置于向右的三角 ▷ 之后。
  • \Require / \Ensure — 前置条件与后置条件;分别以粗体 “Require:”“Ensure:” 起头。
document.tex
\documentclass{article}
\usepackage{algorithm}
\usepackage{algpseudocode}

% declare your own keywords: Require/Ensure become Input/Output
\renewcommand{\algorithmicrequire}{\textbf{Input:}}
\renewcommand{\algorithmicensure}{\textbf{Output:}}

\begin{document}
\listofalgorithms

\begin{algorithm}
  \caption{Power}\label{alg:p}
  \begin{algorithmic}[1]
    \Require $n \ge 0$
    \Ensure  $y = x^n$
    \Procedure{Power}{$x, n$}
      \State $y \gets 1$
      \While{$n \neq 0$}
        \State $y \gets y \times x$ \Comment{one step}
        \State $n \gets n - 1$
      \EndWhile
      \State \Return $y$
    \EndProcedure
  \end{algorithmic}
\end{algorithm}

See Algorithm~\ref{alg:p}.
\end{document}

把它编译两遍,就会得到一个上下带横线的浮动盒子,顶端写着 “Algorithm 1 Power”。左侧依次排列 1 到 8 的行号,\Require\Ensure 两行如重定义所示输出为 “Input:” 和 “Output:”——关键词并非装饰,而是 可以替换的声明。照同样的办法改写 \algorithmicwhile\algorithmicdo\algorithmicend 等,循环与分支的用词也能随之更改。开头的 \listofalgorithms 会生成一份 “List of Algorithms”(algorithm.sty 使用扩展名为 loa 的目录文件),正文中的 \ref{alg:p} 会解析为 “See Algorithm 1.”。务必编译两遍,让编号和交叉引用稳定下来。

关于外框有一处广为流传的误解,在此更正。当你写 \begin{algorithm}[H] 想把盒子 固定在原地 时,不需要 自己加载 float 宏包。algorithm.sty 第 31 行就是 \RequirePackage{float},所以只要加载了 algorithmfloat 就已在其中。在 TeX Live 2024 上,用一份只写了 \usepackage{algorithm} 的文档加上 [H],已确认盒子固定在前后正文之间。顺带一提,algorithm 自身接受 plainruledboxed 三个选项来选择边框样式(默认为 ruled)。

algorithm2e:用花括号传递代码块,行末必须打 \\;

另一个主要选择是 algorithm2e。它自成一体,用一个宏包同时提供外框与内容,在导言区以 \usepackage[…]{algorithm2e} 加载。它的 algorithm 环境本身就是浮动体,因此没有需要嵌套的内层环境。语法与 algpseudocode 也大相径庭,有三点需要区分。第一,输入输出使用专用命令 \KwIn{…}\KwOut{…}(或 \KwData{…}\KwResult{…})。第二,分支与循环把主体作为花括号参数传入\eIf{cond}{then 部分}{else 部分}e 表示“带 else”)、\For{cond}{body}\While{cond}{body}。第三,每条语句都必须以 \\; 结尾——忘了它,下一条语句就会挤到同一行上。

document.tex
\documentclass{article}
\usepackage[ruled, vlined, linesnumbered]{algorithm2e}
\SetKwInOut{Param}{Parameters}   % declare a keyword of your own
\DontPrintSemicolon              % hide the line-ending \;

\begin{document}
\begin{algorithm}[H]
  \caption{Sum of positive entries}\label{alg:s}
  \KwIn{an array $a[1..n]$}
  \Param{tolerance $\epsilon$}
  \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]$\tcp{keep it}
    }{
      \tcc{skip}
    }
  }
  \Return $s$\;
\end{algorithm}
See Algorithm~\ref{alg:s}.
\end{document}

ruled 会在上下画横线并在顶部加一行标题,vlined 添加标示块结构的竖线(在正文中做同样事情的命令是 \SetAlgoLined,其旧名为 \SetLine),linesnumbered 给每行编号。\DontPrintSemicolon 用于把行末的 \\; 从输出中隐藏,适合不想让伪代码看起来像编程语言的场合。注释有两种:\tcp{…}// 形式,位于行末)与 \tcc{…}/* … */ 形式,自成一行)。而 \SetKwInOut{Param}{Parameters} 正是本页的主题\KwIn\KwOut 并不特殊,它们由同一套声明机制造出,你也可以自己再添一个。类似地还有 \SetKw\SetKwFunction\SetKwData\KwTo\Return 背后的词也可以替换。[H]algorithm2e 自行实现,因此这里同样不必显式加载 float

如果从今天开始,该选哪一个

推荐 algorithm + algpseudocode,这并非口味问题,而基于三条实务事实。第一,只有这个组合能在 TeX Live 2024 上无冲突地编译。第二,由于外框是建立在 float 之上的普通浮动体,\caption\label\ref\listofalgorithms 的用法与图表完全一致,无需重新学习。第三,一条语句一个 \State 的写法,正是多数期刊与会议模板已经默认的风格。

选择 algorithm2e 的理由同样明确:你想显式声明输入输出,喜欢用花括号传递代码块和竖线式布局,或者打算大量自造关键词。只要符合其中之一,它就是更自然的选择。无论选哪一个,原则只有一条:整份文档只用一个内容宏包;如果使用 algorithm2e,就 不要加载 algorithm。若想两个都试,请在不同文件中比较,而不要在同一文档里混用——导言区的那两行,会以 \EndFor 行上莫名其妙的错误回敬你。