ARTICLE DETAIL

资讯详情

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

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

LaTeX论文中算法伪代码与代码块排版实战指南 写论文和报告的时候最让我头疼的往往不是公式而是算法伪代码和代码块。公式再复杂LaTeX 里公式环境一开模板套进去就行伪代码这东西用 Word 手调缩进和对齐能让人崩溃截图糊上去又丑又没法改审稿人那边每次改算法都得重截一版。我自己在写毕业论文和投期刊的时候把 LaTeX 写伪代码和代码块的这套东西完整摸了一遍这篇笔记就是把踩过的坑和最终沉淀下来的模板一次性整理出来给同样在处理算法描述、代码展示的朋友们一个可以直接抄作业的参考。这篇内容主要覆盖两大部分一是用algpseudocode和algorithm2e两套主流宏包写伪代码的完整套路二是用listings和minted展示代码块的选型、配置和实战细节。适合正在写毕业论文、准备投稿或者需要做课程设计报告的人尤其是那些已经装好 LaTeX 但一遇到算法和代码就卡住的朋友。1. 论文里的算法和代码为什么值得用LaTeX单独处理1.1 一份能看的伪代码到底要求什么伪代码这东西看着简单真正排起来才知道坑多。它要求缩进严格对齐、关键字加粗、变量名用数学斜体、赋值符号规范、条件分支层次分明最好每行还带着序号。这些要素组合在一起用 Word 硬排的话每次调整一个分支的缩进后面所有行的 Tab 键都要重新按一遍烦到怀疑人生。更重要的是伪代码里经常混着数学符号比如$A[i] \gets key$、$\lfloor (lowhigh)/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与algorithm2e2.1 这四个宏包是什么关系LaTeX 写伪代码绕不开几个名字相近的宏包algorithm、algorithmic、algorithmicx、algorithm2e。新手经常会困惑到底要装哪个它们之间是什么关系简单来说algorithm宏包提供的是浮动体环境就是让算法像表格、图片一样可以整体浮动带编号和标题。它本身不负责伪代码的具体排版。真正负责排版的是后面几个。algorithmic是较早的一套算法排版宏包语法比较简单但定制能力弱现在很多模板已经不推荐直接使用了。algorithmicx是它的升级版提供了一套更灵活的框架允许用户定义自己的算法语言风格。algpseudocode就是基于algorithmicx实现的一种风格这也是目前最常见、最通用的一套写法命令语义清晰写出来的代码可读性很高网上大部分模板用的都是它。algorithm2e则是另一套独立的宏包语法风格和algorithmicx差别很大。它的命令更像编程语言本身比如语句末尾要加分号、块结构用花括号表示还自带ruled、boxed、plain三种样式在计算机学科的一些模板里见得很多。这里要特别提醒algorithm2e和algorithmicx两套不要同时引入它们功能冲突命令也会有大量的重定义问题编译报错非常难排查。选一套用到低切换成本不低。2.2 我的选型结论我自己最终选定的是algorithmalgorithmicxalgpseudocode这套组合。原因有几个第一它的命令可读性好\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 (lowhigh)/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 (lowhigh)/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}{ languagePython, basicstyle\ttfamily\small, keywordstyle\color{blue}\bfseries, commentstyle\color{gray!70}\itshape, stringstyle\color{teal}, numbersleft, numberstyle\tiny\color{gray}, numbersep8pt, framesingle, rulecolor\color{gray!40}, backgroundcolor\color{gray!5}, breaklinestrue, postbreak\mbox{\textcolor{gray}{$\hookrightarrow$}\space}, showstringspacesfalse, tabsize4, captionposb }在正文中使用\begin{lstlisting}[stylemycode, 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-escapeminted的效果确实好语法高亮是 Pygments 做的配色更细腻支持的语言列表非常长几乎覆盖所有主流语言。使用方式很简洁\usepackage{minted} \begin{minted}[linenos,breaklines,framesingle,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 长代码段的断行与跨页写论文时经常要在附录里贴一整段工程代码这种几百行的代码块处理起来比伪代码麻烦得多。首先是断行问题。breaklinestrue可以让长行自动断行但这个断行是视觉断行不是逻辑断行阅读起来有时会有点跳跃。我的建议是正文里的示例代码尽量控制每行长度自己手动换行责任不要全丢给宏包。真正让我头疼过的是跨页问题。lstlisting环境默认允许跨页代码太长时会自动分页但问题是跨页后行号会连续还是重新计数不同版本表现不一致。我的处理方式是对于很长的大段代码使用\lstinputlisting从外部文件导入而不是直接粘贴在 tex 文件里。\lstinputlisting[stylemycode, firstline1, lastline100]{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 与VSCodeLaTeX 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 的排版能力上限很高但下限也很低好的配置和模板值得花点时间一次性打磨到位。
返回列表