<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>排序 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E6%8E%92%E5%BA%8F/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 08 Dec 2023 10:40:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E6%8E%92%E5%BA%8F/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>《计算机程序设计艺术》笔记：Knuth 用半个世纪给算法写的一部《史记》</title>
      <link>https://ftmao.com/blog/bookstore/taocp-book-notes/</link>
      <pubDate>Fri, 08 Dec 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/taocp-book-notes/</guid>
      <description>&lt;h1 id=&#34;计算机程序设计艺术笔记knuth-用半个世纪给算法写的一部史记&#34;&gt;《计算机程序设计艺术》笔记：Knuth 用半个世纪给算法写的一部《史记》&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：被&amp;quot;要不要读 TAOCP&amp;quot;这个问题折磨过的工程师；也想给正在啃这套书的人一张地图和一份劝退/劝进并存的导读。
参考书籍：Donald E. Knuth《计算机程序设计艺术》（The Art of Computer Programming）：卷 1《基本算法》（1968）、卷 2《半数值算法》（1969）、卷 3《排序与查找》（1973）、卷 4A《组合算法（一）》（2011），以及陆续以分册（fascicle）形式发布的 4B、4C。
阅读目标：不假装自己通读了这套书——说实话，没人通读过——而是把它读成一张&amp;quot;算法领域的地形图&amp;quot;：每卷解决什么问题、Knuth 的方法论是什么、哪些内容今天仍是工程师的硬通货、正确的打开姿势是什么。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一定位这不是教材是算法学科的史记&#34;&gt;一、定位：这不是教材，是算法学科的《史记》&lt;/h2&gt;
&lt;p&gt;先破一个流传最广的误读：TAOCP 不是&amp;quot;编程教程&amp;quot;，甚至不是&amp;quot;算法教材&amp;quot;。它是 Knuth 从 1962 年开始写的一部&lt;strong&gt;算法领域的编年史加分析法典&lt;/strong&gt;——每一种重要算法都给出精确的描述、完整的正确性论证、严格的运行时间分析，以及详尽到令人发指的文献考证。Bill Gates 那句著名的话是对这套书最准确的注脚：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;如果你觉得自己是个足够优秀的程序员，就去读 Knuth 的《计算机程序设计艺术》……如果你能读完它，请一定把简历发给我。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这句话的重点不在&amp;quot;读完&amp;quot;，在&amp;quot;读完的人值得收简历&amp;quot;——它说的是这套书的密度。Knuth 自己的写作史也配得上这份密度：1962 年动笔时他以为自己写的是一本&amp;quot;编译器构造&amp;quot;的书，越写发现底料越多，最终膨胀成七卷规划；卷 1 到卷 3 在 1968-1973 年间出齐，然后卷 4 一写就是近四十年，2011 年才出了 4A，至今仍在以分册形式连载。顺带一个工程师会心一笑的插曲：1977 年卷 2 出第二版时，出版商改用照相排版，Knuth 对数学公式的排版质量忍无可忍，于是&lt;strong&gt;自己花了近十年写了 TeX 排版系统&lt;/strong&gt;——全世界数学论文的排版标准，是这套书的副产品。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    TAOCP[&amp;#34;TAOCP 七卷规划&amp;lt;br/&amp;gt;1962 年动笔&amp;#34;] --&amp;gt; V1[&amp;#34;卷 1 基本算法 1968&amp;lt;br/&amp;gt;数学基础 + 数据结构&amp;#34;]
    TAOCP --&amp;gt; V2[&amp;#34;卷 2 半数值算法 1969&amp;lt;br/&amp;gt;随机数 + 高精度算术&amp;#34;]
    TAOCP --&amp;gt; V3[&amp;#34;卷 3 排序与查找 1973&amp;lt;br/&amp;gt;内排序 + 外排序 + 查找&amp;#34;]
    TAOCP --&amp;gt; V4[&amp;#34;卷 4 组合算法&amp;lt;br/&amp;gt;4A 2011 已出&amp;lt;br/&amp;gt;4B/4C 分册连载中&amp;#34;]
    TAOCP --&amp;gt; V5[&amp;#34;卷 5-7 规划中&amp;lt;br/&amp;gt;句法算法 / 语言理论等&amp;#34;]
    V1 -.-&amp;gt;|&amp;#34;排版质量事故&amp;#34;| TEX[&amp;#34;副产品: TeX&amp;lt;br/&amp;gt;全世界数学排版标准&amp;#34;]
    style TEX fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;为什么用-mixmmix-汇编而不是高级语言&#34;&gt;为什么用 MIX/MMIX 汇编，而不是高级语言&lt;/h3&gt;
&lt;p&gt;这是理解全书方法论的一把钥匙。书里所有算法都用一台假想计算机 &lt;strong&gt;MIX&lt;/strong&gt;（后来升级为 64 位 RISC 风格的 &lt;strong&gt;MMIX&lt;/strong&gt;）的机器语言写成。Knuth 给的理由，本质上是三条工程判断：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;要算性能账，就得有一台确定的机器&lt;/strong&gt;。算法分析的核心是精确计算执行次数和时间，而&amp;quot;这条语句花多少时间&amp;quot;在高级语言里是说不清的——编译器、运行时、指令集全在中间隔着。MIX 是一台指令集、时序完全定义好的机器，每条指令几个时钟周期白纸黑字，运行时间的分析因此可以&lt;strong&gt;精确到指令级&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机器语言不赶时髦&lt;/strong&gt;。1960 年代的高级语言换得飞快（Algol、Fortran、PL/I……），用任何一种写书都会很快过时；而一台假想的、设计得足够经典的机器，五十年不过时。事实证明他赌对了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逼你看清代价&lt;/strong&gt;。用汇编写，每一次访存、每一次循环、每一条跳转都暴露在纸面上——Knuth 认为程序员必须知道自己写的每一行代码让机器做了什么，这是&amp;quot;程序设计艺术&amp;quot;里&amp;quot;艺术&amp;quot;二字的底线。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代价也是显然的：阅读门槛被这道汇编抬得很高。这也是为什么本文后面会专门谈&amp;quot;怎么读&amp;quot;——这套书的正确打开方式不是从第一页硬啃。&lt;/p&gt;
&lt;p&gt;另一个体现 Knuth 写作态度的细节是&lt;strong&gt;勘误奖励&lt;/strong&gt;：他给每个在书中首次发现错误的人签发一张&amp;quot;十六进制一美元&amp;quot;的支票（0x$1.00 = 2.56 美元）。钱少到可笑，但这张支票在技术圈是荣誉勋章——它宣示的是一种工程文化：&lt;strong&gt;错误不是羞耻，藏着掖着才是；每一版书都比上一版更接近正确。&lt;/strong&gt; 半个世纪几十个印次，这套书可能是人类历史上被校对得最狠的技术著作。&lt;/p&gt;
&lt;p&gt;还有一个容易忽略的风格特征：几乎每一节结尾都有 &lt;strong&gt;&amp;ldquo;History and bibliography&amp;rdquo;（历史与文献）&lt;/strong&gt; 小节，把这个算法从最早的雏形到最新的改进逐年考证，作者、年份、原始论文一个不落。这让 TAOCP 同时是一部&lt;strong&gt;算法史的目录&lt;/strong&gt;——想知道某个思想的最早出处，查这套书通常比查搜索引擎可靠。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二卷-1基本算法算法的定义与数据结构的奠基&#34;&gt;二、卷 1《基本算法》：算法的定义与数据结构的奠基&lt;/h2&gt;
&lt;h3 id=&#34;算法是什么五个特征与欧几里得算法&#34;&gt;算法是什么：五个特征与欧几里得算法&lt;/h3&gt;
&lt;p&gt;卷 1 开篇就干了件&amp;quot;法典&amp;quot;该干的事：给&amp;quot;算法&amp;quot;下定义。Knuth 用**算法 E（欧几里得求最大公约数）**做标本，提炼出算法的五个特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有穷性（Finiteness）&lt;/strong&gt;：算法必须在有限步内终止。（Knuth 特别注明：只满足其余四条而不满足有穷性的，可以叫&amp;quot;计算方法&amp;quot;，比如某些永不终止但仍有用处的反应式过程。）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确切性（Definiteness）&lt;/strong&gt;：每一步都必须精确定义、无歧义——自然语言描述做不到这一点，这是书里坚持用形式化记号的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入（Input）&lt;/strong&gt;：有零个或多个输入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出（Output）&lt;/strong&gt;：有一个或多个输出，且与输入有确定关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能行性（Effectiveness）&lt;/strong&gt;：每一步都必须足够基本，原则上能用纸笔在有限时间内完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个定义的价值在于它立刻产生了区分能力：菜谱通常不满足确切性（&amp;ldquo;适量盐&amp;rdquo;），死循环程序不满足有穷性，而&amp;quot;掷骰子决定下一步&amp;quot;不满足确切性——都不是算法。&lt;strong&gt;定义不是用来背的，是用来划界的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;数学基础与-oωθ-的来历&#34;&gt;数学基础与 O/Ω/Θ 的来历&lt;/h3&gt;
&lt;p&gt;卷 1 第一章的数学部分（归纳法、数、幂与对数、和与积、排列、斐波那契、生成函数）常被读者跳过，但里面埋着全书的方法论核心：&lt;strong&gt;渐近分析（asymptotic analysis）&lt;/strong&gt;。几个史实值得澄清：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大 O 记号不是 Knuth 发明的——它来自数论学家 &lt;strong&gt;Bachmann&lt;/strong&gt;（1894 年），经 Landau 推广；&lt;/li&gt;
&lt;li&gt;Knuth 的贡献是把它引入算法分析，并在 1976 年专门写了一篇短文《Big Omicron and Big Omega and Big Theta》，&lt;strong&gt;推广了大 Ω（下界）和大 Θ（紧界）记号&lt;/strong&gt;，结束了当时文献里&amp;quot;什么都叫 O&amp;quot;的混乱局面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，今天我们写在面试白板上的 O(n log n) 这套语言，其语法规范是这套书（及配套论文）定下来的。Knuth 的态度始终如一：&lt;strong&gt;&amp;ldquo;大约 n 的平方量级&amp;quot;不是分析，&amp;ldquo;3n² + 5n - 7 次 MIX 指令执行&amp;quot;才是分析，O 记号是对前者的严格速写，不是含糊的挡箭牌。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;数据结构栈队列链表与树&#34;&gt;数据结构：栈、队列、链表与树&lt;/h3&gt;
&lt;p&gt;卷 1 第二章是整部书对后世影响最深的部分之一——今天教科书里的数据结构章节目录，几乎就是这一章的子孙：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性结构&lt;/strong&gt;：栈、队列、双端队列，以及链表（含循环链表、双向链表）的完整代数化描述——插入、删除、遍历的每一步都用 MIX 实现并计算代价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;树与二叉树&lt;/strong&gt;：形式化定义、三种遍历（先序/中序/后序）、&lt;strong&gt;线索二叉树（threaded tree）&lt;/strong&gt;——利用空指针存放前驱后继信息，让遍历不再需要栈。这是一个漂亮的&amp;quot;空间复用&amp;quot;思想实验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态存储分配与垃圾回收&lt;/strong&gt;：内存池的伙伴系统（buddy system）、首次适配/最佳适配策略、以及垃圾回收的标记思想——1968 年就成体系地写在书里，比多数编程语言拥有 GC 早了一代人。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：读这一章最大的震撼是&amp;quot;年代错位感&amp;rdquo;——我们以为的&amp;quot;现代概念&amp;rdquo;（垃圾回收、智能指针的引用计数思想、内存分配策略的权衡），全部写在 1968 年的书里，连权衡的框架都没变过。所谓新技术，多数是老思想在新硬件上的重排。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三卷-2半数值算法随机数与算得准的艺术&#34;&gt;三、卷 2《半数值算法》：随机数与&amp;quot;算得准&amp;quot;的艺术&lt;/h2&gt;
&lt;p&gt;卷 2 处理一个独特的中间地带：对象是数，但关心的是算法而非数值分析。两大主线是随机数和算术。&lt;/p&gt;
&lt;p&gt;先解释一下这个奇怪的书名：&lt;strong&gt;&amp;ldquo;半数值&amp;rdquo;（seminumerical）&lt;/strong&gt; 指的是这类算法介于&amp;quot;纯数值计算&amp;quot;（数值分析的地盘，关心连续数学与误差界）和&amp;quot;纯符号处理&amp;quot;（编译、树操作的地盘）之间——输入输出是数，但算法的骨架是离散的组合结构。随机数生成、进制转换、多精度算术都属于这一带。&lt;/p&gt;
&lt;h3 id=&#34;随机数不该用随机的方法生成&#34;&gt;随机数：不该用随机的方法生成&lt;/h3&gt;
&lt;p&gt;卷 2 第三章是全书被引用最多的章节之一，开篇那句名言每个工程师都该见过一次：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Random numbers should not be generated by a method chosen at random.&amp;quot;（随机数不该用一个随机选出来的方法生成。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Knuth 用亲身经历佐证这句话：他年轻时曾设计过一个&amp;quot;超级随机&amp;quot;的生成器——程序里随机地跳转、随机地执行不同操作，自以为复杂到不可能有规律；实际一跑，&lt;strong&gt;序列几乎立刻陷入一个短循环或不动点&lt;/strong&gt;。教训被写成定理级别的警告：复杂性不等于随机性，没有理论分析的&amp;quot;看起来乱&amp;quot;都是自欺欺人。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性同余法（LCG）&lt;/strong&gt;：x 的下一个值 = (a·x + c) mod m。全书用几十页分析三个参数怎么选——模数 m、乘数 a、增量 c 的选择决定了周期长度和统计质量，每一个坏选择的下场都有具体展示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统计检验&lt;/strong&gt;：卡方检验、Kolmogorov-Smirnov 检验、以及专门测高维均匀性的&lt;strong&gt;谱检验（spectral test）&lt;/strong&gt;——一个 LCG 可以在一维上完美均匀，在二维平面上却排成几条明显的直线，谱检验就是抓这种&amp;quot;隐藏结构&amp;quot;的。&lt;/li&gt;
&lt;li&gt;这一章的工程翻译到今天依然直接：&lt;strong&gt;不要用 &lt;code&gt;rand()&lt;/code&gt; 做密码学，不要自己发明随机数生成器，用之前先查它的检验记录。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;高精度算术与算得准&#34;&gt;高精度算术与&amp;quot;算得准&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;另一条主线是让计算机突破机器字长地算数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多精度算术&lt;/strong&gt;：加减乘的经典算法之外，重点是&lt;strong&gt;算法 D（多精度除法）&lt;/strong&gt;——除法是四则运算里唯一需要&amp;quot;试商&amp;quot;的，Knuth 给出完整的规范化、试商、校正流程和正确性证明。今天所有大整数库的除法实现，祖师爷都是这一节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进制转换&lt;/strong&gt;：二进制、十进制之间的转换算法及其代价分析——这个在当年是日常刚需。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有理数与浮点运算&lt;/strong&gt;：浮点数的表示、舍入误差分析——&amp;ldquo;浮点算术不是实数算术&amp;quot;这个每个工程师都会踩的坑，书里有完整的误差传播分析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多项式算术与幂的快速计算&lt;/strong&gt;：包括快速幂（平方-乘方法）的前身分析，以及乘法复杂度的下潜——Karatsuba 乘法这类&amp;quot;比课本算法快&amp;quot;的分治思路。更进一步是**加法链（addition chain）**问题：计算 x 的 n 次方最少需要几次乘法？&amp;ldquo;反复平方&amp;quot;并不总是最优，而求最优加法链本身至今没有完全解决——又一个&amp;quot;看似简单、实则研究级&amp;quot;的 50 分题栖息地。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：卷 2 是四卷里&amp;quot;最数学&amp;quot;的一卷，但它的工程含义一点不虚：凡是钱、坐标、时间戳参与计算的地方，都在和&amp;quot;算得准&amp;quot;打交道。浮点误差、大数溢出、随机数质量——这三类生产事故的完整理论都在这一卷里，早该被列为后端工程师的体检项目。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四卷-3排序与查找工程师的硬通货&#34;&gt;四、卷 3《排序与查找》：工程师的硬通货&lt;/h2&gt;
&lt;p&gt;四卷里工程密度最高、最值得当工具书常备的一卷。Knuth 把排序和查找这两个&amp;quot;最古老的生产问题&amp;quot;做到了考古学级别的穷尽。&lt;/p&gt;
&lt;h3 id=&#34;内排序一个家族的谱系&#34;&gt;内排序：一个家族的谱系&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    S[&amp;#34;内排序算法家族&amp;#34;] --&amp;gt; I[&amp;#34;插入类&amp;lt;br/&amp;gt;直接插入 / Shell 排序&amp;#34;]
    S --&amp;gt; E[&amp;#34;交换类&amp;lt;br/&amp;gt;冒泡 / 快速排序 Hoare 1962&amp;#34;]
    S --&amp;gt; SE[&amp;#34;选择类&amp;lt;br/&amp;gt;直接选择 / 堆排序 Williams-Floyd&amp;#34;]
    S --&amp;gt; M[&amp;#34;归并类&amp;lt;br/&amp;gt;二路归并 / 自然归并&amp;#34;]
    S --&amp;gt; D[&amp;#34;分布类&amp;lt;br/&amp;gt;计数 / 基数排序&amp;#34;]
    E -.-&amp;gt;|&amp;#34;平均最优&amp;lt;br/&amp;gt;原地性差&amp;#34;| NOTE[&amp;#34;各有适用场景&amp;lt;br/&amp;gt;没有全能冠军&amp;#34;]
    M -.-&amp;gt;|&amp;#34;稳定 + 可外排&amp;#34;| NOTE
    style NOTE fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书里对每一类的处理都是&amp;quot;描述 + 实现 + 精确分析&amp;quot;三段式，几个今天仍在生产环境活跃的名字：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速排序&lt;/strong&gt;（Hoare，1962）：Knuth 给出了平均比较次数的完整推导，以及最坏 O(n²) 的触发条件——&amp;ldquo;已排序输入配最左基准元&amp;quot;这个教科书反面案例就出自这里的分析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;堆排序&lt;/strong&gt;（Williams 提出堆，Floyd 改进）：最坏情况也是 O(n log n)，堆这个数据结构本身后来长成了优先级队列的标准实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;归并排序&lt;/strong&gt;：稳定、可预测，并且是通往外排序的桥。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读这一章时建议始终带着 Knuth 的三个评价维度去看每个算法：&lt;strong&gt;稳定性&lt;/strong&gt;（相等元素的相对次序是否保持）、&lt;strong&gt;原地性&lt;/strong&gt;（额外空间是常数还是随 n 增长）、&lt;strong&gt;适应性&lt;/strong&gt;（输入已接近有序时能否变快）。三个维度没有全能冠军——快排快在常数、堆排赢在最坏情况、归并赢在稳定和可外排——&lt;strong&gt;选型就是在这张三维表里做取舍&lt;/strong&gt;，这也是&amp;quot;没有银弹，只有权衡&amp;quot;在算法层的具体形态。&lt;/p&gt;
&lt;h3 id=&#34;最优排序n-log-n-下界的信息论论证&#34;&gt;最优排序：n log n 下界的信息论论证&lt;/h3&gt;
&lt;p&gt;卷 3 最优雅的一段数学是&lt;strong&gt;基于比较的排序的下界证明&lt;/strong&gt;，值得单独记：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;n 个元素&amp;lt;br/&amp;gt;共有 n! 种可能排列&amp;#34;] --&amp;gt; B[&amp;#34;比较排序 = 决策树&amp;lt;br/&amp;gt;每个内部节点是一次比较&amp;lt;br/&amp;gt;每片叶子对应一种排列&amp;#34;]
    B --&amp;gt; C[&amp;#34;树至少有 n! 片叶子&amp;#34;]
    C --&amp;gt; D[&amp;#34;高度至少为 log2(n!)&amp;#34;]
    D --&amp;gt; E[&amp;#34;Stirling 公式&amp;lt;br/&amp;gt;log2(n!) 约等于&amp;lt;br/&amp;gt;n log2 n - 1.44n&amp;#34;]
    E --&amp;gt; F[&amp;#34;结论:&amp;lt;br/&amp;gt;任何比较排序的最坏情况&amp;lt;br/&amp;gt;下界为 Ω(n log n)&amp;#34;]
    style F fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个论证的力量在于它&lt;strong&gt;不针对任何具体算法&lt;/strong&gt;——它限制的是&amp;quot;比较&amp;quot;这个信息获取方式本身：一次比较最多带来 1 比特信息，而区分 n! 种排列需要 log2(n!) 比特。要快过 n log n，唯一的出路是放弃纯比较——这正是基数排序、计数排序这些&amp;quot;分布类&amp;quot;算法存在的理由。这是我在全书中最喜欢的一段：&lt;strong&gt;用信息论给一类算法封顶，一句话堵死一个方向的无效努力。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;外排序与查找&#34;&gt;外排序与查找&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外排序&lt;/strong&gt;：当数据放不进内存，瓶颈从比较次数变成读写趟数。多路归并、**置换选择（replacement selection）**生成初始归并段、多相归并——这一整套是磁带时代的遗产，但思想直接活在今天的 LSM-tree（LevelDB/RocksDB 的多路归并 compaction）里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查找&lt;/strong&gt;：顺序查找与&lt;strong&gt;二分查找&lt;/strong&gt;（Knuth 照例指出：思路人人会，写对的没几个——边界条件的完整分析在书里；他还考据出二分查找的思路 1946 年就见诸文献，而第一个对所有输入规模都正确的完整算法直到 1962 年才发表，一个&amp;quot;显而易见&amp;quot;的算法花了十六年才写对）；&lt;strong&gt;哈希&lt;/strong&gt;：拉链法、开放寻址、线性探测的平均探查次数分析——线性探测的完整数学分析正是 Knuth 本人在 1962 年做出的，他自称正是这个问题让他决定写这套书；&lt;strong&gt;平衡树&lt;/strong&gt;：AVL 树的旋转与再平衡，多路查找树与 &lt;strong&gt;B 树&lt;/strong&gt;——数据库索引的物理形态；&lt;strong&gt;数字查找树（trie）&lt;/strong&gt;：按位/按字符逐级展开的查找结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五卷-4a组合算法生成一切的艺术&#34;&gt;五、卷 4A《组合算法》：生成一切的艺术&lt;/h2&gt;
&lt;p&gt;隔了 38 年，2011 年出的卷 4A 换了一个世界：从&amp;quot;算数&amp;quot;和&amp;quot;排序&amp;quot;进入&lt;strong&gt;组合对象&lt;/strong&gt;——布尔函数、组合、排列、划分——的领域。这卷的气质和前三卷一脉相承：先把对象定义到无歧义，再讨论&amp;quot;如何系统性地、不重不漏地枚举出全部实例&amp;rdquo;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;布尔函数与按位运算&lt;/strong&gt;（7.1 节）：真值表、按位技巧的宝库——掩码、并行按位计数（population count 的 sideways addition）、&amp;ldquo;x 与 x-1 的按位与可以抹掉最低位的 1&amp;quot;这类惯用法——这一节读起来像高级位操作黑魔法集，但每个技巧都有正确性论证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成所有组合对象&lt;/strong&gt;（7.2.1 节）：全 n 元组与 &lt;strong&gt;Gray 码&lt;/strong&gt;（相邻码字只差一位的编码，枚举时每次只翻转一位）、全排列、全组合、整数划分、集合划分、所有树——每类对象都给出&amp;quot;不重不漏、常数均摊时间&amp;quot;的生成算法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯思想&lt;/strong&gt;：组合生成的算法骨子里都是&amp;quot;受控的回溯&amp;rdquo;——按字典序推进、到头回退。系统化的回溯框架（Algorithm B）在卷 4B 的 7.2.2 节正式登场；顺带交代一句出版史：4A 出版后 Knuth 继续以分册形式推进，卷 4B 已于 2022 年正式成书，当年那个&amp;quot;卷 4 写了四十年&amp;quot;的段子至此翻篇了一半。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用一张图记住回溯的骨架——所有组合枚举问题都可以投影成同一棵树：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    R[&amp;#34;空解&amp;lt;br/&amp;gt;第 0 层&amp;#34;] --&amp;gt; A[&amp;#34;第 1 位选 a&amp;#34;]
    R --&amp;gt; B[&amp;#34;第 1 位选 b&amp;#34;]
    A --&amp;gt; A1[&amp;#34;第 2 位选 a&amp;lt;br/&amp;gt;...继续深入&amp;#34;]
    A --&amp;gt; A2[&amp;#34;第 2 位选 b&amp;#34;]
    A2 --&amp;gt;|&amp;#34;约束不满足&amp;lt;br/&amp;gt;剪枝回退&amp;#34;| X1[&amp;#34;放弃整棵子树&amp;#34;]
    A1 --&amp;gt; A1L[&amp;#34;到达叶子&amp;lt;br/&amp;gt;输出一个完整解&amp;#34;]
    B --&amp;gt; B1[&amp;#34;...同样的展开&amp;#34;]
    X1 -.-&amp;gt;|&amp;#34;回退到最近分叉&amp;lt;br/&amp;gt;试下一个分支&amp;#34;| A
    style X1 fill:#ffcccc
    style A1L fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;核心纪律只有两条：&lt;strong&gt;到底就输出，碰壁就回退，回退后试下一个未试过的分支&lt;/strong&gt;——不重不漏靠的就是这条纪律，而不是事后去重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：卷 4A 对今天的工程师有一个直接映射：&lt;strong&gt;所有&amp;quot;遍历所有可能配置&amp;quot;的需求&lt;/strong&gt;——测试用例生成、组合优化、穷举校验——都是组合生成问题，而&amp;quot;生成&amp;quot;远比&amp;quot;收集后再去重&amp;quot;高效。不重不漏四个字，说起来像小学要求，做起来是算法设计里最硬的纪律之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六怎么读这套书习题分级费马大定理与路线图&#34;&gt;六、怎么读这套书：习题分级、费马大定理与路线图&lt;/h2&gt;
&lt;h3 id=&#34;习题分级体系00-到-50-的登山标尺&#34;&gt;习题分级体系：00 到 50 的登山标尺&lt;/h3&gt;
&lt;p&gt;TAOCP 的习题是半本书——Knuth 给每道题标了难度分，这套编号体系本身就是个值得单独记住的设计：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;编号&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
					&lt;th&gt;预期投入&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;00&lt;/td&gt;
					&lt;td&gt;看一眼就该会&lt;/td&gt;
					&lt;td&gt;几秒钟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;简单&lt;/td&gt;
					&lt;td&gt;一分钟内&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;20&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;一刻钟上下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;30&lt;/td&gt;
					&lt;td&gt;较难&lt;/td&gt;
					&lt;td&gt;可能需要数小时&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;40&lt;/td&gt;
					&lt;td&gt;学期项目级&lt;/td&gt;
					&lt;td&gt;数周&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;50&lt;/td&gt;
					&lt;td&gt;研究级问题&lt;/td&gt;
					&lt;td&gt;出题时可能无人解决&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;编号前可加 M（数学向）或 HM（高等数学向）。最著名的梗是卷 1 里那道 &lt;strong&gt;[M50]&lt;/strong&gt;：题目是&amp;quot;证明费马大定理&amp;rdquo;——Knuth 的意思是&amp;quot;这道题出题时没人会做&amp;rdquo;。1995 年 Wiles 真的证明了它之后，后续印次里这道题的处境变成了书迷间的段子。&lt;strong&gt;50 分题不是作业，是路标：它告诉你学科边界在哪里。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;我的路线图建议&#34;&gt;我的路线图建议&lt;/h3&gt;
&lt;p&gt;对全职工程师，&amp;ldquo;通读&amp;quot;是个伪目标，务实的姿势是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;卷 3 当工具书常备&lt;/strong&gt;：排序和查找是唯一一卷会在日常代码评审、性能分析、系统设计面试中直接变现的。通读一遍，之后按需回查。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 1 读第二章&lt;/strong&gt;：数据结构部分是&amp;quot;为什么链表这样设计、为什么 GC 这样工作&amp;quot;的第一性原理来源；第一章数学基础按需查，不必顺序啃。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 2 按需精读&lt;/strong&gt;：写大数、随机、浮点相关代码之前，把对应章节当 pre-mortem 读一遍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 4A 当兴趣读物&lt;/strong&gt;：遇到组合枚举需求（生成测试组合、配置穷举）时按目录取用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;习题只做 20 分以下的&lt;/strong&gt;：10-20 分题保证理解，30 分以上当文献指南——题目本身常附带一整段文献史，不打算做题也值得读题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别按顺序读&lt;/strong&gt;。Knuth 自己也没指望人顺序读：每节相对独立，前置依赖写在节首。把这套书当成&amp;quot;算法领域的百科全书&amp;quot;而不是&amp;quot;课程&amp;rdquo;，是心态上最重要的一步。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七落到工程实践这套书改变了什么&#34;&gt;七、落到工程实践：这套书改变了什么&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;复杂度分析从&amp;quot;感觉&amp;quot;变成了&amp;quot;账&amp;quot;。&lt;/strong&gt; 读过 Knuth 的分析后，再说&amp;quot;这个操作是 O(n)&amp;ldquo;会本能地问：常数是什么？最坏情况和平均情况分别是多少？输入分布假设是什么？这套书最宝贵的遗产不是任何单个算法，而是**&amp;ldquo;性能是可以、也必须被精确计算的&amp;quot;这个信念**——profile 之前的估算、capacity planning 里的量级推演，全是它的日常形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;下界&amp;quot;的敬畏。&lt;/strong&gt; n log n 的信息论论证训练了一种思维方式：优化之前先问这个问题的理论下界在哪，离下界还有多远。很多&amp;quot;再优化一下&amp;quot;的努力，看一眼下界就知道是徒劳——&lt;strong&gt;知道哪里没有油水，和知道哪里有油水一样值钱。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机与浮点有了红线意识。&lt;/strong&gt; &amp;ldquo;不自己造随机数生成器&amp;quot;&amp;ldquo;钱不用浮点表示&amp;quot;这类团队规约，以前是被当经验口诀背下来的；读完卷 2 它们变成了有证明的结论，执行的腰杆完全不一样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读旧文献的胆子变大了。&lt;/strong&gt; Knuth 的行文示范了一件事：1960 年代的论文不是&amp;quot;过时的古董&amp;rdquo;，而是很多问题唯一的完整答案所在。排障和选型时敢往回翻原始论文，是这套书给的胆量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;正确性&amp;quot;的标准被永久抬高了。&lt;/strong&gt; 书里每个算法都带正确性证明或严格论证，读多了会形成一种肌肉记忆：写完一段非平凡逻辑，先问&amp;quot;循环不变式是什么&amp;quot;&amp;ldquo;边界情况枚举全了吗&amp;rdquo;。二分查找十六年才写对的考据，是这条标准最好的反面教材。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TAOCP 是算法学科的编年史加分析法典：七卷规划，卷 1-3 出版于 1968-1973，卷 4A 迟至 2011，副产品是 TeX；Gates 那句&amp;quot;读完发简历&amp;quot;说的是它的密度。&lt;/li&gt;
&lt;li&gt;MIX/MMIX 假想机不是老派怪癖，是方法论：要精确算性能账，就得有一台指令级确定的机器。&lt;/li&gt;
&lt;li&gt;卷 1 立规矩：算法五特征（有穷、确切、输入、输出、能行），数据结构大全，O/Ω/Θ 的语法规范（O 来自 Bachmann，Ω/Θ 由 Knuth 推广）。&lt;/li&gt;
&lt;li&gt;卷 2 管&amp;quot;算得准&amp;rdquo;：随机数不该用随机方法生成（LCG 参数分析、谱检验），多精度算术（算法 D），浮点误差。&lt;/li&gt;
&lt;li&gt;卷 3 是工程师硬通货：排序家族全谱、n log n 下界的信息论论证（比较即信息获取）、外排序思想活在今天的 LSM-tree 里、哈希与 B 树。&lt;/li&gt;
&lt;li&gt;卷 4A 进入组合世界：按位技巧、不重不漏地生成所有组合/排列/划分，回溯的思想雏形。&lt;/li&gt;
&lt;li&gt;读法：卷 3 常备、卷 1 读数据结构、卷 2/4A 按需精读；习题分级 00-50，[M50] 的费马大定理题是学科边界的路标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套书我没读完，大概率也永远不会读完——但这不妨碍它一直在工作：它就立在手边，像一个不说话的考官，每次想偷懒说&amp;quot;差不多 n log n 吧&amp;quot;的时候，抬头就能看见。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 1: &lt;em&gt;Fundamental Algorithms&lt;/em&gt; (3rd ed., Addison-Wesley, 1997; 初版 1968).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 2: &lt;em&gt;Seminumerical Algorithms&lt;/em&gt; (3rd ed., 1997; 初版 1969).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 3: &lt;em&gt;Sorting and Searching&lt;/em&gt; (2nd ed., 1998; 初版 1973).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 4A: &lt;em&gt;Combinatorial Algorithms, Part 1&lt;/em&gt; (Addison-Wesley, 2011).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &amp;ldquo;Big Omicron and Big Omega and Big Theta&amp;rdquo;, &lt;em&gt;ACM SIGACT News&lt;/em&gt;, 1976（O/Ω/Θ 记号规范化的原始短文）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
