拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LaTeX论文中算法伪代码与代码块排版实战指南

LaTeX论文中算法伪代码与代码块排版实战指南

写论文和报告的时候,最让我头疼的往往不是公式,而是算法伪代码和代码块。公式再复杂,LaTeX 里公式环境一开,模板套进去就行;伪代码这东西,用 Word 手调缩进和对齐能让人崩溃,截图糊上去又丑又没法改,审稿人那边每次改算法都得重截一版。我自己在写毕业论文和投期刊的时候,把 LaTeX 写伪代码和代码块的这套东西完整摸了一遍,这篇笔记就是把踩过的坑和最终沉淀下来的模板一次性整理出来,给同样在处理算法描述、代码展示的朋友们一个可以直接抄作业的参考。

这篇内容主要覆盖两大部分:一是用algpseudocode和algorithm2e两套主流宏包写伪代码的完整套路,二是用listings和minted展示代码块的选型、配置和实战细节。适合正在写毕业论文、准备投稿或者需要做课程设计报告的人,尤其是那些已经装好 LaTeX 但一遇到算法和代码就卡住的朋友。

1. 论文里的算法和代码,为什么值得用LaTeX单独处理

1.1 一份"能看"的伪代码到底要求什么

伪代码这东西,看着简单,真正排起来才知道坑多。它要求缩进严格对齐、关键字加粗、变量名用数学斜体、赋值符号规范、条件分支层次分明,最好每行还带着序号。这些要素组合在一起,用 Word 硬排的话,每次调整一个分支的缩进,后面所有行的 Tab 键都要重新按一遍,烦到怀疑人生。

更重要的是,伪代码里经常混着数学符号,比如$A[i] \gets key$、$\lfloor (low+high)/2 \rfloor$这种。这类符号要么用 Word 的公式编辑器一个个点,要么就把数学公式截图往文档里粘,两种方式都谈不上优雅。LaTeX 的好处在于,伪代码环境和数学模式天生无缝衔接,$...$直接嵌进去,符号字体和正文字体高度统一,PDF 里看起来很专业。

还有一点容易被忽略:伪代码在论文里往往需要被引用。算法改了一版,编号要跟着变,行号可能也要变。LaTeX 里的\label和\ref机制能自动处理这些引用关系,不需要手工维护。这一点在你后期修改算法、调整章节顺序时,能省下大量时间。

1.2 截图、Word、Markdown各自的软肋

先说说截图方案。很多人图省事,直接在 IDE 里截个图塞进论文。缺点是显而易见的:分辨率不统一,缩放后发虚;代码和算法一旦修改,截图就要重来;而且截图里的字体样式跟论文正文完全不搭,视觉上非常突兀。更麻烦的是,截图没法被检索,PDF 里的文字没法复制,审稿人想看清楚细节只能放大图像。

Word 方案的问题在于对齐和编号。Tab键按出来的缩进在不同字号、不同行距下会错位;代码中的等宽字体、语法高亮、行号这些功能,Word 虽然能做一点,但配置繁琐,改一次样式全局乱套。伪代码里的循环、分支嵌套层级一深,Word 的列表层级和缩进就很容易失控。

Markdown 的代码块写博客很好用,但放到论文场景里就力不从心了:它没有浮动体机制,代码块位置不好控制;不支持行号引用;和 LaTeX 公式混排也麻烦。所以真正要产出高质量 PDF 文档,还是得回到 LaTeX 这套原生排版系统上,在导言区做一次配置,后面所有算法和代码都能统一风格地排出来,这才是长期收益最大的方案。

2. 伪代码宏包四兄弟:algorithm、algorithmic、algorithmicx与algorithm2e

2.1 这四个宏包是什么关系

LaTeX 写伪代码,绕不开几个名字相近的宏包:algorithm、algorithmic、algorithmicx、algorithm2e。新手经常会困惑:到底要装哪个?它们之间是什么关系?

简单来说,algorithm宏包提供的是浮动体环境,就是让算法像表格、图片一样可以整体浮动,带编号和标题。它本身不负责伪代码的具体排版。真正负责排版的是后面几个。

algorithmic是较早的一套算法排版宏包,语法比较简单,但定制能力弱,现在很多模板已经不推荐直接使用了。algorithmicx是它的升级版,提供了一套更灵活的框架,允许用户定义自己的算法语言风格。algpseudocode就是基于algorithmicx实现的一种风格,这也是目前最常见、最通用的一套写法,命令语义清晰,写出来的代码可读性很高,网上大部分模板用的都是它。

algorithm2e则是另一套独立的宏包,语法风格和algorithmicx差别很大。它的命令更像编程语言本身,比如语句末尾要加分号、块结构用花括号表示,还自带ruled、boxed、plain三种样式,在计算机学科的一些模板里见得很多。

这里要特别提醒:algorithm2e和algorithmicx两套不要同时引入,它们功能冲突,命令也会有大量的重定义问题,编译报错非常难排查。选一套用到低,切换成本不低。

2.2 我的选型结论

我自己最终选定的是algorithm+algorithmicx+algpseudocode这套组合。原因有几个:第一,它的命令可读性好,\If、\While、\Function一眼就能看懂,修改算法逻辑时不容易漏掉\EndIf、\EndWhile;第二,它在学术模板中的兼容性普遍较好,很多期刊、学校的毕业论文模板默认支持的就是这一套;第三,它和algorithm浮动体环境配合稳定,不太容易出现奇怪的冲突。

algorithm2e我也完整学过一遍,后面会单独讲它的写法和差异。如果你用的是某些计算机学会模板或者投某些会议,模板里已经强制用了algorithm2e,那就跟着模板走;如果是自己从零搭环境,我建议直接从algpseudocode入手,学习曲线更平缓,出错率也更低。

3. algpseudocode实战:从模板到自定义样式

3.1 最小可用框架

用algpseudocode写伪代码,导言区最少需要这三行:

\usepackage{algorithm} \usepackage{algorithmicx} \usepackage{algpseudocode}

然后在正文中这样组织:

\begin{algorithm}[htbp] \caption{二分查找} \label{alg:binary_search} \begin{algorithmic}[1] \Require 有序数组 $A[1..n]$,目标值 $key$ \Ensure 目标值所在下标,若不存在则返回 $-1$ \State $low \gets 1$,$high \gets n$ \While{$low \le high$} \State $mid \gets \lfloor (low+high)/2 \rfloor$ \If{$A[mid] == key$} \State \Return $mid$ \ElsIf{$A[mid] < key$} \State $low \gets mid + 1$ \Else \State $high \gets mid - 1$ \EndIf \EndWhile \State \Return $-1$ \end{algorithmic} \end{algorithm}

这里重点解释几个关键点。\begin{algorithmic}[1]中括号里的参数表示行号的起始编号,写[1]就是第一行从 1 开始编号,如果省略数字,则不显示行号。\caption必须写在\label之前,\label才能在之后用\ref正常引用到算法编号。\Require和\Ensure默认输出"Require:"和"Ensure:",用于描述输入输出,语义上非常直观。

\State命令负责开启一行伪代码,相当于"这一行要开始写内容了"。\Return在algpseudocode里默认是小写return,后面直接跟要返回的内容。\gets是赋值箭头,写在数学模式$...$里面,比直接打等号更符合算法描述的惯例。

3.2 常用命令速查与易错点

algpseudocode的核心控制结构命令并不算多,把下面这些记熟,绝大多数算法都能写出来。

命令作用备注
\State <内容>开始一行普通语句这是最常用的命令
\If{条件}...\ElsIf{条件}...\Else...\EndIf条件分支\ElsIf注意大小写,不要写成\ElseIf
\For{条件}...\EndFor循环条件写在花括号里
\While{条件}...\EndWhile循环和\For类似
\Repeat...\Until{条件}直到型循环先执行后判断
\Function{名字}{参数}...\EndFunction函数定义自动缩进,函数体会整体右移
\Return <内容>返回语句默认输出小写 return
\Comment{注释}行尾注释自动右对齐,很方便
\Call{函数名}{参数}函数调用比如\Call{BinarySearch}{A, n, key}

易错点有几个。第一,\ElsIf不要写成\ElseIf或者\Elif,这是algpseudocode的命令名称,拼错不会报错,但输出结果会很奇怪。第二,所有条件、变量、表达式如果涉及数学符号,必须放进$...$或\ensuremath{}里,否则符号会以文本模式输出,看起来歪歪扭扭。第三,块结构必须闭合,\If对应\EndIf,\While对应\EndWhile,漏掉一个,整段伪代码的缩进都会错乱,而且报错位置往往在很远的地方,不好定位。

\Function是一个值得多说几句的命令。它会让函数名以加粗文本输出,参数列表放在后面的圆括号里,函数体整体缩进,非常美观。我一般把算法分成若干函数来写,主流程调\Call调用子函数,整个算法的结构性会比一坨\State强很多。

3.3 中文化标签、编号与浮动体控制

国内论文常常要把"Require"和"Ensure"改成"输入"和"输出",algpseudocode提供了重定义命令的方式:

\renewcommand{\algorithmicrequire}{\textbf{输入:}} \renewcommand{\algorithmicensure}{\textbf{输出:}}

放在导言区即可,这样所有算法的输入输出标签都会变成中文。注意这里的冒号建议用中文全角冒号,视觉上更统一。

关于算法编号,默认情况下algorithm环境的编号是按文章顺序排的,比如"算法1、算法2"。如果你希望编号带章节前缀,比如"算法3.2",可以使用\usepackage{chngcntr}或者\counterwithin命令把algorithm计数器绑定到section:

\usepackage{chngcntr} \counterwithin{algorithm}{section}

这个技巧在写学位论文时特别有用,因为学位论文章节多,算法不按章节编号的话很容易混乱。

还有一个很容易踩的坑是浮动体位置。\begin{algorithm}[htbp]中,h表示当前位置,t表示页顶,b表示页底,p表示独立一页。默认写[htbp]是给 LaTeX 充分的自由度,它可能会把算法挪到别的位置去。如果你希望算法严格出现在当前文字附近,可以引入float宏包后使用[H],表示强制放在这里:

\usepackage{float} \begin{algorithm}[H]

不过要提醒一句,双栏排版下使用[H]要非常小心,算法太长时会直接把两栏撑乱,反而适得其反。我的经验是:单栏文档用[H]没问题,双栏模板尽量用[htbp],如果算法放的位置实在不理想,再手动调整代码顺序。

4. algorithm2e的另一种写法:分号、块结构和参数风格

4.1 algorithm2e版本的二分查找

如果你投的模板要求用algorithm2e,上面的\If...\EndIf那套写法就行不通了。algorithm2e的语法更像编程语言,语句以\;结束,块结构用花括号或者\Begin表示。同样实现二分查找,algorithm2e的写法是:

\usepackage[ruled,linesnumbered]{algorithm2e} \begin{algorithm}[H] \caption{二分查找} \label{alg:binary_search2} \KwIn{有序数组 $A[1..n]$,目标值 $key$} \KwOut{目标值所在下标,若不存在则返回 $-1$} \Begin{ $low \gets 1$\; $high \gets n$\; \While{$low \le high$}{ $mid \gets \lfloor (low+high)/2 \rfloor$\; \eIf{$A[mid] == key$}{ \KwRet{$mid$}\; }{ \If{$A[mid] < key$}{ $low \gets mid + 1$\; }{ $high \gets mid - 1$\; } } } \KwRet{$-1$}\; } \end{algorithm}

注意几个关键差异。\KwIn和\KwOut对应algpseudocode里的\Require和\Ensure。\eIf是if-else的合体,整个结构是一个块,后面跟两个花括号分别表示真分支和假分支。\KwRet是返回命令,输出"return"。每一行语句末尾都要加\;,漏掉的话,后续语句可能被错误合并到同一行,这是新手最容易犯的错误。

4.2 两种风格的对比与切换注意点

两套宏包的风格差异,可以用下面这个表格一次性看清:

对比维度algpseudocodealgorithm2e
语句结束方式无需特殊符号,\State自动换行每行末尾必须加\;
分支结构\If{条件}...\EndIf\If{条件}{...}或\eIf{条件}{...}{...}
输入输出命令\Require、\Ensure\KwIn、\KwOut
返回命令\Return\KwRet
内置样式依赖algorithm浮动体,默认样式简洁支持plain、ruled、boxed三种样式
自定义灵活性重定义\algorithmicxxx命令提供\SetKwInput、\SetKwBlock等定制命令

切换使用时,最需要注意的是不能两套混用。有些初学者会把algorithm2e的\;加到algpseudocode里,或者把\Return写进algorithm2e环境,编译直接报"undefined control sequence"。我的经验是,一旦确定模板用哪套,就只在那一套的体系内查资料、写命令,不要凭印象混搭。

4.3 浮动体与跨栏问题的实操建议

algorithm2e的浮动体控制比algpseudocode稍微直观一点,它支持[H]强制位置,这在单栏文档中效果很好。双栏论文中使用algorithm2e时,如果算法太长,可以考虑让算法跨两栏显示,方法是:

\begin{algorithm*}[t] \caption{跨双栏的算法} ... \end{algorithm*}

带星号的algorithm*环境可以让算法横跨两个栏,适合那种特别长、单栏放不下的算法。这个特性在某些双栏模板中是救命的,否则一个长算法被迫拆到两栏里,阅读体验非常糟糕。同样的道理,algpseudocode配合algorithm宏包也可以使用algorithm*环境,只是有时模板会覆盖这个环境的行为,需要实测确认。

样式方面,ruled会在算法顶部和底部画横线,标题横跨整个算法宽度,很符合期刊风格;boxed给算法加方框,适合报告类文档;plain是最简洁的样式。如果模板默认样式不满意,可以用\RestyleAlgo{ruled}在导言区全局调整,不需要逐个改\begin{algorithm}的参数。

5. 代码块:listings与minted的方案对比

5.1 verbatim为什么只配临时用

LaTeX 里最基础的代码展示环境是verbatim,它的作用是原样输出内容,空格、换行、特殊字符都不会被处理。但它有几个致命的缺点:没有语法高亮、没有行号、没有边框背景样式、代码过长时不会自动断行。所以verbatim只适合临时展示一小段配置,真正要在论文里放代码,必须用专门宏包。

主流的代码块方案有两个:listings和minted。listings是纯 LaTeX 实现,不依赖外部工具,几乎所有 LaTeX 发行版自带;minted底层调用 Python 的 Pygments 语法高亮库,效果好一个档次,但需要额外安装软件、配置编译参数。下面分别说。

5.2 listings的完整配置与中文注释处理

listings的基本配置并不复杂,难点往往在中文注释上。先给一套我沉淀下来的实用配置:

\usepackage{listings} \usepackage{xcolor} \lstdefinestyle{mycode}{ language=Python, basicstyle=\ttfamily\small, keywordstyle=\color{blue}\bfseries, commentstyle=\color{gray!70}\itshape, stringstyle=\color{teal}, numbers=left, numberstyle=\tiny\color{gray}, numbersep=8pt, frame=single, rulecolor=\color{gray!40}, backgroundcolor=\color{gray!5}, breaklines=true, postbreak=\mbox{\textcolor{gray}{$\hookrightarrow$}\space}, showstringspaces=false, tabsize=4, captionpos=b }

在正文中使用:

\begin{lstlisting}[style=mycode, caption={示例代码}, label={lst:demo}] def hello(): print("Hello, LaTeX!") \end{lstlisting}

label={lst:demo}放在lstlisting的可选参数里,之后可以用\ref{lst:demo}引用这个代码块的编号。

这里必须专门说说中文注释的问题。listings底层按字节读取代码,对中文的支持一直不算好。在 XeLaTeX 编译下,注释里的中文经常显示成乱码或者直接被吞掉。我的处理方案有三个,按优先级排序:

第一,代码里的注释尽量写英文,这是最省事也最稳妥的方案,学术论文里的代码注释本来就很短,用英文完全没问题。第二,如果一定要中文注释,在\lstset里加上escapeinside=`` ``,然后在中文注释两侧用反引号包裹,让 LaTeX 层处理这部分内容:

\begin{lstlisting}[escapeinside=``] # 这段注释是中文的,用反引号包起来 def hello(): print("hi") \end{lstlisting}

第三,放弃listings改用minted。minted对中文注释的支持天然好很多,因为它通过 Pygments 生成高亮的 LaTeX 代码,中文部分由 LaTeX 字体机制接管,基本不会乱码。

5.3 minted的高亮效果与shell-escape

minted的效果确实好,语法高亮是 Pygments 做的,配色更细腻,支持的语言列表非常长,几乎覆盖所有主流语言。使用方式很简洁:

\usepackage{minted} \begin{minted}[linenos,breaklines,frame=single,fontsize=\small]{python} def hello(): print("Hello, LaTeX!") \end{minted}

关键问题是编译方式变了:minted需要调用外部 Python 脚本,编译时必须加上-shell-escape参数,也就是要用下面的命令编译:

xelatex -shell-escape main.tex

如果忘记加-shell-escape,编译会直接报错,提示Pygments未运行之类的问题。用 Overleaf 的网页版倒是没这个烦恼,Overleaf 默认允许minted运行,只要在菜单里把编译器切到 XeLaTeX 或 LuaLaTeX 即可。

安装 Pygments 的命令是:

pip install pygments

装好之后在本地编译,第一次运行会比较慢,因为每次都要调 Pygments 生成高亮内容,后面会慢慢好起来。如果你经常用 VSCode 编写 LaTeX,建议把编译命令里的-shell-escape加进 LaTeX Workshop 的配置里,具体在.vscode/settings.json中修改latex-workshop.latex.tools中的args参数,加上-shell-escape即可。

6. 代码块进阶:断行、引用、外部文件一次讲清楚

6.1 长代码段的断行与跨页

写论文时经常要在附录里贴一整段工程代码,这种几百行的代码块,处理起来比伪代码麻烦得多。首先是断行问题。breaklines=true可以让长行自动断行,但这个断行是"视觉断行",不是逻辑断行,阅读起来有时会有点跳跃。我的建议是:正文里的示例代码尽量控制每行长度,自己手动换行,责任不要全丢给宏包。

真正让我头疼过的是跨页问题。lstlisting环境默认允许跨页,代码太长时会自动分页,但问题是跨页后行号会连续还是重新计数,不同版本表现不一致。我的处理方式是,对于很长的大段代码,使用\lstinputlisting从外部文件导入,而不是直接粘贴在 tex 文件里。

\lstinputlisting[style=mycode, firstline=1, lastline=100]{code/example.py}

这样有几个好处:一是 tex 文件不会被超长代码撑得没法看;二是代码可以单独在编辑器里维护,改完重新编译即可;三是可以通过firstline、lastline参数自由控制展示范围,不需要复制粘贴出来裁剪。唯一的注意点是文件路径要正确,相对路径是相对于 tex 文件所在的目录,不是相对于当前工作目录。

6.2 代码行号引用与局部展示

行号引用是论文中比较高级的需求。比如审稿意见说"第 15 行那个变量命名不规范",你需要在回复中提到具体的行号。listings的行号也能用\ref引用,方法是借助escapeinside插入一个标签:

\begin{lstlisting}[escapeinside=``] def hello(): print("hi") %`\label{line:hello}`% \end{lstlisting}

然后在正文中写"见代码第\ref{line:hello}行",引用到的就是那行代码的行号。这个功能的原理是,escapeinside让 LaTeX 处理反引号之间的内容,\label记录了当前行号。不过说实话,这个功能在不同版本宏包下偶尔会有兼容问题,我建议在正式提交前做一次实测,不行的话就直接手动写行号,损失不大。

局部展示除了用\lstinputlisting的firstline/lastline参数,还有另一种方式是linerange,配合逗号分隔可以展示多个不连续区间,比如linerange={1-10,20-30}。这在解释"只保留核心逻辑"的代码片段时很实用。

6.3 与VSCode+LaTeX Workshop的配合经验

既然前面提到了 VSCode 配置,这里展开说一下我自己的编辑器方案。我日常写 LaTeX 用的是 VSCode 加 LaTeX Workshop 插件,编译器用 XeLaTeX,这是为了中文支持。

配合minted时,我会在settings.json里单独配置一个带-shell-escape的编译工具。LaTeX Workshop 的 recipes 允许把不同的编译命令组合成一个工具链,比如"XeLaTeX(minted)",这样既不影响普通文档的编译速度,也解决了minted必须有-shell-escape的问题。

有一个细节:minted缓存目录.minted_cache会在项目根目录生成,记得把它加入.gitignore,不然协作时会提交一堆没用的文件。用listings则完全没这个问题,因为它不需要外部工具,编译速度快,这也是我平时更常用listings的原因。

另外,我在 VSCode 里写伪代码时有个小习惯:算法环境里的\State、\If、\While这些命令,我会利用 LaTeX Workshop 的代码片段功能做成快捷输入,敲\If直接补全成\If{}...\EndIf的骨架,效率提升非常明显。这个方式也推荐给经常要写伪代码的朋友,配置一次,长期受益。

最后再分享一个我自己的使用习惯:把常用的\lstdefinestyle和伪代码宏包的配置单独存成一个style.tex文件,放到项目根目录,每篇新论文用\input{style.tex}直接引入。这样不管开多少新文档,代码块和伪代码的样式永远是统一的,不用每次重复配置。踩过几次坑之后,我是真的理解了这句话——LaTeX 的排版能力上限很高,但下限也很低,好的配置和模板,值得花点时间一次性打磨到位。

返回列表