[{"data":1,"prerenderedAt":144},["ShallowReactive",2],{"articles":3,"search-index":71},{"articles":4},[5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,63,67],{"slug":6,"title":7,"description":8},"tutorial\u002Fhydra-clio","Hydra 与 Clio 阅读笔记","Hydra 与 Clio 的物体关联、空间层次、导航和地图纠错机制，以及策略驱动拓扑图的可行性与限制。",{"slug":10,"title":11,"description":12},"demo\u002Ftypst-syntax","Typst 语法样板","用于验证博客 Typst 子集的样板：标题、段落、列表、代码块、表格和网格。",{"slug":14,"title":15,"description":16},"ai\u002Ftokenizer","Tokenizer 定义与实现","Tokenizer 的学习笔记：定义、基本概念与实现。",{"slug":18,"title":19,"description":20},"code\u002Fjavascript\u002Fnpm-pnpm","Node.js 包管理：从单个包到 Workspace","一份面向初学者的 npm 与 pnpm 学习笔记。",{"slug":22,"title":23,"description":24},"code\u002Frust\u002Fcargo","Cargo：从单个包到 Workspace","一份面向初学者的 Cargo 学习笔记。",{"slug":26,"title":27,"description":28},"tutorial\u002Fopensuse","OpenSUSE 折腾指南","得益于OpenSUSE的Yast，安装过程可以说是相当的舒服，全部图形化操作，是我在安装过许多Linux发行版中，最方便最舒服的。这里就不详细写出安装步骤了（其实是我没有记录过程），但会提供官方文档。",{"slug":30,"title":31,"description":32},"tutorial\u002Fwin-deepin-dualboot","Windows + Deepin 双系统的安装与使用","说实话我受够了Windows的环境搭建，想要精简就极度繁琐，想要方便就得用宇宙IDE，占十几二十多GB存储。因此，Linux就是最好的选择，除非你需要写Win或Mac的软件等特定情况。",{"slug":34,"title":35,"description":36},"tutorial\u002Fpicgo-smms","Obsidian 或 Typora + Picgo + smms 实现图片自动上传",">typora写作是很舒服，但是到了图片上传我简直太难了。上一篇博客我说我图床用的是imgchr，现在我屈服了，玩博客用imgchr简直是魔鬼好吧，图片上传完了，居然是乱序上传，导致我很难改图片地址。",{"slug":38,"title":39,"description":40},"tutorial\u002Fhexo-blog","github + hexo博客搭建教程","托更了很久，我一点不好意思都没有手动滑稽，我想应该没有人等着我的教程的吧。之所以这时才做这教程，是因为我准备开启新项目了，如果还不填坑的话，坑就越来越多，然后就不想填了。为了避免这种情况，所以我决定先",{"slug":42,"title":43,"description":44},"course\u002Fmodern_signal\u002Fbasic","现代信号处理基础","分类分类标准：连续 \u002F 离散周期 \u002F 非周期能量 \u002F 功率确定 \u002F 随机1D \u002F 2D \u002F 3D单通道 \u002F 多通道周期与非周期离散的函数不一定是周期函数例子: cos⁡(0.5πn) 是周期的; cos⁡(n) 是非周期的能量与功率能量信号",{"slug":46,"title":47,"description":48},"course\u002Fmodern_signal\u002Fclassic","经典信号处理","离散时间信号定义与联系𝑥[𝑛] 是离散时间信号，n 是整数索引。 𝑥(𝑡) 是连续时间信号，t 是连续变量。𝑥[𝑛]=𝑥(𝑛⋅𝑇𝑠) 其中 𝑇𝑠 是采样周期。代价：采样频率过低会导致混叠现象采样会损失采样点之间的连续",{"slug":50,"title":51,"description":52},"course\u002Fmodern_signal\u002Fhw1","现代信号处理 Homework 1","课程：现代信号处理（2026 Fall） 覆盖范围：Lecture 1–4提交要求：推荐使用 LaTeX 来完成作业，或者推导过程手写均可；如使用 AI 辅助，请在作业末尾披露使用了哪些环节、如何验证结果的正确性。最终请提交 pdf 文件。",{"slug":54,"title":55,"description":56},"course\u002Fmodern_signal\u002Fhw2","现代信号处理 Homework 2","课程：现代信号处理（2026 Fall） 覆盖范围：Lecture 5–6提交要求：第一部分需提交书面作业，最终提交 pdf 文件；第二部分为下节课随堂抽查准备，不要求书面提交。推荐使用 LaTeX 完成作业，也可手写推导；如使用 AI 辅",{"slug":58,"title":59,"description":60},"ai\u002Fdeep_learning","深度学习基础","",{"slug":62,"title":62,"description":60},"ai\u002Foptimizer",{"slug":64,"title":65,"description":66},"course\u002Fadvanced_ai\u002Fassignment1","1. Graph Search (36 marks)","Assignment 1Graph Search and Evolutionary ComputationTotal: 100 marks Deadline: 2026.10.30Instructions. Show intermediat",{"slug":68,"title":69,"description":70},"course\u002Fadvanced_ai\u002Fbasic_search","Basic Search","IntroductionStateAlgorithmsBFSDFSIDSA* SearchUCSUse CasesGraph Search8-Puzzle",{"articles":72},[73,75,77,83,91,96,99,101,104,107,110,113,119,124,133,137,141],{"slug":58,"title":59,"description":60,"plainText":60,"keywords":74},[],{"slug":62,"title":62,"description":60,"plainText":60,"keywords":76},[],{"slug":14,"title":15,"description":16,"plainText":78,"keywords":79},"基本概念Tokenizer 是连接原始文本与LLM 数值输入的编码系统，其基本任务是：文本 → Token 序列 → Token ID 序列 → LLM例如：\"unbelievable\" → [\"un\", \"believ\", \"able\"] → [412, 8271, 539]一个完整的 Tokenizer 通常包含：Vocabulary：Token ↔ ID 的映射；Tokenization Rules：文本如何被切分为 Token；Normalization：Unicode、空格等文本规范化规则；Special Tokens：如 \u003CBOS>、\u003CEOS>、\u003CPAD>；Decoder：Token 序列如何恢复为文本。因此，Tokenizer 并不只是一个映射表。tokenizer.json 只是将这些规则和数据序列化保存下来。为什么需要 TokenizerLLM 不能直接处理字符串，只能处理数值向量，因此首先需要把文本转换为离散 ID。最直接的两种粒度都有明显问题。字符 \u002F Byte 级例如：unbelievable → u n b e l i e v a b l e优点：词表很小；可以表示任意文本；几乎不存在 OOV（未知词）问题。缺点：序列很长；LLM 需要进行更多次计算。Word 级例如：unbelievable → [unbelievable]优点：序列较短。缺点：词表会非常大；unbelievable、unbelievably 等需要分别存储；无法良好处理新词、代码和多语言文本。因此现代 LLM 通常采用 Subword：unbelievable → un + believ + ableTokenizer 本质上是在：小词表 + 长序列 ↕ 大词表 + 短序列之间寻找合适的计算折中。Tokenizer 如何产生所谓“训练 Tokenizer”，通常不是神经网络意义上的梯度训练，而是：准备代表性语料 ↓ 确定预处理规则 ↓ 选择 Tokenizer 算法 ↓ 统计 \u002F 优化语料 ↓ Vocabulary + Rules ↓ 保存 Tokenizer例如选择：algorithm = BPE vocab_size = 32000算法会根据训练语料自动学习哪些文本片段值得成为独立 Token。因此可以将 Tokenizer 训练类比为：LLM: 数据 + 网络结构 + 优化算法 → Model Weights Tokenizer: 语料 + Tokenizer 算法 + 超参数 → Vocabulary + RulesTokenizer 一般在 LLM 正式训练前确定，并在训练和推理期间保持一致。Tokenizer 基本算法BPE（Byte Pair Encoding）BPE 的核心思想是：> 不断合并语料中最常共同出现的相邻片段。例如训练语料：low lower lowest初始拆分：l o w l o w e r l o w e s t统计相邻组合：(l, o) → 3 (o, w) → 3 (w, e) → 2 ...于是执行：l + o → lo得到：lo w lo w e r lo w e s t继续统计：lo + w → low最终可能得到：Vocabulary: l o w lo low er est ... Merge Rules: l o → lo lo w → low e r → er ...简化后的 BPE 训练算法：vocab = initial_units(corpus) while len(vocab) \u003C target_vocab_size: pairs = count_adjacent_pairs(corpus) a, b = most_frequent(pairs) new_token = a + b corpus = merge(corpus, a, b) vocab.add(new_token)因此 BPE 可以理解为一种根据语料自动学习压缩字典的方法。常见片段：the ing tion 人工 智能 return function更容易成为独立 Token。UnigramUnigram 的思路与 BPE 相反。BPE 从很小的词表开始：字符 \u002F Byte → 不断添加 TokenUnigram 通常从一个较大的候选词表开始：大量候选 Token → 不断删除价值较低的 Token每个 Token 都带有一个概率：Token Probability un 0.08 believe 0.03 able 0.05 ...对于：unbelievable可能存在多种切分：un + believable un + believe + able u + n + believe + ableUnigram 根据整条 Token 序列的概率选择较优方案：𝑃(𝑡1,…,𝑡𝑛)=∏𝑖𝑃(𝑡𝑖)训练过程中逐渐删除贡献较小的 Token，直到词表达到目标大小。其特点是：不依赖固定的 BPE Merge 顺序；同一字符串可以存在多个候选切分；可以通过概率模型选择更合适的 Token 序列。WordPieceWordPiece 与 BPE 类似，也通过 Subword 构建词表。区别主要在于 Token 的选择标准不同：BPE 主要根据相邻片段出现频率；WordPiece 更关注加入某个 Token 后对语言建模能力的改善。BERT 是典型的 WordPiece 使用者。例如：playing → play + ##ing其中 ## 表示该 Token 出现在单词内部。Tokenizer 的设计设计 Tokenizer 时主要需要决定以下内容：训练语料 ↓ 基础单位：Byte \u002F Character ↓ Normalization ↓ Pre-tokenization ↓ BPE \u002F Unigram \u002F WordPiece ↓ Vocabulary Size ↓ Special Tokens其中最重要的几个因素是：训练语料Tokenizer 会直接反映训练数据分布。如果大量数据是代码，可能学习：return const def :: ==如果大量数据是中文，则可能学习：中国 人工智能 模型 系统因此 Tokenizer 本身也是一种针对数据分布的编码设计。Vocabulary Size词表越大：文本 → 更少 Token但 Embedding 和输出层参数更多。对于隐藏维度 𝑑：𝑁embedding=𝑉×𝑑其中 𝑉 为词表大小。因此：Vocabulary ↑ Sequence Length ↓ Embedding Parameters ↑不存在无限增大词表的收益。评价指标Tokenizer 不应只看“能否编码文本”，还应关注：平均 Token 数；bytes \u002F token；中文、英文等不同语言的编码效率；Code Tokenization 效率；Vocabulary Size；对未知字符和异常输入的鲁棒性。Tokenizer 的局限传统 Tokenizer 有一个根本特点：> Token 的划分规则在 LLM 训练之前就已经固定。例如 BPE 可能始终将某个字符串编码为：un + believable而不会根据上下文动态改变计算粒度。它本身并不知道：当前 Token 的语义；当前任务是否困难；哪些区域值得更多计算；哪些区域可以被高度压缩。因此 Tokenizer 实际上人为规定了 LLM 的基础计算粒度。未来研究方向Learnable Tokenizer一种直接思路是：> 让一个神经网络决定文本应该如何分块。例如：Bytes ↓ Small Neural Network ↓ Dynamic Chunks ↓ Large Language Model与固定 BPE 不同，Chunk 可以根据上下文动态变化。简单区域可以使用较大的 Chunk：[The cat is sitting]复杂区域则使用更细粒度：[x] [9] [Q] [@] [7]此时 Tokenization 不再是独立的数据预处理，而成为模型的一部分。无固定 Tokenizer进一步可以完全取消传统 Vocabulary：UTF-8 Bytes ↓ Local Encoder ↓ Latent Patches ↓ Global Model其中：Local Model 处理字符、拼写等局部结构；Global Model 处理语义、知识与推理。核心思想从：如何设计一个更好的 Tokenizer？变成：模型应该如何自动决定自己的计算粒度？这类方法的优势包括：不依赖人工固定词表；无 OOV；对多语言更加自然；可以动态分配计算资源；Global Model 可以在更短的序列上工作。主要问题则集中在输出侧。层次化计算传统 LLM 基本在单一 Token 粒度上进行计算：token token token token ↓ Large Transformer但语言本身具有天然层次：Byte ↓ Character ↓ Subword ↓ Word ↓ Phrase ↓ Sentence ↓ Concept因此一种可能的未来架构是：Bytes ↓ Tiny Local Model ↓ Lexical Chunks ↓ Small Model ↓ Semantic Chunks ↓ Large Global Model低层模型负责：字符 拼写 局部语法高层模型负责：语义 知识 推理 规划其目的不是单纯“去掉 Tokenizer”，而是避免让昂贵的大模型对所有低层信息进行同等级计算。输出与 Diffusion无 Tokenizer 或 Latent 模型的一个核心困难是输出。传统自回归模型：token_1 → token_2 → token_3 → ...优点是质量高、因果关系明确，但必须逐步生成。Diffusion 可以同时更新多个位置：[MASK] [MASK] [MASK] [MASK] ↓ [word] [MASK] [word] [MASK] ↓ [word] [word] [word] [word]因此它有机会提高并行生成能力。但 Diffusion 也存在：需要多次 Denoising；每一步可能重新计算整段序列；输出长度处理更困难；更少 Denoising Step 通常意味着质量下降。因此目前的核心问题仍是：Generation Speed ↕ Generation Quality一种值得关注的结构是： Global Model ↓ Semantic Latent ↓ Local Diffusion Model ↓ Token \u002F Byte SequenceGlobal Model 负责低频、高层的推理；Local Decoder 负责高速展开具体语言。从 Tokenizer 到 Latent Thought更进一步，可以质疑另一个假设：> 推理本身是否必须以语言 Token 的形式进行？传统 Chain-of-Thought：内部表示 → 语言 Token → 内部表示 → 语言 Token → ...模型每进行一步推理，都必须把内部状态转换成人类语言。但人的思考体验往往并不完全表现为逐词生成：问题 ↓ 模糊的概念 \u002F 联想 \u002F 候选方案 ↓ 形成表达意图 ↓ 组织语言 ↓ 逐字、逐词说出因此可以考虑：Input ↓ Encoder ↓ Continuous Latent Thought ↓ Reasoning \u002F Planning ↓ Communicative Intent ↓ Language Decoder ↓ Text此时语言只是智能系统的一种输入输出形式，而不是推理本身。这种架构可能允许连续表示同时保留多个候选方案，而不必像 Token Generation 一样过早做离散选择。最终问题也从：如何设计 Tokenizer？逐渐演化为：模型应该使用什么内部表示进行思考？ 不同计算粒度之间应该如何组织？ 语言是否只是内部认知状态的一种表达形式？这已经从 Tokenizer 问题进一步扩展为 LLM 表示与推理架构问题。",[80,81,82],"tokenizer","bpe","llm",{"slug":18,"title":19,"description":20,"plainText":84,"keywords":85},"本文从 Node.js 项目为什么需要包管理器出发，依次介绍单个包、依赖、脚本、本地命令、锁文件和 Workspace。示例同时给出 npm 与 pnpm 的常用写法，但一个项目只应选择其中一个，并只提交对应的锁文件。本文以 Node.js 24 LTS、npm 11 和 pnpm 11 为基线。Node.js、npm 与 pnpm 分别发布，行为并不总是同步；遇到与本机不一致的地方，应先确认三者版本，再查看对应版本的官方文档。包管理器解决什么问题只有一个 JavaScript 文件时，可以直接运行 node index.js。前端项目变大后，还需要下载框架和构建工具、固定依赖版本、执行测试与打包命令，并协调多个应用和共享库。如果这些规则只存在于口头说明或全局环境中，其他人很难复现项目。npm 和 pnpm 将这些规则记录在项目中。它们主要负责：读取和更新 package.json；从 registry 解析并下载依赖；维护可复现安装所需的锁文件；将项目内工具暴露为可执行命令；运行 scripts 中约定的开发、测试和构建任务；管理包含多个 package 的 Workspace。包管理器并不是 JavaScript 编译器，也不会自行决定怎样处理 TypeScript、JSX、CSS 或图片。Vite、Rsbuild、TypeScript、ESLint 和测试框架等工具负责具体工作，npm 或 pnpm 负责安装并调用它们。这一点与同时负责依赖解析和 Rust 构建流程的 Cargo 不完全相同。六个基本概念Node.js 工具链经常同时出现 runtime、package、registry、dependency、script 和 workspace。它们描述不同层次。Node.js 是运行 JavaScript 和工具命令的 runtime。Node.js 通常附带 npm，但 pnpm 是独立发布的工具。Package 是由一个 package.json 描述的源码或发布单元。应用、组件库和命令行工具都可以是 package。Registry 是发布和下载 package 的服务。默认通常是 npm registry，但 package manager 与 registry 不是同一个事物。Dependency 是当前 package 声明需要的另一个 package；依赖还会继续拥有自己的间接依赖。Script 是 package.json 中命名的命令入口，例如 dev、build 和 test。Workspace 将多个 package 组织在一起，使安装、锁文件和批量命令可以统一管理。继续查阅：package.json 字段见 npm package.json；pnpm 与 npm 的设计差异见 pnpm vs npm。选择并固定包管理器npm 通常随 Node.js 一同安装。pnpm 可以由操作系统的包管理机制安装，也可以在 Corepack 可用时启用。Corepack 是否随 Node.js 提供取决于 Node.js 版本和发行方式：Node.js 25 起不再附带 Corepack，因此不要假定每台机器都有 corepack 命令。先查看本机实际版本：node --version npm --version pnpm --version团队项目应明确只使用一种 package manager。支持 packageManager 字段的工具还能据此识别项目期望的工具及精确版本：{ \"name\": \"my-project\", \"private\": true, \"packageManager\": \"pnpm@11.23.0\" }版本号只是示例，真实项目应填写团队实际验证过的版本。该字段是重要提示，但不是所有环境都会自动执行它；CI 和开发环境仍应显式提供相同版本。选择 npm 时提交 package-lock.json，选择 pnpm 时提交 pnpm-lock.yaml。不要在同一项目中交替运行两种工具并同时维护两份锁文件。继续查阅：Corepack 的版本范围和启用方式见 Node.js Corepack；pnpm 的安装方式见 pnpm Installation。管理单个 Package每个 Node.js package 都以 package.json 为配置入口。它描述包本身、依赖和命令，但源代码目录、构建入口与产物位置通常由具体工具决定，而不是由 npm 或 pnpm 统一规定。创建项目用 npm 在当前目录生成配置：mkdir my-project cd my-project npm init -y使用 pnpm 时：mkdir my-project cd my-project pnpm init-y 表示 npm 接受默认值。两条初始化命令都只创建 package.json，不会生成源码或测试；下面是需要继续手工创建的一种常见布局：my-project\u002F ├── package.json ├── src\u002F │ └── main.js └── test\u002F └── main.test.js先创建一个最小入口：console.log(\"Hello, Node.js!\");一个适合应用项目的最小 package.json 可以写成：{ \"name\": \"my-project\", \"version\": \"0.1.0\", \"private\": true, \"type\": \"module\", \"scripts\": { \"start\": \"node src\u002Fmain.js\", \"test\": \"node --test\" } }name 和 version 标识 package。private: true 防止意外发布应用。type: \"module\" 表示 .js 文件默认使用 ECMAScript Modules；如果省略，Node.js 对 .js 的默认解释会不同。scripts 保存项目命令。库项目若准备发布，需要进一步正确配置 exports、files、类型声明和发布内容；这些属于 package API 与发布设计，不能只靠删除 private 完成。运行项目命令执行脚本时，两种工具的基本形式相同：npm run start npm run testpnpm run start pnpm run teststart、test 等少数名称在 npm 中有快捷命令，但教程和团队脚本中统一写 run 通常更清楚。pnpm 在名称不与内置命令冲突时也允许省略 run：pnpm test向脚本后的程序传递参数时，用 -- 明确分隔 package manager 参数和程序参数：npm run start -- --port 3000 pnpm run start -- --port 3000开发、测试与生产构建Node.js package manager 没有 Cargo dev 和 release profile 的统一对应物。项目通过脚本名称表达任务，再由具体工具解释模式：{ \"scripts\": { \"dev\": \"vite\", \"build\": \"vite build\", \"preview\": \"vite preview\", \"test\": \"vitest run\", \"lint\": \"eslint .\", \"typecheck\": \"tsc --noEmit\" } }运行 npm run build 或 pnpm run build 只是调用本地 Vite。是否压缩、怎样拆包、输出到哪里，都由 Vite 配置决定。dist\u002F 是常见产物目录，但不是 npm 或 pnpm 的保证。继续查阅：脚本的精确行为见 npm Scripts和 pnpm run。具体构建选项应查看实际使用的构建工具文档。管理依赖依赖声明写在 package.json，锁文件记录解析后的完整依赖图，node_modules\u002F 则是安装结果。三者职责不同，不应手工把 node_modules\u002F 当作依赖清单。添加与移除依赖npm 使用 install 添加依赖：npm install react npm install --save-dev vite vitest npm uninstall reactpnpm 使用 add 添加依赖，裸 install 只安装清单中已有的依赖：pnpm add react pnpm add --save-dev vite vitest pnpm remove react--save-dev 可以缩写为 -D。命令会同时更新 package.json 和锁文件。生成结果大致如下：{ \"dependencies\": { \"react\": \"^19.0.0\" }, \"devDependencies\": { \"vite\": \"^7.0.0\", \"vitest\": \"^3.0.0\" } }上面的版本仅用于说明字段，不应复制为新项目的固定推荐。添加依赖时让 package manager 根据当前 registry 信息写入版本范围，再检查结果是否符合项目要求。依赖类型最常见的依赖字段有：dependencies：应用运行或库的公开实现正常工作所需的依赖；devDependencies：只用于开发、测试、检查和构建的工具；peerDependencies：库要求最终使用者同时提供的兼容依赖，例如组件库对 React 的要求；optionalDependencies：安装失败时允许继续的可选依赖，使用方必须处理它不存在的情况。应用使用的运行时库通常放在 dependencies，而 Vite、ESLint 和测试框架通常放在 devDependencies。不要因为构建工具会参与生成生产产物，就把它误判为应用运行时依赖。peerDependencies 主要用于发布库，不是减少应用安装项的通用手段。包管理器会如何安装和校验 peer dependency 与其版本有关；设计库的 peer 范围时，应按支持矩阵测试，而不是设置一个未经验证的宽范围。版本范围与实际版本package.json 中常见的 SemVer 范围不是精确锁定：^1.2.3 通常允许更新到兼容的 1.x 版本；~1.2.3 通常允许更新到 1.2.x；1.2.3 表示直接依赖要求该精确版本，但间接依赖仍由完整依赖图决定。package.json 表达允许范围，锁文件记录本次解析出的精确版本与来源。初学时应让添加命令生成常见范围；需要改变策略时再查询 SemVer，而不是随意删除 ^ 或把所有依赖写成 latest。锁文件与可复现安装npm 维护 package-lock.json，pnpm 维护 pnpm-lock.yaml。两者都应由 package manager 自动更新并提交到版本控制，通常不应手工编辑。本地开发安装：npm install pnpm installCI 中应要求清单与锁文件一致，并禁止静默改写锁文件：npm ci pnpm install --frozen-lockfilenpm ci 要求已有锁文件，会先移除现有 node_modules\u002F，不更新 package 清单或锁文件。pnpm 的 CI 默认行为可能随版本与环境变化，显式写出 --frozen-lockfile 更容易表达流水线意图。升级依赖是一次需要审查的项目变更。升级后应阅读锁文件 diff，并运行项目的测试、类型检查和构建，而不是只确认安装命令成功。npm 与 pnpm 的安装结构npm 通常会提升依赖，让许多 package 出现在根 node_modules\u002F。pnpm 使用内容寻址 store、硬链接和符号链接构造隔离的依赖图，项目根通常只直接暴露已声明的依赖。因此，一段代码在 npm 下可能偶然导入某个间接依赖，在 pnpm 下却报“找不到模块”。正确修复通常是把实际使用的 package 声明为当前 package 的直接依赖，而不是依赖 npm 的提升结果，也不是立即把 pnpm 改成完全平铺模式。node_modules\u002F 是可重新生成的安装产物，不应提交到版本控制。pnpm 的全局 store 也不是项目锁文件，不能代替 pnpm-lock.yaml。继续查阅：npm 安装见 npm install；pnpm 添加和安装见 pnpm add与 pnpm install；pnpm 的链接结构见 Symlinked node_modules structure。使用项目内工具把 CLI 工具安装到项目的 devDependencies，可以让团队和 CI 使用同一版本，而不是依赖每个人机器上不同的全局安装。脚本自动找到本地命令当脚本执行 vite、eslint 或 vitest 时，npm 和 pnpm 会将项目的可执行目录加入 PATH：{ \"devDependencies\": { \"eslint\": \"^9.0.0\" }, \"scripts\": { \"lint\": \"eslint .\" } }因此只需运行：npm run lint pnpm run lint不需要把 ESLint 全局安装，也不应在脚本里写某台机器上的绝对路径。直接执行命令需要临时调试脚本之外的命令时，可以使用：npm exec -- eslint --version pnpm exec eslint --versionpnpm exec 在项目环境中执行已安装依赖提供的命令。npm exec 会优先寻找本地命令，但本地不存在时还可以从 cache 或 registry 获取 package 后执行；它不是“仅本地执行”的可靠边界。若必须保证工具已经由项目声明，应先把它加入 devDependencies，再通过 scripts 调用。pnpm 在命令名不与内置命令冲突时还允许写成 pnpm eslint --version，但显式 exec 更容易区分 package manager 命令与依赖提供的命令。下载后一次性执行如果工具尚未加入项目，又只需执行一次，可以使用：npm exec --package create-vite -- create-vite my-app pnpm dlx create-vite my-appnpx 是 npm 提供的常用兼容入口，但它和 npm exec 的参数解析细节并不完全相同。文档和自动化脚本中优先使用语义明确的 npm exec。一次性执行仍会运行从 registry 获取的代码。应确认 package 名、版本和来源；对项目长期依赖的生成器或检查工具，更适合加入 devDependencies 并通过脚本调用。安装脚本与供应链边界依赖可能在安装期间声明 preinstall、install 或 postinstall 等生命周期脚本。npm 与 pnpm 不同版本对依赖构建脚本的默认策略并不相同；当前 pnpm 会要求显式批准部分依赖脚本，并提供 pnpm approve-builds 等机制。看到“build scripts were ignored”一类提示时，不应盲目允许全部脚本。先确认是哪个依赖、脚本为何必要，再按当前 pnpm 文档配置允许或忽略列表。CI 中应提交并审查这类策略，使开发机和流水线保持一致。继续查阅：命令执行见 npm exec、pnpm exec和 pnpm dlx；pnpm 构建脚本批准见 pnpm approve-builds。使用 Workspace当 Web 应用、组件库和内部工具分别成为 package 后，各自独立安装会产生多份锁文件和重复配置。Workspace 让 package manager 共同解析它们，并把本地 package 链接起来。Workspace 常用于 Monorepo，但两者不是同一个概念：Monorepo 描述多个项目存放在同一仓库，Workspace 描述 package manager 如何共同管理其中一组 package。虚拟项目结构下面的项目包含一个应用和两个共享 package：my-project\u002F ├── package.json ├── package-lock.json # 使用 npm 时存在 ├── pnpm-lock.yaml # 使用 pnpm 时存在 ├── pnpm-workspace.yaml # 使用 pnpm 时存在 ├── apps\u002F │ └── web\u002F │ ├── package.json │ └── src\u002Fmain.js └── packages\u002F ├── ui\u002F │ ├── package.json │ └── src\u002Findex.js └── config\u002F └── package.json图中同时列出两种工具的文件只是为了对照。真实项目应保留 package-lock.json，或者保留 pnpm-lock.yaml 与 pnpm-workspace.yaml，不能把两套配置混合使用。根 package 通常不发布，并保存跨成员运行的命令：{ \"name\": \"my-project\", \"private\": true, \"scripts\": { \"build\": \"echo run workspace builds\", \"test\": \"echo run workspace tests\" } }用 npm 声明 Workspacenpm 在根 package.json 中使用 workspaces：{ \"name\": \"my-project\", \"private\": true, \"workspaces\": [ \"apps\u002F*\", \"packages\u002F*\" ] }packages\u002Fui\u002Fpackage.json 声明自己的名称和版本：{ \"name\": \"@example\u002Fui\", \"version\": \"0.1.0\", \"type\": \"module\" }apps\u002Fweb\u002Fpackage.json 可以用满足本地 package 版本的普通 SemVer 范围引用它：{ \"name\": \"@example\u002Fweb\", \"private\": true, \"dependencies\": { \"@example\u002Fui\": \"^0.1.0\" } }npm 安装时会识别并链接匹配的 Workspace package。npm 不使用 pnpm 的 workspace: 协议，因此不要把 pnpm 示例原样复制到 npm 项目。在根目录安装全部成员：npm install给指定成员添加依赖：npm install react --workspace @example\u002Fweb运行单个成员或全部成员的脚本：npm run dev --workspace @example\u002Fweb npm run build --workspaces --if-present npm test --workspaces --if-present用 pnpm 声明 Workspacepnpm 使用根目录的 pnpm-workspace.yaml 选择成员：packages: - \"apps\u002F*\" - \"packages\u002F*\"根 package 总是属于 Workspace，即使 packages 没有显式包含根目录。成员 package 可以用 workspace: 协议要求依赖必须来自当前 Workspace：{ \"name\": \"@example\u002Fweb\", \"private\": true, \"dependencies\": { \"@example\u002Fui\": \"workspace:^\" } }workspace:^ 表示链接本地 package，并在发布时转换为相应的 caret 版本范围。应用通常不会发布，但显式协议仍能防止本地 package 不匹配时悄悄改从 registry 下载。在根目录安装全部成员：pnpm install给指定成员添加依赖：pnpm --filter @example\u002Fweb add react运行单个成员或递归运行脚本：pnpm --filter @example\u002Fweb run dev pnpm --recursive --if-present run build pnpm --recursive --if-present run test--filter 不只支持 package 名，还能按路径、依赖关系和变更范围选择 package。复杂过滤器应先阅读文档并用无副作用命令确认选择结果，避免对错误的一组成员批量修改依赖。共享依赖版本npm 与 pnpm 的 Workspace 都会共享根锁文件，但“锁文件中只有一个版本”并不意味着每个成员声明了相同范围。成员仍应只声明自己真实使用的依赖。pnpm 还提供 catalog，可以在 pnpm-workspace.yaml 中集中命名常用版本：packages: - \"apps\u002F*\" - \"packages\u002F*\" catalog: react: ^19.0.0 typescript: ^5.8.0成员使用 catalog: 引用：{ \"dependencies\": { \"react\": \"catalog:\" }, \"devDependencies\": { \"typescript\": \"catalog:\" } }Catalog 适合确实需要统一维护的版本，不应成为把所有依赖集中到根目录的理由。每个成员的 package.json 仍然是判断其直接依赖的入口。继续查阅：npm Workspace 见 npm Workspaces；pnpm Workspace、过滤和 catalog 见 pnpm Workspaces、Filtering和 Catalogs。在 CI 中安装与验证可靠的流水线应从仓库中的清单和锁文件重新安装，而不是复用开发机的 node_modules\u002F。最小流程通常是：# npm 项目 npm ci npm run lint npm test npm run build或者：# pnpm 项目 pnpm install --frozen-lockfile pnpm run lint pnpm test pnpm run buildWorkspace 可以改为前文的 --workspaces 或 --recursive 命令。流水线还应固定 Node.js 与 package manager 版本，并缓存 package manager 的下载缓存或 pnpm store，而不是把 node_modules\u002F 当作唯一缓存边界。安装成功只证明依赖图可以落盘，不证明源代码正确。至少应运行项目已经声明的类型检查、静态检查、测试和生产构建；库项目还应验证打包结果和公开 API。遇到问题时怎么查npm、pnpm、Node.js 和构建工具各有自己的版本。排错时先判断问题发生在哪一层，再进入对应文档，比反复删除锁文件更可靠。从版本与本机帮助开始node --version npm --version pnpm --version npm help install npm help run-script pnpm help install pnpm help recursive本机帮助与已安装 CLI 一致，适合确认参数名称。网上文章可能针对另一个大版本，复制命令前应先核对版本。观察依赖图npm 常用：npm ls npm explain react npm outdatedpnpm 常用：pnpm list pnpm why react pnpm outdatedls 或 list 查看安装图，explain 或 why 回答“为什么安装了这个 package”，outdated 查看可更新项。重复版本不必然是错误；先检查各成员和间接依赖的版本范围是否允许统一。按现象定位找不到模块：先确认实际导入它的 package 是否在自己的 package.json 中声明了直接依赖。锁文件不一致：使用项目指定的 package manager 更新依赖并提交锁文件，不要在 CI 中关闭冻结检查。npm 可以、pnpm 不行：检查是否依赖了被 npm 提升但未声明的 package，以及依赖安装脚本是否被 pnpm 拦截。脚本找不到命令：确认工具已加入当前 package 的依赖，并通过 run 或 exec 进入项目环境。Workspace 改错成员：确认当前目录和 --workspace 或 --filter 的实际选择范围。构建行为不一致：继续检查 Vite、TypeScript、测试框架等具体工具版本；package manager 只负责调用它们。删除 node_modules\u002F 后重新安装可以验证安装结果是否可重建，却不会自动修复错误的依赖声明。删除锁文件会重新选择整张依赖图，可能引入更多变化，不应作为没有分析的第一步。选择正确的官方资料npm CLI 文档：查询 npm 命令、package.json、锁文件和 Workspace；pnpm 文档：从 Motivation 了解设计，再进入 CLI、Settings 和 Workspace；Node.js API 文档：查询模块系统、runtime 行为和 Corepack；实际构建工具的官方文档：查询 dev、build、产物目录和配置文件。最后可以保留一个简单顺序：先确认 Node.js 与 package manager 版本，再阅读本机 help；依赖问题查看锁文件和依赖图，命令问题查看 scripts，构建问题进入真正执行构建的工具文档。这样无需把所有 CLI 参数背下来，也能知道下一步应去哪里验证。",[86,87,88,89,90],"npm","pnpm","node","workspace","包管理",{"slug":22,"title":23,"description":24,"plainText":92,"keywords":93},"本文从 Cargo 要解决的问题出发，依次介绍单个包、依赖、构建目标和 Workspace。示例只展示目录、命令与配置；其中的源文件只是占位，不包含具体 Rust 代码。本文以当前稳定版 Cargo 和 Rust 2024 Edition 为基线。Cargo 仍在演进；遇到与本机行为不一致的地方，应优先查看本机帮助和当前稳定版官方文档。为什么需要 Cargo只有一个源文件时，可以直接调用 rustc 编译。项目变大后，我们很快会遇到新的问题：源文件应该怎样摆放，第三方库从哪里下载，多个库应按什么顺序编译，开发构建和发布构建又该使用哪些参数？如果每次都手动处理，这些规则会散落在命令和脚本中，难以复现。Cargo 将这些规则集中到项目中。它主要负责：创建符合约定的 Rust 项目；下载并解析依赖；调用 Rust 编译器构建代码；运行程序和测试；管理包含多个包的 Workspace。四个基本概念Cargo 文档经常使用 package、crate、target 和 workspace。它们看起来相近，但描述的是不同层次。Package 是由一个 Cargo.toml 描述的源码集合。日常所说的“一个 Cargo 项目”，通常就是一个 package。Crate 是 Rust 编译器一次处理的代码单元。一个库或一个可执行程序都会被编译成 crate。Target 告诉 Cargo 从哪个入口编译出哪种 crate。常见 target 包括 library、binary 和 integration test。Workspace 将多个 package 组织在一起，让它们共享锁文件、构建目录和部分配置。一个 package 至少包含一个 crate；它最多有一个 library target，但可以有多个 binary target。Rust 的 module 是 crate 内部组织代码的方式，不属于 Cargo 的项目管理层次，本文不展开。继续查阅：概念不清楚时，可查看 Cargo 术语表和 Rust Book：Packages and Crates。管理单个包手动创建目录和编译命令很容易遗漏必要文件。Cargo 因而约定了一套默认布局，并提供命令生成它。只要项目遵守约定，大部分配置都不需要显式书写。创建项目在新目录中创建一个可执行程序：cargo new my-project在已经存在的目录中初始化项目：mkdir my-project cd my-project cargo init两种方式都会生成最小的 Cargo.toml 和源码入口。若要单独创建只提供库的 package，可以使用 cargo new \u003C目录> --lib。本文的 my-project 先保留可执行程序结构，其默认目录大致如下：my-project\u002F ├── Cargo.toml └── src\u002F └── main.rsCargo.toml 称为 manifest，即 package 的配置入口：[package] name = \"my-project\" version = \"0.1.0\" edition = \"2024\" [dependencies][package] 描述当前包。name 是包名，version 是当前包的版本，edition 决定采用哪一版 Rust 语言规则。[dependencies] 用来声明普通依赖；刚创建的项目没有依赖，因此它可以为空。检查、构建和运行编辑过程中，最常用的是 cargo check：cargo check它检查类型、借用等问题，但不生成最终机器码，因此通常比完整构建更快。链接或代码生成阶段的问题仍需通过 cargo build 发现。需要可执行产物时运行：cargo buildCargo 会先解析并构建依赖，再构建当前包。默认产物位于 target\u002Fdebug\u002F。如果只想立即运行程序，可以使用：cargo runcargo run 会在需要时先构建，再运行当前包的 binary target。若参数需要传给程序而不是 Cargo，用额外的 -- 分隔：cargo run -- --input sample.txt运行测试使用：cargo test它会构建并运行单元测试、集成测试和文档测试。测试代码通常还需要被测包之外的辅助依赖，这类依赖将在后文介绍。开发构建与发布构建开发时更在意编译速度和调试信息，交付时更在意运行速度。Cargo 用 profile 保存这两组构建策略。cargo build 和 cargo run 默认使用 dev profile。需要优化后的产物时使用：cargo build --release cargo run --releaserelease 产物位于 target\u002Frelease\u002F。cargo test 使用单独的 test profile，而不是简单复用 dev。初学阶段通常不必修改 profile；先选对 dev 或 release 即可。继续查阅：命令不熟悉时先运行 cargo help new、cargo help check 或 cargo help build。完整选项见 Cargo Commands；默认目录见 Package Layout。管理依赖项目使用第三方库时，手动下载源码会带来版本、更新顺序和间接依赖等问题。Cargo 允许在 manifest 中声明“需要什么”，再由解析器选择满足条件的一组具体版本。添加依赖推荐先使用命令添加依赖，而不是凭记忆编写配置：cargo add serde cargo add serde --features derive cargo add pretty_assertions --dev这些命令会更新 Cargo.toml。对应配置可能类似：[dependencies] serde = { version = \"1\", features = [\"derive\"] } [dev-dependencies] pretty_assertions = \"1\"[dependencies] 中的库参与正常构建。[dev-dependencies] 只供测试、示例和性能测试等开发目标使用，不会成为普通构建的一部分。命令行是常用入口，manifest 才是最终记录。修改后应阅读生成的配置，确认依赖类型、版本要求和 Feature 符合预期。版本要求不是精确版本依赖中的 version = \"1\" 不是“只能使用 1.0.0”，而是允许 Cargo 在兼容的 1.x 版本中选择。类似地，\"1.2.3\" 通常表示 >=1.2.3, \u003C2.0.0。初学时不必先记住所有版本运算符。更实用的做法是：让 cargo add 写入常见形式；需要精确控制时，再查询“version requirement”。随意使用精确等号会阻止兼容更新，也更容易造成依赖冲突。Feature 是可选能力一个依赖可能提供并非所有用户都需要的能力。Feature 让使用者按需开启它们：[dependencies] serde = { version = \"1\", features = [\"derive\"] }这里启用了 serde 的 derive Feature。Feature 名及其含义由依赖本身定义，不应猜测；应查看该 crate 的文档或其 Cargo.toml。临时选择当前包的 Feature 时，可以使用 --features、--all-features 或 --no-default-features。这些选项影响依赖解析，遇到复杂组合时应查阅 Cargo 的 Features 参考，而不是不断试错。Cargo.lock 记录实际选择Cargo.toml 描述允许的版本范围，Cargo.lock 记录本次解析实际选择的每个依赖版本。它由 Cargo 自动维护，不应手工编辑。对于应用和 Workspace，通常应将 Cargo.lock 提交到版本控制，以便不同机器得到一致的依赖组合。锁文件不会强迫使用库的下游项目采用同一组版本；如果不确定是否提交，Cargo 官方目前的建议也是提交。需要主动更新锁定版本时使用：cargo update cargo update -p serde继续查阅：依赖来源和版本规则见 Specifying Dependencies；Feature 见 Features；锁文件见 Cargo.toml vs Cargo.lock。组织项目目标一个程序常常既要提供可复用逻辑，又要提供命令行入口和外部测试。如果把它们都当成独立项目维护，会产生重复配置。Cargo 用 target 表示同一 package 中不同的编译入口。默认目录约定下面是一个只展示职责、不包含真实代码的虚拟项目：my-project\u002F ├── Cargo.toml ├── src\u002F │ ├── lib.rs # library target │ ├── main.rs # 默认 binary target │ └── bin\u002F │ └── admin.rs # 额外的 binary target └── tests\u002F └── cli.rs # integration test targetCargo 会自动发现这些入口：src\u002Flib.rs 是唯一的 library target；src\u002Fmain.rs 是与 package 同名的 binary target；src\u002Fbin\u002F 下的文件是额外的 binary target；tests\u002F 下每个文件都是独立的 integration test target。集成测试从 package 外部使用 library 的公开接口，因此通常需要 src\u002Flib.rs。如果项目只有 binary 而没有可复用库，仍然可以测试 binary，但组织方式和可访问接口会有所不同。运行指定 target 时，可以显式选择名称：cargo run --bin admin cargo test --test cli何时显式配置 target使用默认目录时，无需在 Cargo.toml 中重复声明 target。只有在入口路径或名称不符合默认约定时，才需要配置：[lib] name = \"shared\" path = \"src\u002Fshared.rs\" [[bin]] name = \"app\" path = \"src\u002Fentry.rs\" [[test]] name = \"cli\" path = \"tests\u002Fcommand.rs\"[lib] 只能出现一次；[[bin]] 和 [[test]] 是数组表，可以重复出现。显式配置很灵活，但也增加了读者需要维护的信息。能使用默认布局时，应优先依靠约定。继续查阅：目录自动发现和全部 target 类型见 Cargo Targets。不确定命令选择了哪些 target 时，查看相应命令帮助中的 “Target Selection”。使用 Cargo Workspace当 app、shared 和内部工具分别成为 package 后，每个包都有自己的 manifest。若它们各自维护版本、依赖和构建目录，配置会重复，包之间也难以保持一致。Workspace 用一个根 manifest 描述它们的关系。Cargo Workspace 常用于 Monorepo，但两者不是同一个概念：Monorepo 描述代码存放方式，Workspace 描述 Cargo 如何共同管理一组 package。虚拟项目结构先将原项目的 manifest、src\u002F 和 tests\u002F 移入 app\u002F，再新增共享库与内部工具，得到下面的虚拟 Workspace：my-project\u002F ├── Cargo.toml # Workspace 根配置 ├── Cargo.lock # 全体成员共享 ├── target\u002F # 全体成员共享 ├── app\u002F │ ├── Cargo.toml │ ├── src\u002Fmain.rs │ └── tests\u002Fcli.rs ├── shared\u002F │ ├── Cargo.toml │ └── src\u002Flib.rs └── tools\u002F ├── Cargo.toml └── src\u002Fmain.rsapp 是用户运行的程序，shared 是可复用库，tools 是内部辅助工具。测试仍是 app 的 target，不必为了“测试”再创建一个 package。声明 Workspace根目录的 Cargo.toml 不含 [package]，因此它是一个虚拟 Workspace：[workspace] members = [\"app\", \"shared\", \"tools\"] default-members = [\"app\"] resolver = \"3\" [workspace.package] version = \"0.1.0\" edition = \"2024\" [workspace.dependencies] shared = { path = \"shared\" } serde = { version = \"1\", features = [\"derive\"] }members 列出 Workspace 成员。default-members 表示在根目录运行命令且没有显式选择 package 时，默认操作 app。虚拟 Workspace 没有根 package，因此若不设置 default-members，默认会操作全部成员。resolver = \"3\" 采用与 Edition 2024 对应的依赖解析规则。虚拟 Workspace 自身没有 package edition，不能从 [package] 推断 resolver，所以应显式写出。[workspace.package] 和 [workspace.dependencies] 保存成员可以继承的公共值；仅仅写在这里还不会自动应用到每个成员。在成员中继承配置app\u002FCargo.toml 可以这样引用公共配置：[package] name = \"app\" version.workspace = true edition.workspace = true [dependencies] shared.workspace = true serde.workspace = true [dev-dependencies] pretty_assertions = \"1\"version.workspace = true 表示从 [workspace.package] 继承版本；shared.workspace = true 表示从 [workspace.dependencies] 继承依赖声明。这样可以集中维护公共版本和路径，同时让每个成员清楚声明自己实际使用了哪些依赖。shared\u002FCargo.toml 和 tools\u002FCargo.toml 也只继承各自需要的值：# shared\u002FCargo.toml [package] name = \"shared\" version.workspace = true edition.workspace = true [dependencies] serde.workspace = true# tools\u002FCargo.toml [package] name = \"tools\" version.workspace = true edition.workspace = true [dependencies] shared.workspace = true路径依赖中的 path 相对于声明它的 manifest。上例的 shared = { path = \"shared\" } 位于根 manifest，因此指向根目录下的 shared\u002F。选择要操作的包在 Workspace 根目录执行：cargo check由于设置了 default-members = [\"app\"]，Cargo 默认检查 app，并按依赖关系检查它需要的 shared。需要操作全部成员时使用：cargo check --workspace cargo test --workspace只操作某个 package 时使用 -p，它是 --package 的缩写：cargo check -p shared cargo run -p app cargo run -p toolsWorkspace 全体成员共享根目录下的 Cargo.lock 和 target\u002F。[profile.*] 等构建配置也只从 Workspace 根 manifest 读取；把 profile 写进成员 manifest 不会产生预期效果。继续查阅：Workspace 字段、继承范围和默认成员规则见 Workspaces。命令选择规则见任一 Cargo 命令页面中的 “Package Selection”。遇到问题时怎么查Cargo 的命令和配置很多，没有必要全部记住。更稳定的方法是先判断问题属于“命令怎么用”“某个字段是什么意思”，还是“为什么会选择这个依赖”，再去对应入口查找。从本机帮助开始查看 Cargo 总帮助：cargo help查看具体命令：cargo help build cargo help test cargo run --help本机帮助与当前安装版本一致，适合确认参数名称、默认行为和 package\u002Ftarget 选择选项。查看依赖关系当依赖版本或 Feature 与预期不同时，先观察 Cargo 实际解析出的图：cargo tree cargo tree -i serde cargo tree -e featurescargo tree 显示依赖树；-i 反向查找“谁依赖了它”；-e features 显示 Feature 如何被启用。看到重复依赖并不必然表示错误，应先检查版本要求是否允许统一。选择正确的官方资料The Cargo Book 是 Cargo 官方文档的总入口，其中不同部分解决不同问题：Guide 适合第一次了解项目布局、依赖和测试流程；Reference 适合确认 Cargo.toml 字段、版本规则、Feature 和 Workspace 的精确语义；Commands 适合查询某条命令的全部参数；Glossary 适合核对 package、crate、target 等术语。官方还提供 Cargo Book 中文版。当译文和当前工具行为不一致时，以本机帮助和英文稳定版为准。不要把 Cargo Reference 与 The Rust Reference 混淆：后者描述 Rust 语言本身，而不是 Cargo。最后可以保留一个简单的查询顺序：先运行 cargo help \u003Ccommand>，再阅读 Cargo Book 的 Guide；需要精确配置时进入 Reference，并搜索 manifest 中真实出现的字段名。这样无需背下整本手册，也能在需求变化时找到可靠答案。",[94,95,89,90],"cargo","rust",{"slug":64,"title":65,"description":66,"plainText":97,"keywords":98},"Assignment 1Graph Search and Evolutionary ComputationTotal: 100 marks Deadline: 2026.10.30Instructions. Show intermediate steps whenever a derivation is requested. All graphs are undirected, and each edge label denotes its travel cost. Do not assume an edge exists unless it is drawn. For a tour, returning to the start is compulsory.1. Graph Search (36 marks)Consider the routing network in Figure 1. Starting at 𝑆, find a route to the goal 𝐺. For each algorithm, give (i) the order in which vertices are visited\u002Fexpanded, (ii) the solution route, and (iii) its total cost.图 1 Undirected routing network. Edge labels are travel costs.Rules for this question: Vertices are ordered alphabetically when choices are tied. BFS marks a vertex as discovered when it is enqueued; DFS uses recursive traversal and chooses the alphabetically first unvisited neighbour; UCS expands the vertex with the smallest current path cost (alphabetical order resolves equal-cost ties). For UCS, keep the first predecessor when an alternative has exactly the same cost. Stop when 𝐺 is removed from the frontier for BFS\u002FUCS, or first visited for DFS.1.1. Breadth-First Search (BFS) — 12 marksApply Breadth-First Search (BFS). Show the visited\u002Fexpansion order, the final route, and its cost.Visited\u002Fexpansion order: 𝑆,𝐴,𝐵,𝐶,𝐷,𝐹,𝐸,𝐺Final route: 𝑆→𝐴→𝐷→𝐺Total cost: 4+8+17=291.2. Depth-First Search (DFS) — 12 marksApply Depth-First Search (DFS). Show the visited order, the final route, and its cost.Visited order: 𝑆,𝐴,𝐵,𝐹,𝐷,𝐺Final route: 𝑆→𝐴→𝐵→𝐹→𝐷→𝐺Total cost: 4+3+7+3+17=341.3. Uniform-Cost Search (UCS) — 12 marksApply Uniform-Cost Search (UCS). Show the expansion order and the cumulative cost at each expansion, then give the final route and its cost.Expansion order and cumulative costs: 𝑆(0), 𝐵(2), 𝐴(4), 𝐶(5), 𝐸(9), 𝐹(9), 𝐷(12), 𝐺(17) — the tie at 9 is broken alphabetically, so 𝐸 precedes 𝐹.Final route: 𝑆→𝐵→𝐹→𝐺Total cost: 2+7+8=172. Travelling Salesman Problem (30 marks)Figure 2 shows a six-city travelling salesman problem (TSP). A valid tour starts at 𝑂, visits each of 𝐴,𝐵,𝐶,𝐷,𝐸 exactly once, and returns to 𝑂. Only the displayed roads may be used.图 2 TSP road network with six cities and the available road distances.2.1. Tour encoding — 5 marksHow would you encode a candidate tour as an individual in an evolutionary algorithm?Permutation encoding: an individual is an ordering of the five non-start cities, e.g. (𝐴,𝐶,𝐸,𝐷,𝐵). The endpoint 𝑂 is fixed, so it is not stored in the chromosome and is added when the tour is evaluated: 𝑂→𝐴→𝐶→𝐸→𝐷→𝐵→𝑂. Every city occurs exactly once.2.2. Fitness function — 5 marksPropose an appropriate fitness function for minimizing tour length. Explain how invalid tours are handled, and calculate the length and fitness of the tour O → A → C → E → D → B → O.Fitness function: 𝑓(𝑃)=11+𝐿(𝑃), where 𝐿(𝑃) is the total length of tour 𝑃, so a shorter tour scores higher.Handling invalid tours: a tour that repeats a city, omits a city, or uses a non-existent road is infeasible and gets 𝑓=0.Tour length and fitness: 𝐿=6+8+9+7+10+5=45, hence 𝑓=146≈0.0217.2.3. Crossover operator — 10 marksDesign a suitable crossover operator. Explain how it handles the “visit each city once” constraint and the possibility of missing roads.Operator and procedure: order crossover (OX). Copy a randomly selected segment from P1 into the child. Starting immediately after the second crossover point, scan P2 cyclically and fill the remaining positions cyclically, skipping cities already present.Visit-once constraint: holds by construction — each city is copied at most once and the scan fills every remaining slot, so none is duplicated or lost.Missing roads: the child may use a road that does not exist. Repair it by swapping the offending city with another position and re-checking the whole tour, repeating for a bounded number of attempts; a child that cannot be repaired is discarded and reseeded.2.4. Mutation operator — 10 marksDesign a suitable mutation operator. Explain why it is appropriate for this representation and how you handle missing roads.Operator and procedure: swap mutation — pick two positions at random and exchange the cities in them.Why it is appropriate: it preserves the permutation (no city is duplicated or lost), never touches the fixed 𝑂, and changes at most four edges, so it explores locally.Missing roads: a swap can create up to four new edges, so check the whole mutated tour, not just the swapped pair. Retry with another pair until the tour is feasible, or give up and set 𝑓=0 \u002F discard the mutant.3. Crossover in the Adjacency Representation (34 marks)Consider the following two parent tours (the closing edge back to city 1 is implicit):𝑃1=(1,9,8,7,5,4,2,6,3),𝑃2=(1,8,6,7,9,3,2,4,5).All tours are cyclic. For edge recombination, each city’s adjacency list contains its two neighbours from each parent, with duplicates removed; these are undirected edges.3.1. Edge Recombination Crossover (ER) — 16 marksApply Edge Recombination Crossover (ER) starting from city 1. After selecting a city, delete it from all adjacency lists. Choose the next city among the unvisited neighbours of the current city that has the fewest remaining neighbours. Break any ties by choosing the numerically smallest city. If no unvisited parental neighbour remains, choose the numerically smallest unvisited city. Show the updated choices at each step and give the final cyclic tour.Initial adjacency lists: 1:{3,5,8,9}, 2:{3,4,6}, 3:{1,2,6,9}, 4:{2,5},5:{1,4,7}, 6:{2,3,7,8}, 7:{5,6,8,9}, 8:{1,6,7,9}, 9:{1,3,7,8}.Updated choices at each step: candidates are the current city’s unvisited neighbours, with their remaining neighbour count in brackets.StepCurrentCandidatesChosen113 (3), 5 (2), 8 (3), 9 (3)5254 (1), 7 (3)4342 (2)2423 (2), 6 (3)3536 (2), 9 (2)6667 (2), 8 (2)7778 (1), 9 (1)8889 (0)9Final cyclic tour: 1→5→4→2→3→6→7→8→9→13.2. Alternate-Edges Crossover (AEX) — 10 marksApply Alternate-Edges Crossover (AEX) starting at city 1, taking the directed successor from 𝑃2 first, then 𝑃1, then 𝑃2, etc. Read successors cyclically in the parents’ written order. If the proposed successor has already been visited, instead choose the numerically smallest unvisited city, then continue alternating parents as usual. Show the chosen parent, proposed successor, and actual next city at every step. Give the final cyclic tour.Chosen parent \u002F proposed successor \u002F actual next city at each step:StepParentCurrentProposedActual1𝑃21882𝑃18773𝑃27994𝑃19825𝑃22446𝑃14237𝑃23258𝑃1546In steps 4, 6, 7 and 8 the proposed successor was already visited, so the smallest unvisited city was taken; the parent alternation continues unchanged.Final cyclic tour: 1→8→7→9→2→4→3→5→6→13.3. ER vs. AEX — 8 marksCompare ER and AEX for these parents by counting how many undirected edges of each offspring (including its closing edge) occur in at least one parent. Explain an advantage of ER and one limitation of this comparison.ER parental-edge count: 9 of 9 — (1,5), (4,5), (2,4), (2,3), (3,6), (6,7), (7,8), (8,9), (1,9) all occur in at least one parent.AEX parental-edge count: 4 of 9 — only (1,8), (7,8), (7,9), (2,4) are parental; (2,9), (3,4), (3,5), (5,6), (1,6) are not.Advantage of ER: it builds the child from the parents’ adjacency information, so ER prioritizes parental adjacency relationships, preserving useful structural building blocks. In this particular example, it successfully preserves all nine parental edges.Limitation of this comparison: the count measures structure, not fitness. No road costs are given here, so a child with fewer parental edges may still be the shorter tour; the comparison alone cannot rank the two offspring.End of Assignment 1",[],{"slug":68,"title":69,"description":70,"plainText":70,"keywords":100},[],{"slug":42,"title":43,"description":44,"plainText":102,"keywords":103},"分类分类标准：连续 \u002F 离散周期 \u002F 非周期能量 \u002F 功率确定 \u002F 随机1D \u002F 2D \u002F 3D单通道 \u002F 多通道周期与非周期离散的函数不一定是周期函数例子: cos⁡(0.5πn) 是周期的; cos⁡(n) 是非周期的能量与功率能量信号: 能量为有限的，平均功率为零的信号功率信号: 能量为无限的，平均功率为有限的信号确定与随机确定信号: 任意时刻下，取值确定不变的信号随机信号: 任意时刻下，取值不可预知的信号信号获取流程传感器: 物理量转化为电信号模数转化: 模拟信号转化为数字信号采样: 时间离散化量化: 幅值离散化编码: 二进制表示奈奎斯特准则采样频率需要大于2倍的信号频率, 否则会出现混叠",[],{"slug":46,"title":47,"description":48,"plainText":105,"keywords":106},"离散时间信号定义与联系𝑥[𝑛] 是离散时间信号，n 是整数索引。 𝑥(𝑡) 是连续时间信号，t 是连续变量。𝑥[𝑛]=𝑥(𝑛⋅𝑇𝑠) 其中 𝑇𝑠 是采样周期。代价：采样频率过低会导致混叠现象采样会损失采样点之间的连续信息基本离散信号单位冲激信号𝛿[𝑛]={1𝑛=00𝑛≠0单位阶跃信号𝑢[𝑛]={1𝑛≥00𝑛\u003C0单位冲激信号与单位阶跃信号的关系：𝛿[𝑛]=𝑢[𝑛]−𝑢[𝑛−1]正弦序列𝑥[𝑛]=𝐴⋅cos(𝜔𝑛+𝜑)𝐴: 幅值𝜔: 角频率𝜑: 初相位周期性充要条件：𝜔=2𝜋𝑘𝑁𝑘∈𝑍实指数序列𝑥[𝑛]=𝐴⋅𝑎𝑛𝑎∈𝑅|𝑎|>1: 指数增长|𝑎|\u003C1: 指数衰减𝑎\u003C0: 正负交替变化复指数序列𝑥[𝑛]=𝑒(𝑗𝜔+𝜑)𝑛=𝑒𝜑𝑛⋅𝑒𝑗𝜔𝑛=cos(𝜔𝑛)+𝑗sin(𝜔𝑛)欧拉公式：cos(𝜔𝑛)=𝑒𝑗𝜔𝑛+𝑒−𝑗𝜔𝑛2离散时间信号的运算移位𝑥[𝑛−𝑘]： 向右平移 k 个单位，过去𝑥[𝑛+𝑘]： 向左平移 k 个单位，未来翻转𝑥[−𝑛]： 关于 n=0 翻转缩放𝑥[𝑀𝑛]：相当于使用𝑀𝑛 代替 n 作为索引，重新采样信号当 𝑀>1 时，信号少采样，信号被压缩当 0\u003C𝑀\u003C1 时，信号插值，信号被拉伸信号的分解𝑥[𝑛]=∑𝑥[𝑘]⋅𝛿[𝑛−𝑘]离散时间系统性质线性：满足叠加原理时不变：系统的特性不随时间变化因果：系统的输出仅依赖于当前及过去的输入稳定：有界输入产生有界输出线性𝑇{𝑎𝑥1[𝑛]+𝑏𝑥2[𝑛]}=𝑎⋅𝑇{𝑥1[𝑛]}+𝑏⋅𝑇{𝑥2[𝑛]},∧𝑇{0}=0时不变（移不变）𝑇{𝑥[𝑛−𝑘]}=𝑦[𝑛−𝑘]因果𝑦[𝑛]=𝑇{𝑥[𝑛]}=𝑓(𝑥[𝑛],𝑥[𝑛−1],𝑥[𝑛−2],…)稳定𝑦[𝑛]=𝑇{𝑥[𝑛]},∧|𝑥[𝑛]|\u003C∞∧|𝑦[𝑛]|\u003C∞线性移不变系统ℎ[𝑛]=𝑇{𝛿[𝑛]}卷积𝑦[𝑛]=∑𝑥[𝑛]⋅ℎ[𝑛−𝑘]=𝑥[𝑛]∗ℎ[𝑛]",[],{"slug":50,"title":51,"description":52,"plainText":108,"keywords":109},"课程：现代信号处理（2026 Fall） 覆盖范围：Lecture 1–4提交要求：推荐使用 LaTeX 来完成作业，或者推导过程手写均可；如使用 AI 辅助，请在作业末尾披露使用了哪些环节、如何验证结果的正确性。最终请提交 pdf 文件。题一 求移动平均系统的单位抽样响应 ℎ[𝑛]已知移动平均系统 𝑦[𝑛]=𝑥[𝑛]+𝑥[𝑛−1]2。求该系统的单位抽样响应 ℎ[𝑛]，并写出其非零点的位置与取值；由 ℎ[𝑛] 判断该系统是否因果、是否 BIBO 稳定，说明依据；该系统是 FIR 还是 IIR？从差分方程的形式和 ℎ[𝑛] 的长度两个角度分别说明。答：解：根据定义可知：当输入为单位抽样信号 𝑥[𝑛]=𝛿[𝑛] 时，系统的输出即为单位抽样响应 ℎ[𝑛]。因此：ℎ[𝑛]=12(𝛿[𝑛]+𝛿[𝑛−1])当且仅当 𝑛=0 或 𝑛=1 时，ℎ[𝑛] 非零，且取值为 12。解：根据因果性定义的定义可知：当单位抽样响应为 ℎ[𝑛]=𝑓(𝑥[𝑛],𝑥[𝑛−1],𝑥[𝑛−2],…) 时，系统是因果的。由于 ℎ[𝑛]=12(𝛿[𝑛]+𝛿[𝑛−1])，因此系统是因果的。根据 BIBO 稳定性的定义可知：当输入满足 |𝑥[𝑛]|\u003C∞ 时，输出满足 |𝑦[𝑛]|\u003C∞，系统是 BIBO 稳定的。令 |𝑥[𝑛]|≤𝑀\u003C∞，则有：|𝑦[𝑛]|=|12(𝑥[𝑛]+𝑥[𝑛−1])|≤12(|𝑥[𝑛]|+|𝑥[𝑛−1]|)≤12(𝑀+𝑀)=𝑀\u003C∞因此，系统是 BIBO 稳定的。综上，系统是因果且 BIBO 稳定的。解：从差分方程角度看：由于 𝑦[𝑛]=𝑥[𝑛]+𝑥[𝑛−1]2，不存在过去的输出作为反馈，因此系统是 FIR。从 ℎ[𝑛] 的长度角度看：由于 ℎ[𝑛] 仅在 𝑛=0 和 𝑛=1 时非零，长度为2，因此系统是 FIR。题二 能量信号与功率信号离散时间信号的总能量与平均功率定义为𝐸=∑𝑛=−∞∞|𝑥[𝑛]|2,𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|𝑥[𝑛]|2.证明：除零信号外，一个信号不可能既是能量信号又是功率信号（即证明 𝐸\u003C∞ 时必有 𝑃=0）；判定并证明以下各信号是能量信号、功率信号，还是两者都不是：① 𝑥[𝑛]=(12)𝑛𝑢[𝑛]；② 𝑥[𝑛]=𝑢[𝑛]；③ 𝑥[𝑛]=cos(𝜋𝑛\u002F4)；④ 𝑥[𝑛]=𝑛𝑢[𝑛]。有同学说：“一个能量信号与一个功率信号相加，结果一定是功率信号。”请判断该命题的真伪。若真，给出证明；若假，给出反例。答：解：设 𝑥[𝑛] 是非零的能量信号，且 𝐸\u003C∞。则有：0≤∑𝑛=−𝑁𝑁|𝑥[𝑛]|2\u003C𝐸\u003C∞,∀𝑁>0因此：0≤12𝑁+1∑𝑛=−𝑁𝑁|𝑥[𝑛]|2\u003C𝐸2𝑁+1,∀𝑁>0令 𝑁→∞，则有：0≤𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|𝑥[𝑛]|2≤lim𝑁→∞𝐸2𝑁+1=0根据夹逼定理可知，𝑃=0。因此，除零信号外，一个信号不可能既是能量信号又是功率信号。解：① 𝑥[𝑛]=(12)𝑛𝑢[𝑛]𝐸=∑𝑛=−∞∞|(12)𝑛𝑢[𝑛]|2=∑𝑛=0∞(14)𝑛=11−14=43\u003C∞𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|(12)𝑛𝑢[𝑛]|2=lim𝑁→∞12𝑁+1∑𝑛=0𝑁(14)𝑛=lim𝑁→∞12𝑁+11−(14)𝑁+11−14=lim𝑁→∞43(2𝑁+1)(1−(14)𝑁+1)=0因此这是一个能量信号。② 𝑥[𝑛]=𝑢[𝑛]𝐸=∑𝑛=−∞∞|𝑢[𝑛]|2=∑𝑛=0∞1=∞𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|𝑢[𝑛]|2=lim𝑁→∞12𝑁+1∑𝑛=0𝑁1=lim𝑁→∞𝑁+12𝑁+1=12因此这是一个功率信号。③ 𝑥[𝑛]=cos(𝜋𝑛\u002F4)𝐸=∑𝑛=−∞∞|cos(𝜋𝑛\u002F4)|2=∞𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁cos2(𝜋𝑛\u002F4)=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁1+cos(𝜋𝑛2)2=lim𝑁→∞𝑁+12𝑁+1=12因此这是一个功率信号。④ 𝑥[𝑛]=𝑛𝑢[𝑛]𝐸=∑𝑛=−∞∞|𝑛𝑢[𝑛]|2=∑𝑛=0∞𝑛2=∞𝑃=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|𝑛𝑢[𝑛]|2=lim𝑁→∞12𝑁+1∑𝑛=0𝑁𝑛2=lim𝑁→∞12𝑁+1𝑁(𝑁+1)(2𝑁+1)6=lim𝑁→∞𝑁(𝑁+1)6=∞因此这既不是一个能量信号也不是一个功率信号。解：该命题为真命题。证明如下：设 𝑥[𝑛]=𝑒[𝑛]+𝑝[𝑛]，其中 𝑒[𝑛] 是能量信号（0\u003C𝐸𝑒\u003C∞），𝑝[𝑛] 是功率信号（0\u003C𝑃𝑝\u003C∞）。由本题 (1) 的结论可知，能量信号的平均功率必为零：𝑃𝑒=0。和信号的平均功率为𝑃𝑥=lim𝑁→∞12𝑁+1∑𝑛=−𝑁𝑁|𝑒[𝑛]+𝑝[𝑛]|2.利用 |𝑒[𝑛]+𝑝[𝑛]|2=|𝑒[𝑛]|2+|𝑝[𝑛]|2+2Re{𝑒[𝑛]𝑝∗[𝑛]}，部分和可拆成三项：前两项分别趋于 𝑃𝑒=0 与 𝑃𝑝，关键在于证明交叉项也趋于 0。由柯西–施瓦茨不等式：|12𝑁+1∑𝑛=−𝑁𝑁𝑒[𝑛]𝑝∗[𝑛]|2≤(12𝑁+1∑𝑛=−𝑁𝑁|𝑒[𝑛]|2)⋅(12𝑁+1∑𝑛=−𝑁𝑁|𝑝[𝑛]|2).其中第一个因子满足12𝑁+1∑𝑛=−𝑁𝑁|𝑒[𝑛]|2≤𝐸𝑒2𝑁+1→0,第二个因子趋于 𝑃𝑝，有界。因此交叉项趋于 0。于是𝑃𝑥=𝑃𝑒+𝑃𝑝=𝑃𝑝,0\u003C𝑃𝑥\u003C∞.再证 𝐸𝑥=∞：本题 (1) 已证非零信号满足 𝐸\u003C∞⇒𝑃=0，取逆否命题得 𝑃>0⇒𝐸=∞；今 𝑃𝑥=𝑃𝑝>0，故 𝐸𝑥=∞。综上，𝐸𝑥=∞ 且 0\u003C𝑃𝑥\u003C∞，𝑥[𝑛] 是功率信号，命题得证。题三 离散正弦的周期性证明：𝑥[𝑛]=cos(𝜔𝑛+𝜑) 是周期信号，当且仅当 𝜔2𝜋 为有理数（即存在正整数 𝑚、𝑁 使 𝜔𝑁=2𝜋𝑚）；求下列信号的最小周期（若是周期的）：① cos(3𝜋𝑛\u002F7)；② cos(0.5𝜋𝑛)；并判断 ③ cos(𝑛) 是否周期，说明理由；推理：化简 cos((2𝜋−𝜔)𝑛)，说明它与 cos(𝜔𝑛) 的关系。据此解释：为什么在讨论实离散正弦\u002F余弦信号的频率大小时，只需考虑 [0,𝜋] 区间？（注意：该结论对复指数信号 𝑒𝑗𝜔𝑛 不成立——𝑒𝑗𝜔𝑛 与 𝑒−𝑗𝜔𝑛 通常是不同序列，一般离散时间信号的完整数字频率需取 [−𝜋,𝜋) 等长度为 2𝜋 的区间。）“离散正弦的最高数字频率为 𝜋 rad\u002Fsample”这句话应如何理解？答：解：根据周期信号的定义，𝑥[𝑛] 是周期信号，当且仅当存在正整数 𝑁，使得对一切 𝑛 都有 𝑥[𝑛+𝑁]=𝑥[𝑛]。设 𝑥[𝑛]=cos(𝜔𝑛+𝜑)，代入得cos(𝜔𝑛+𝜑+𝜔𝑁)=cos(𝜔𝑛+𝜑),∀𝑛∈𝑍由于两余弦相等当且仅当cos𝛼=cos𝛽⇔𝛼=±𝛽+2𝜋𝑘,𝑘∈𝑍所以对每个 𝑛，要么 𝜔𝑁=2𝜋𝑘（取“+”），要么 2(𝜔𝑛+𝜑)+𝜔𝑁=2𝜋𝑘𝑛（取“−”）。若某个 𝑛 取“+”分支，则直接得到 𝜔𝑁=2𝜋𝑘；若对所有 𝑛 都只能取“−”分支，则把 𝑛 与 𝑛+1 对应的两式相减，得2𝜔=2𝜋(𝑘𝑛+1−𝑘𝑛)即 𝜔2𝜋 仍是两整数之比。因此，无论哪种情形，𝑥[𝑛] 是周期信号都等价于：存在正整数 𝑚、𝑁，使𝜔𝑁=2𝜋𝑚下证该条件与“𝜔2𝜋 为有理数”等价。必要性：设 𝜔𝑁=2𝜋𝑚，两边同除以 2𝜋𝑁，得𝜔2𝜋=𝑚𝑁因为 𝑚、𝑁 都是正整数，所以 𝜔2𝜋 是有理数。充分性：设 𝜔2𝜋=𝑝𝑞，其中 𝑝、𝑞 为正整数。取 𝑁=𝑞，则𝜔𝑁=𝜔𝑞=2𝜋𝑝𝑞⋅𝑞=2𝜋𝑝所以条件满足，此时序列以 𝑁=𝑞 为周期。综上，𝑥[𝑛]=cos(𝜔𝑛+𝜑) 是周期信号，当且仅当 𝜔2𝜋 为有理数。解：由 (1) 的结论，𝑥[𝑛] 是周期信号当且仅当 𝜔𝑁=2𝜋𝑚 存在正整数解，使 𝑁 最小的解即最小周期。① 因为 𝜔=3𝜋\u002F7，代入 𝜔𝑁=2𝜋𝑚，得3𝜋7𝑁=2𝜋𝑚,𝑖.𝑒.3𝑁=14𝑚由于 3 与 14 互素，𝑁 必须是 14 的倍数，所以最小周期为 𝑁0=14（此时 𝑚=3）。② 因为 cos(0.5𝜋𝑛)=cos(𝜋𝑛\u002F2)，即 𝜔=𝜋\u002F2，代入得𝜋2𝑁=2𝜋𝑚,𝑖.𝑒.𝑁=4𝑚所以最小周期为 𝑁0=4（此时 𝑚=1）。③ 此时 𝜔=1。注意 𝜔=1 本身是有理数，但由 (1)，需要检验的是 𝜔2𝜋=12𝜋 是否为有理数：由于 𝜋 是无理数，所以 12𝜋 不是有理数，即不存在正整数 𝑚、𝑁 使 𝜔𝑁=2𝜋𝑚。因此 cos(𝑛) 不是周期信号。解：先化简。因为 𝑛 是整数，2𝜋𝑛 恰好是整数个完整周期，所以cos((2𝜋−𝜔)𝑛)=cos(2𝜋𝑛−𝜔𝑛)=cos(−𝜔𝑛)又因为余弦是偶函数，cos(−𝜔𝑛)=cos(𝜔𝑛)，所以cos((2𝜋−𝜔)𝑛)=cos(𝜔𝑛)即频率 𝜔 与 2𝜋−𝜔 产生完全相同的离散序列，二者不可区分。对于为什么只需考虑 [0,𝜋]。第一，数字频率本身以 2𝜋 为周期：因为𝑒𝑗(𝜔+2𝜋)𝑛=𝑒𝑗𝜔𝑛𝑒𝑗2𝜋𝑛=𝑒𝑗𝜔𝑛所以 𝜔 与 𝜔+2𝜋𝑘（𝑘∈𝑍）是同一频率，总可先把 𝜔 折回某个长度为 2𝜋 的区间，如 [0,2𝜋)。第二，由上面化简得到的恒等式，(𝜋,2𝜋) 内的每个频率都与 (0,𝜋) 内的某个频率产生同一序列（如 1.6𝜋→0.4𝜋、1.2𝜋→0.8𝜋）。因此，讨论实离散正弦\u002F余弦的频率大小时，只需考虑 [0,𝜋]。该结论对复指数不成立的原因：因为余弦是偶函数，cos(−𝜔𝑛)=cos(𝜔𝑛)，正、负频率本来就无法区分；但 𝑒𝑗𝜔𝑛 与 𝑒−𝑗𝜔𝑛 一般是不同的序列。例如取 𝜔=𝜋2，则𝑒𝑗𝜋𝑛\u002F2:1,𝑗,−1,−𝑗,1,…𝑒−𝑗𝜋𝑛\u002F2:1,−𝑗,−1,𝑗,1,…两者明显不同。所以复指数信号必须保留正、负频率，其完整数字频率应取长度为 2𝜋 的区间，如 [−𝜋,𝜋)。“最高数字频率为 𝜋 rad\u002Fsample”的理解：因为cos(𝜋𝑛)=1,−1,1,−1,…每前进一个采样点恰好翻转一次符号，这已是离散序列所能做到的最快交替；若再增大频率，如 𝜔=1.2𝜋>𝜋，则由折叠关系cos(1.2𝜋𝑛)=cos(0.8𝜋𝑛)它反而等价于更低的频率，不会产生新的振荡模式。因此，𝜋 rad\u002Fsample 是实离散正弦\u002F余弦的最高不重复数字频率。综上：数字频率以 2𝜋 为周期；实余弦又有偶性，所以频率大小只需在 [0,𝜋] 内讨论，𝜋 即最高数字频率；复指数必须区分正、负频率，故取 [−𝜋,𝜋) 等长度为 2𝜋 的区间。题四 系统四个性质的判定判断系统 𝑦[𝑛]=𝑥[𝑛]𝑥[𝑛−1] 的线性、移不变性、因果性、BIBO 稳定性。每一项都要给出证明或反例，不能只写结论。答：线性：设输入缩放为 𝑎𝑥[𝑛]，其中 𝑎 为常数，则输出为𝑇{𝑎𝑥[𝑛]}=(𝑎𝑥[𝑛])(𝑎𝑥[𝑛−1])=𝑎2𝑥[𝑛]𝑥[𝑛−1]若系统是线性的，则应有 𝑎𝑇{𝑥[𝑛]}=𝑎𝑥[𝑛]𝑥[𝑛−1]。由于当 𝑎≠0,1 且 𝑥[𝑛]𝑥[𝑛−1] 不恒为零时两者不相等（取 𝑎=2 与任意非零信号即为反例），所以齐次性不成立。因此，该系统是非线性的。移不变性：根据移不变的定义，若输入移位 𝑛0，输出应随之移位 𝑛0。设 𝑥1[𝑛]=𝑥[𝑛−𝑛0]，则系统对 𝑥1 的输出为𝑦1[𝑛]=𝑥1[𝑛]𝑥1[𝑛−1]=𝑥[𝑛−𝑛0]𝑥[𝑛−𝑛0−1]又因为原输出的移位为𝑦[𝑛−𝑛0]=𝑥[𝑛−𝑛0]𝑥[𝑛−𝑛0−1]=𝑦1[𝑛]可见两者对任意 𝑛0 都相等，因此该系统是移不变的。因果性：根据因果性的定义，输出只能依赖当前及过去的输入。由 𝑦[𝑛]=𝑥[𝑛]𝑥[𝑛−1] 可知，输出只用到当前输入 𝑥[𝑛] 与过去输入 𝑥[𝑛−1]，不涉及 𝑥[𝑛+1],𝑥[𝑛+2],… 等未来输入。因此，该系统是因果的。BIBO 稳定性：根据 BIBO 稳定性的定义，有界输入应产生有界输出。设输入有界，即存在 𝑀\u003C∞ 使 |𝑥[𝑛]|≤𝑀，则|𝑦[𝑛]|=|𝑥[𝑛]𝑥[𝑛−1]|=|𝑥[𝑛]||𝑥[𝑛−1]|≤𝑀⋅𝑀=𝑀2\u003C∞所以输出也有界。因此，该系统是 BIBO 稳定的。综上：该系统是非线性、移不变、因果、BIBO 稳定的。注意非线性不等于时变——乘法运算 𝑥[𝑛]𝑥[𝑛−1] 破坏了线性，但由于系统不显式依赖绝对时刻 𝑛，所以它仍是移不变的。题五 从 𝛿 分解推导卷积从信号分解 𝑥[𝑛]=∑𝑘𝑥[𝑘]𝛿[𝑛−𝑘] 出发，自己完整推一遍卷积公式 𝑦[𝑛]=∑𝑘𝑥[𝑘]ℎ[𝑛−𝑘] 的来历。要求：明确写出推导的每一步用到了系统的哪一条性质（线性还是移不变），并说明该性质在推理中起什么作用；指出推导中 𝑆{𝛿[𝑛−𝑘]}=ℎ[𝑛−𝑘] 这一关系是在哪一步使用的，并解释它为什么依赖于系统的移不变性；进一步说明：对于线性时变系统，为什么需要用 ℎ[𝑛,𝑘]（𝑛 时刻对 𝑘 时刻单位冲激的响应）来描述，而不能用单一的 ℎ[𝑛] 完全刻画系统；用一句话概括：为什么说“ℎ[𝑛] 完全刻画了 LSI 系统”。答：解：由题意，任意离散信号可分解为移位冲激的线性组合：𝑥[𝑛]=∑𝑘𝑥[𝑘]𝛿[𝑛−𝑘]把它作为输入，得𝑦[𝑛]=𝑆{𝑥[𝑛]}=𝑆{∑𝑘𝑥[𝑘]𝛿[𝑛−𝑘]}由于系统满足线性，故𝑦[𝑛]=∑𝑘𝑥[𝑘]𝑆{𝛿[𝑛−𝑘]}①由于系统满足移不变，而单位抽样响应定义为 ℎ[𝑛]=𝑆{𝛿[𝑛]}，所以移位冲激的响应就是 ℎ 的相应移位：𝑆{𝛿[𝑛−𝑘]}=ℎ[𝑛−𝑘]②把②的结果代入①，即得卷积公式：𝑦[𝑛]=∑𝑘𝑥[𝑘]ℎ[𝑛−𝑘]=𝑥[𝑛]∗ℎ[𝑛]解：该关系用在推导的第二步，即把 𝑆{𝛿[𝑛−𝑘]} 替换为 ℎ[𝑛−𝑘]。它依赖移不变性的原因是：移不变的定义是“若 𝑥[𝑛]→𝑦[𝑛]，则 𝑥[𝑛−𝑘]→𝑦[𝑛−𝑘]”。取 𝑥[𝑛]=𝛿[𝑛]，其响应为 ℎ[𝑛]，则由移不变性可得𝛿[𝑛−𝑘]→ℎ[𝑛−𝑘],𝑖.𝑒.𝑆{𝛿[𝑛−𝑘]}=ℎ[𝑛−𝑘]若系统线性但时变，上式一般不成立。例如 𝑦[𝑛]=𝑛𝑥[𝑛]：当输入 𝛿[𝑛] 时，输出为𝑆{𝛿[𝑛]}=𝑛𝛿[𝑛]=0当输入 𝛿[𝑛−1] 时，输出为𝑆{𝛿[𝑛−1]}=𝑛𝛿[𝑛−1]=𝛿[𝑛−1]可见后者并不是前者的移位。由于冲激放在不同时刻系统的行为不同，所以单一的 ℎ[𝑛] 无法完全刻画时变系统，必须定义二元函数ℎ[𝑛,𝑘]=𝑆{𝛿[𝑛−𝑘]}表示“𝑘 时刻输入单位冲激、𝑛 时刻观察到的响应”。由线性性仍可得𝑦[𝑛]=∑𝑘𝑥[𝑘]ℎ[𝑛,𝑘]但由于缺少移不变性，ℎ[𝑛,𝑘] 不能化简为 ℎ[𝑛−𝑘]；当且仅当系统移不变时 ℎ[𝑛,𝑘]=ℎ[𝑛−𝑘]，即响应只依赖间隔 𝑛−𝑘，与绝对时刻无关。综上：线性时变系统必须用 ℎ[𝑛,𝑘] 描述，只有移不变系统才能退化为单一的 ℎ[𝑛]。答：任意输入都可分解为移位冲激的线性组合，而 LSI 系统对每个移位冲激的响应都由 ℎ[𝑛] 的相应移位唯一确定，因此知道 ℎ[𝑛] 就能通过卷积 𝑦[𝑛]=𝑥[𝑛]∗ℎ[𝑛] 求出对任意输入的输出。所以 ℎ[𝑛] 完全刻画了 LSI 系统。题六 采样、混叠与奈奎斯特的逻辑推导某采样系统采样率 𝑓𝑠=100 Hz。请逐问严格论证。该系统的奈奎斯特频率是多少？在严格无混叠、可唯一重构的条件下，信号最高频率 𝑓max 与该值应满足什么关系？（提示：考虑 𝑥(𝑡)=sin(2𝜋⋅50𝑡) 以 100 Hz 采样会得到什么序列，据此说明边界频率处为何不能取等号。）若输入一个 90 Hz 的正弦，采样后还原出来的“表观频率”是多少？请用“折叠”的逻辑解释：为什么 90 Hz 会被“看成”这个频率，而不是 90 Hz。若输入同时含有 40 Hz 与 60 Hz 两个正弦分量，采样后它们是否会混叠到同一离散（表观）频率？分别算出两者的表观频率并比较，并进一步讨论：若两个分量都是余弦、或都是正弦，采样序列是否完全相同？再据此说明：奈奎斯特条件 𝑓𝑠>2𝐵 中的 𝐵 应当理解为“信号最高频率”还是“某个频带”，以及为什么采样前必须加抗混叠滤波器。推而广之：给出“频率为 𝑓 的正弦在采样率 𝑓𝑠 下被混叠成的表观频率 𝑓alias”的一般公式（要求把频率折叠到 [0,𝑓𝑠2] 区间，用取模\u002F折叠表达），并说明该公式如何同时解释 (2) 与 (3)。答：解：奈奎斯特频率定义为采样率的一半，所以𝑓𝑁=𝑓𝑠2=1002=50Hz由采样定理可知，严格无混叠、可唯一重构的条件是𝑓𝑠>2𝑓max,𝑖.𝑒.𝑓max\u003C50Hz边界处不能取等号的原因：设 𝑥(𝑡)=sin(2𝜋⋅50𝑡)，以 𝑓𝑠=100 Hz 采样即 𝑡=𝑛100，代入得𝑥[𝑛]=sin(2𝜋⋅50⋅𝑛100)=sin(𝜋𝑛)=0由于一个真实存在的 50 Hz 正弦被采成了全零序列，原信号无法重构，所以必须严格取 𝑓max\u003C𝑓𝑠2。解：先求数字频率。由 𝜔=2𝜋𝑓𝑓𝑠，代入 𝑓=90 Hz、𝑓𝑠=100 Hz，得𝜔=2𝜋⋅90100=1.8𝜋由于离散时间频率以 2𝜋 为周期（题三 (3) 已证），所以1.8𝜋≡1.8𝜋−2𝜋=−0.2𝜋又因为实正弦只看频率大小，所以表观数字频率为 0.2𝜋，反算回模拟频率：0.2𝜋=2𝜋𝑓alias𝑓𝑠⇒𝑓alias=0.1𝑓𝑠=10Hz折叠逻辑：由于 90 Hz 超过奈奎斯特频率 50 Hz，它关于 50 Hz 镜像折回，𝑓𝑠−𝑓=100−90=10 Hz。因此 90 Hz 与 10 Hz 产生同一离散序列，采样后 90 Hz 被“看成”10 Hz，而不是 90 Hz。解：分别求两个分量的数字频率：𝜔1=2𝜋⋅40100=0.8𝜋,𝜔2=2𝜋⋅60100=1.2𝜋因为 40 Hz 在 [0,50] 内，所以其表观频率就是 40 Hz；而 60 Hz 超过 50 Hz，折回 𝑓𝑠−60=40 Hz。因此两者混叠到同一表观频率 40 Hz。若两个分量都是余弦：由于 1.2𝜋=2𝜋−0.8𝜋，由题三 (3) 的折叠恒等式 cos((2𝜋−𝜔)𝑛)=cos(𝜔𝑛) 可得cos(1.2𝜋𝑛)=cos(0.8𝜋𝑛)所以两个采样序列完全相同。若两个分量都是正弦：由于 sin(−𝑥)=−sin(𝑥)，可得sin(1.2𝜋𝑛)=sin(−0.8𝜋𝑛)=−sin(0.8𝜋𝑛)所以序列相差一个负号，即相位差 𝜋，表观频率仍相同。因此无论正弦还是余弦，仅凭采样结果都无法分辨原来的 40 Hz 与 60 Hz。关于 𝐵 的含义：由于经典奈奎斯特定理针对基带限带信号（𝑋(𝑓)=0，|𝑓|>𝐵），所以 𝐵 应理解为信号最高频率 ，而不是任意一段频带的宽度。例如只含 900–1000 Hz 分量的信号带宽虽只有 100 Hz，也不能直接套用 𝑓𝑠>200 Hz——那属于带通采样问题，需满足额外条件。抗混叠滤波器：由于混叠在采样瞬间就不可逆地发生，60、90、120 Hz 分别折成 40、10、20 Hz，采完后看到 10 Hz 分量已无法区分它来自 10 Hz 还是 90 Hz，所以必须在 ADC 之前用模拟低通滤波器滤除高于 𝑓𝑠2 的成分。解：先对 𝑓𝑠 取模，再关于 𝑓𝑠2 折叠。设 𝑟=𝑓mod𝑓𝑠∈[0,𝑓𝑠)，则表观频率为𝑓alias={𝑟0≤𝑟≤𝑓𝑠2𝑓𝑠−𝑟𝑓𝑠2\u003C𝑟\u003C𝑓𝑠也可写成紧凑形式：𝑓alias=min𝑘∈𝑍|𝑓−𝑘𝑓𝑠|∈[0,𝑓𝑠2]验证 (2)：𝑓=90 时，距最近的 𝑓𝑠 整数倍为 |90−100|=10，所以 90→10 Hz。验证 (3)：|40−0|=40，|60−100|=40，所以 40 与 60 的表观频率同为 40 Hz。可见该公式同时解释了 (2) 与 (3)：直观上，频率轴在 𝑓𝑠2=50 Hz 处对折，60→40、70→30、80→20、90→10、100→0。题七 从 𝛿 分解与卷积出发的逆推 + 性质判定已知某 LSI 系统 𝑆 的单位抽样响应为 ℎ[𝑛]=(12)𝑛𝑢[𝑛]。(a) 因果性与稳定性判定：仅凭 ℎ[𝑛]=(12)𝑛𝑢[𝑛]，判断该系统是否因果、是否 BIBO 稳定，并说明推理依据。(b) 翻转 + 移位的输入经系统后的响应：对输入先做“翻转再右移 1”得到 𝑣[𝑛]=𝑥[−𝑛+1]。取 𝑥[𝑛]=𝛿[𝑛]，先写出 𝑣[𝑛] 的具体形式，再用卷积求系统对该输入的响应 𝑤[𝑛]=𝑆{𝑣[𝑛]} 的闭式。(c) 复合系统的性质：把“先翻转移位、再过系统 𝑆”整体看作一个新系统 𝑇，即 𝑇{𝑥[𝑛]}=𝑆{𝑥[−𝑛+1]}。逐项严格论证 𝑇 是否仍为：① 线性；② 移不变；③ 因果；④ BIBO 稳定。（注意：𝑆 本身是“线性、移不变、因果、稳定”的，但经过翻转预处理后，哪些性质被保住、哪些被打破？请解释“为什么翻转会同时破坏移不变性和因果性”，并指出这与 (b) 中 𝛿 的偶对称性有何关联。）答：(a) 解：因果性：由于 𝑢[𝑛]=0（𝑛\u003C0），所以 ℎ[𝑛]=(12)𝑛𝑢[𝑛] 在 𝑛\u003C0 时恒为零。由 LSI 系统因果的充要条件，𝑛\u003C0 时 ℎ[𝑛]=0可知，该系统是因果的。稳定性：由 LSI 系统 BIBO 稳定的充要条件，需要检验∑𝑛=−∞∞|ℎ[𝑛]|=∑𝑛=0∞(12)𝑛=11−12=2\u003C∞因为求和收敛于有限值 2，所以该系统是 BIBO 稳定的。综上，系统是因果且 BIBO 稳定的。(b) 解：由题意取 𝑥[𝑛]=𝛿[𝑛]，先求 𝑣[𝑛]：𝑣[𝑛]=𝑥[−𝑛+1]=𝛿[−𝑛+1]由于冲激只在括号内自变量为零处非零，令 −𝑛+1=0，解得 𝑛=1；又因为 𝛿[𝑛] 是偶函数，所以𝛿[−𝑛+1]=𝛿[𝑛−1]再由卷积的冲激移位性质 𝛿[𝑛−𝑛0]∗ℎ[𝑛]=ℎ[𝑛−𝑛0]，取 𝑛0=1，得𝑤[𝑛]=𝑆{𝑣[𝑛]}=𝛿[𝑛−1]∗ℎ[𝑛]=ℎ[𝑛−1]把 ℎ[𝑛]=(12)𝑛𝑢[𝑛] 代入，可得𝑤[𝑛]=(12)𝑛−1𝑢[𝑛−1](c) 解：把 𝑇 看成翻转预处理 𝑃 + 系统 𝑆的串联，即 𝑇{𝑥[𝑛]}=𝑆{𝑥[−𝑛+1]}，逐项论证。① 线性：设输入为 𝑎𝑥1[𝑛]+𝑏𝑥2[𝑛]，则(𝑎𝑥1+𝑏𝑥2)[−𝑛+1]=𝑎𝑥1[−𝑛+1]+𝑏𝑥2[−𝑛+1]所以翻转预处理 𝑃 是线性的；又因为 𝑆 是线性的，而线性系统串联仍线性，因此 𝑇 是线性的。② 移不变：设 𝑥1[𝑛]=𝑥[𝑛−𝑛0]，则经翻转预处理后𝑥1[−𝑛+1]=𝑥[−𝑛+1−𝑛0]=𝑃{𝑥}[𝑛+𝑛0]可见输入延迟 𝑛0 后，翻转后的信号反而提前了 𝑛0。由于 𝑆 是移不变的，所以最终输出为𝑇{𝑥[𝑛−𝑛0]}=𝑦[𝑛+𝑛0]而移不变要求输出为 𝑦[𝑛−𝑛0]。由于一般 𝑦[𝑛+𝑛0]≠𝑦[𝑛−𝑛0]，因此 𝑇 不是移不变的——时间翻转把“延迟”变成了“超前”。③ 因果：把 𝑇 按卷积展开：𝑇{𝑥}[𝑛]=∑𝑘𝑥[−𝑘+1]ℎ[𝑛−𝑘]令 𝑚=1−𝑘 换元，得𝑇{𝑥}[𝑛]=∑𝑚𝑥[𝑚]ℎ[𝑛−1+𝑚]由于 ℎ[𝑟]≠0（𝑟≥0），所以输出要用到所有满足 𝑚≥1−𝑛 的 𝑥[𝑚]，其中一般包含 𝑚>𝑛 的未来输入。因此 𝑇 不是因果的——时间翻转把未来与过去对调了。④ BIBO 稳定：设输入有界 |𝑥[𝑛]|≤𝑀。由于翻转与平移不改变幅值上界，所以|𝑥[−𝑛+1]|≤𝑀又因为 𝑆 是 BIBO 稳定的，有界输入经 𝑆 后输出仍有界，因此 𝑇 是 BIBO 稳定的。综上：𝑇 是线性的、非移不变的、非因果的、BIBO 稳定的。翻转预处理本身线性、稳定，但同时破坏移不变性与因果性；(b) 中翻转“没出问题”只是因为 𝛿[𝑛] 是偶函数，不能推广到一般信号。",[],{"slug":54,"title":55,"description":56,"plainText":111,"keywords":112},"课程：现代信号处理（2026 Fall） 覆盖范围：Lecture 5–6提交要求：第一部分需提交书面作业，最终提交 pdf 文件；第二部分为下节课随堂抽查准备，不要求书面提交。推荐使用 LaTeX 完成作业，也可手写推导；如使用 AI 辅助，请在作业末尾披露使用了哪些环节，以及如何验证结果的正确性。第一部分 需提交的书面作业题一 滤波器类型的判定某离散时间 LSI 系统的频率响应为 𝐻(𝑒𝑗𝜔)。已知|𝐻(𝑒𝑗0)|=0,|𝐻(𝑒𝑗𝜋)|=max𝜔∈[0,𝜋]|𝐻(𝑒𝑗𝜔)|>0.这两个条件分别说明系统具有什么频率响应特征？仅凭这两个条件，能否断定该系统是高通滤波器？请说明理由。若不能，请给出反例，并验证该反例满足题设条件。解：因为 |𝐻(𝑒𝑗0)|=0，所以系统直流分量被抑制，即直流增益为零。又因为 |𝐻(𝑒𝑗𝜋)|=max𝜔∈[0,𝜋]|𝐻(𝑒𝑗𝜔)|>0，所以系统在 𝜔=𝜋 处取得最大幅度增益。综上，系统在直流增益为零，在最高频处增益最大且非零。解：仅凭这两个条件，不能断定该系统是高通滤波器。下面先构造一个满足题设条件的幅频响应，再找出与它对应的 LSI 系统，作为反例。考虑𝑓(𝜔)=|sin(3𝜔2)|,𝜔∈[0,𝜋]容易验证𝑓(0)=0,𝑓(𝜋)=|sin(3𝜋2)|=1=max𝜔∈[0,𝜋]𝑓(𝜔)即 𝑓(𝜔) 满足题设的两个条件。但进一步有𝑓(𝜋3)=|sin(𝜋2)|=1,𝑓(2𝜋3)=|sin(𝜋)|=0也就是说，该幅频响应在较低的 𝜔=𝜋3 处就已经取到最大增益，却在更高的 𝜔=2𝜋3 处把信号完全抑制，不具备高通滤波器“高频通带”的特征。注意到延迟差分系统𝑦[𝑛]=𝑥[𝑛]−𝑥[𝑛−3]其频率响应为𝐻(𝑒𝑗𝜔)=1−𝑒−𝑗3𝜔提出公因子后由欧拉公式得|𝐻(𝑒𝑗𝜔)|=|𝑒−𝑗3𝜔\u002F2|⋅|𝑒𝑗3𝜔\u002F2−𝑒−𝑗3𝜔\u002F2|=2|sin(3𝜔2)|=2𝑓(𝜔)它只比 𝑓(𝜔) 多一个正常数因子 2，既不影响题设的两个条件是否成立，也不影响滤波器类型的判断。该系统的单位抽样响应为ℎ[𝑛]=𝛿[𝑛]−𝛿[𝑛−3]它是有限长序列，且 𝑛\u003C0 时 ℎ[𝑛]=0，因此 𝑦[𝑛]=𝑥[𝑛]−𝑥[𝑛−3] 是一个合法的因果 FIR 系统，自然也是 LSI 系统。再由|𝐻(𝑒𝑗0)|=2𝑓(0)=0,|𝐻(𝑒𝑗𝜋)|=2𝑓(𝜋)=2=max𝜔∈[0,𝜋]|𝐻(𝑒𝑗𝜔)|可知它确实满足题设的两个条件。综上，仅凭 |𝐻(𝑒𝑗0)|=0 与 |𝐻(𝑒𝑗𝜋)|=max𝜔∈[0,𝜋]|𝐻(𝑒𝑗𝜔)|>0 这两个条件，不能断定系统是高通滤波器。",[],{"slug":10,"title":11,"description":12,"plainText":114,"keywords":115},"这是一篇用于验证博客 Typst 子集的样板文章，只使用最基本的结构：标题、段落、列表、代码块、表格、网格、对齐和图片。文章刻意不包含纸面布局指令，网页上的渲染行为由前端决定。基本结构段落之间以空行分隔，行内支持强调与等宽两种标记。列表用于枚举要点：一个标题与两个小节；三段短小的说明文字；一个代码块、一个表格和一个网格。编译本文可以使用：typst compile content\u002Fdemo\u002Ftypst-syntax.typ表格与网格表格适合呈现成对的数据：语言职责Typst内容源语法Rust解析与结构转换Vue网页渲染网格用于左右并排的独立内容：子集校验：只接受博客需要的语法。前端渲染：布局细节交给 CSS。图片由官方导出内嵌，博客改为内容寻址缓存：对齐用于居中或靠边的独立内容：一份居中的引文或题记。—— 右对齐的署名",[116,117,118],"typst","demo","样板",{"slug":38,"title":39,"description":40,"plainText":120,"keywords":121},"Introduction——前言托更了很久，我一点不好意思都没有[手动滑稽]，我想应该没有人等着我的教程的吧。之所以这时才做这教程，是因为我准备开启新项目了，如果还不填坑的话，坑就越来越多，然后就不想填了。为了避免这种情况，所以我决定先填了这坑，再开新坑。本篇文章将会一步步教你如何用github和hexo搭建属于自己的博客，以及搭建过程中的一些注意事项，也会教你如何解决搭建过程中出现的问题，我不仅仅会提供一种解决问题的方法，还会提供一种解决问题的思想。因为我早就搭建好博客了，所以为了模拟搭建过程和搭建过程所遇到的问题，我选择虚拟机来模拟这一效果。或许我可以不那么麻烦，但是我的记忆力实际上是不怎么好的，更何况现在距离我搭建完博客已经很久了，有些细节我都忘了，而这些细节又很重要。所以为了最好的效果，我就决定麻烦一些，既方便我以后的复习，也方便大家。Preparation——准备工作在这里，我将告诉你用github pages + hexo的方式搭建博客所需的一些前置要求。当然啦，平台是windows，linux以后再说吧。githubgithub 就不用多说了吧，想要用github pages 那就就免不了一个github的账号，当然有了就不用注册了，可以直接跳过。github是全英文的，我怕有的人看到英文就头疼，所以我就给一个注册的步骤吧。如果用谷歌浏览器的，直接用谷歌翻译后问题就不大了。下面我就教一些不用谷歌浏览器的人github的注册方法。打开github在网页的地址栏输入github的官网github.com，然后回车。打开注册页面在github的官网上头点击Sign up打开注册页面。输入信息并注册Node.jsHexo是基于Node.js开发的，所以要安装hexo，Node.js必不可少打开官网老司机可以直接跳过了。可以百度搜索Node.js，或者直接打开nodejs.org在右上角找到一个像谷歌翻译的图标，点击后，找到 中文(简体)并点击，即可更换语言。百度搜索到的一般是已经调好语言了的，所以不在需要更换语言，可以直接跳到下一步。如果对英文不敏感的话，也可直接省略。下载Node.js官网提供两个版本，一个最新版(即当前发布版)，和一个稳定版(即长期支持版)。个人建议下载稳定版，当然你想要最新版也没问题。安装安装没什么好说的，双击运行后，一路默认安装即可。完成后如图所示。检验安装安装后，就免不了检验是否安装成功。打开命令行，即\u003Ckbd>win\u003C\u002Fkbd>+\u003Ckbd>r\u003C\u002Fkbd>，输入 cmd，回车。即可打开命令行。然后在命令行中输入node -v，回车后，输出结果为版本号，说明node.js安装成功。在命令行中输入npm -v，回车后，输出结果为版本号，说明npm安装成功。Gitgit是一个好东西，他有挺多用处，但我都不懂用。如果想了解更多，请去廖雪峰老师的官方网站。不要害怕不精通就不会用，虽然我们不精通也不了解，但这并不妨碍我们使用它。下载首先打开Git的官方网站，同理你可以百度搜索，也可以直接打开git-scm.com进入官网。看到绿色的小电脑没有？点击下方的download即可下载。当然啦，因为是外国网站，下载速度是非常慢的，下面提供一个办法，打开阿里源的镜像站（注意我提供的网址仅限windows平台的Git），下滑找到最新版本，并点击。找到自己的操作系统类型，是32位还是64位。选择好后，点击下载。安装安装也是跟node.js一样，全部默认，毕竟看不懂英文嘛[手动滑稽]。检验安装安装完后，自然也免不了检验，老司机就跳过吧。同样打开命令行，即\u003Ckbd>win\u003C\u002Fkbd>+\u003Ckbd>r\u003C\u002Fkbd>，输入 cmd，回车。在命令行中输入git --version，回车。看到输出版本号即为安装成功。或者直接在桌面右键看到Git GUI Here和Git Bash Here即为安装成功。HexoHexo在我看来就是一个将Markdown按照模版转换为网页的工具。“什么是 Hexo？”>“Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown（或其他渲染引擎）解析文章，在几秒内，即可利用靓丽的主题生成静态网页。”>“——hexo官方文档”注意我提供的Hexo安装方法有两个，第一个是官方的安装方法，但是众所周知，因为国内的原因，所以下载速度十分缓慢，我并不推荐，所以我提供了第二个方法，利用阿里源的镜像，加快下载速度。当然这两种方法我都会在虚拟机上模拟，尽可能地排除问题。必要准备首先你需要找个位置新建一个文件夹，这个文件夹就是以后博客所在的文件夹。那里有hexo、hexo的主题、博客的文章等等文件，可以说那个文件夹就是本地博客与github pages的桥梁，所以必须找好地方。比如我，就直接反正D盘，命名为Blog。而在虚拟机中，因为我只是模拟效果，所以很随意地反在桌面。安装方法一现在我们已经创建好了文件夹，进入文件夹后，右键打开Git Bash Here在打开的命令行中输入npm install hexo-cli -g，回车。之后它就会自动下载文件，等待它命令运行完成即可。运行完成后，显示如图所示，仅供参考。然后输入hexo init，回车后继续等待。结果仅供参考。注意：不要中途退出或者\u003Ckbd>ctrl\u003C\u002Fkbd>+\u003Ckbd>c\u003C\u002Fkbd>停止运行命令，不然会出现一定的错误，这时候将文件夹里的文件清空，再重新运行命令。如果中途下载没有速度或者没有进展，也可用此方法，可能会有一定的效果。接下来检查一下目录，如图所示，会有以下文件和文件夹。检查完后，在命令行中运行npm install。结果如图所示。之后运行hexo s，复制提供的本地博客链接，在浏览器中打开。如图所示，即为安装成功。在命令行中按\u003Ckbd>ctrl\u003C\u002Fkbd>+\u003Ckbd>c\u003C\u002Fkbd>停止运行命令。安装方法二进入文件夹，右键打开Git Bash Here在打开的命令行中运行npm config set registry https:\u002F\u002Fregistry.npm.taobao.org，切换到淘宝源。注意：npm config set registry http:\u002F\u002Fwww.npmjs.org切换官方源。切换完后可用npm info underscore检测是否已经换到淘宝源，看到有淘宝地址，即切换成功。之后就跟方法一同样了。下面我就不重复了，就放一些效果图吧。Github And Hexo Setting——Github和Hexo的设置做了这么久的前期准备，下面终于到正片了。现在我也算是为什么很多教程都没有这么详细的缘故了，真是废时间啊。其实配置ssh key和创建github pages顺序是了调换的，但配置_config.yml必须放在这两个之后，看了就懂了。配置SSH Keyssh key在某种程度上就是一个登录github的密钥，但它实际上并不是，它实际上是github验证用户身份的身份证。有了ssh key，git才能上传文件到用户的仓库，没有ssh key也可以布置博客，只不过会非常麻烦，全部都要手动上传。检查ssh key在命令行中输入cd ~\u002F.ssh，即可检查是否创建过ssh key，如果没有则会提示找不到文件，如图所示。如果创建过ssh key，则任何提升都没有，但工作目录会移动到ssh，所以要注意，不要没切回去就乱输命令。如果已经创建，可以通过cat ~\u002F.ssh\u002Fid_rsa.pub查看ssh key。创建ssh key如果没有创建过ssh key则首先需要全局配置git的本地用户。理论上本地用户可以随意配置，但是我建议输入的是github的信息，不然以后容易遗忘。git config --global user.name \"用户名\" git config --global user.email \"邮箱地址\"配置完成后执行下面的命令生成ssh key，中途需要输入三次回车。ssh-keygen -t rsa -C '上面的邮箱'创建完后通过cat ~\u002F.ssh\u002Fid_rsa.pub查看ssh key。添加信任打开github的账号设置新建ssh key输入必要的信息，选择添加ssh key即可。注意输入的ssh key包括ssh-rsa这一部分。检验想知道是否成功配置ssh key，在git bash中输入ssh -T git@github.com（注意大小写）。出现“You’ve successfully authenticated, but GitHub does not provide shell access.”则说明添加成功。创建Github pages首先登陆github，在右上角点击加号旁的倒三角，选择New repository。填好仓库的信息（公共仓库与私人仓库随意选择），然后创建，创建完后自动开启github pages。注意：仓库名只能和用户名相同并且要加上.github.io，比如我的用户名为Selcon123，我的仓库名只能为Selcon123.github.io，这是因为github只给用户一个与用户名相同的仓库开启github pages。配置_config.yml在博客的文件夹中打开_config.yml，在最下面找到deploy，并进行修改，如图所示。特别注意：输完仓库名后还要加上.git，举个例子，假如我的用户名为Selcon123，我需修改为deploy: type: git repo: https:\u002F\u002Fgithub.com\u002FSelcon123\u002FSelcon123.github.io.git branch: masterDeploy Website——部署网站在这里我打算介绍一些指令，但从这里开始我就不模拟了，因为我的博客已经搭好了，如果模拟的话可能会出现一些问题。hexo官网提供挺多方法部署网站，但我只讲一种，如果想用其他的方法，可以执行研究。使用git部署首先先在git bash中，运行npm install hexo-deployer-git --save安装插件。如果在之前安装hexo时用方法二切换了阿里源，安装应该挺快。安装之后就可以用git部署了，也就是可以在博客文件夹中打开git bash，运行命令部署。以下即常用命令合集。hexo clean #清空缓存（有时候出问题时用到） hexo g #生成页面（缓存） hexo d #部署 hexo g -d #生成并部署 hexo s #本地预览（要先生成）下面是hexo自带的常用命令hexo new \"文章名\" #新建文章（博客） hexo new page \"页面名\" #新建页面（常用于个性化）Writing——正式写作下面我就介绍一整套写作流程流程。新建文章首先在git bash中执行hexo new “My first blog”新建一篇名为My first blog的文章。在.\u002Fsource\u002F\\_posts中找到并编辑，可以用windows自带的文本编辑器编辑，也可用其他。写作语法.md的文件格式说明了它的身份，它是一个Markdown格式的文本，一种对程序员比较友好的文本格式，但必不代表普通人就不懂用了，什么都是可以学习的嘛。Markdown我这里并不会教如何用Markdown，我只会讲如何学习Markdown。推荐菜鸟教程的Markdown教程，有关命令介绍得很清楚，很适合新手，当然我也是从那里学习的。学习完了Markdown，就可以使用其他软件快速地编辑文章，我推荐typora，它是一个优秀的Markdown文本编辑器。但是并不推荐新手直接就用这个编辑器，因为它太优秀了，以至于把Markdown的门槛给抹去了，这样其实对个人的成长并不怎么好。工具终究是工具，最好用的还是自己的脑袋，过度依赖工具，会使自己变成工具的“工具人”。htmlMarkdown是支持部分html标签的，但不是支持html。至于支持什么，自己查吧。图片相关图片这种基本老生常谈了，其实就是图床的问题。其实图片可以上传到github的仓库上，但是github的速度众所周知，所以比较推荐其他的图床。图床这种东西网上找一大片，但最重要的是稳定和速度。稳定就不用说了，要是几天这个平台就没了，那就欲哭无泪了，所以尽量选择老牌，而且最好是能看到他的盈利模式，一般免费的容易死的快。速度这个就更不用说了，要是速度还不如github那就呵呵了，所以尽量选择国内的吧，国外的一般在国内速度都不是很好，当然说不定也有例外。像我的话，用的是路过图床，2011年的，国内的，速度还行，盈利靠广告和企业收费，所以就比较令人放心。但唯一可惜的就是每小时只能传30张，每张不能超过10M。上传到博客在git bash执行hexo g -d部署到博客即可。自定义域名绑定因为我并不打算自定义域名（其实是没钱），所以我并不会自定义域名，可能以后有机会在写一下吧，在这之前可以百度学习。后话至于个性化的内容可能要等以后了，这篇文章也四千多字了，以后的个性化内容也不少，可以说是一个坑分成两次部吧，预告一下个性化的内容：404自定义，音乐播放器，主题更换，站长配置，评论系统，百度自动推送，广告添加，特效等等。如果等不及了可以自行解决，也可以拿钱砸我（明示打赏！），叫我更快点，反正我都不介意[手动滑稽]。如果看来我的教程还是不懂的话，可以到Hexo的官网查看文档学习。本来是想一天就解决了的，没想到事情突然变多，一直拖了三天左右，这也是没办法的事。之所以想要补了这个坑，是因为我想开新坑，至于是什么新坑，拭目以待吧。如果有什么错误，欢迎指出。",[122,123],"Markdown","Hexo",{"slug":6,"title":7,"description":8,"plainText":125,"keywords":126},"Hydra 研究分层三维场景图的实时构建与回环优化；Clio 研究如何根据任务选择物体和区域的粒度。两者都将语义对象与空间结构结合，但场景图本身不负责全部探索、避障和运动控制。物体识别与实例关联HydraHydra 将二维语义分割结果通过深度和相机位姿融合进三维地图，再对同一语义类别的网格顶点分别做欧氏聚类。每一簇形成一个物体，保存类别、质心、包围盒及对应表面。语义类别区分“是什么”，空间聚类区分“有几件”。两把分开的椅子可以成为两个实例；同类物品紧贴时可能被合并。识别粒度受前端类别集合限制，建立场景图不会自动增加部件级识别能力。新物体与已有节点类别相同、空间重叠时，系统把新顶点并入已有节点。论文以“一个物体的质心落在另一个物体的包围盒中”近似判断重叠。新观测用于补全和修正已有物体，而不是每帧新增节点。ClioClio 用 FastSAM 提取图像区域，用 CLIP 描述区域的开放词汇语义，再采用 Khronos 的跨帧关联与重建方法形成三维片段（primitives）。新区域只有在 CLIP 特征相似度和三维重叠程度都满足阈值时，才关联到已有的跟踪记录（track）；多个候选中选择重叠程度最高的一个。没有匹配则新建 track，长时间没有新观测则结束 track，利用其中的多帧数据重建 primitive，并平均 CLIP 特征。后端再对这些片段进行任务驱动聚类。物体片段只有在三维包围盒非零重叠时，才成为候选合并对象。系统比较它们对整组任务的相关性，优先合并任务信息损失最小的一对，直到进一步合并造成的信息损失超过阈值。这是凝聚式信息瓶颈（Agglomerative IB）的应用。因此，跨帧关联与任务聚类不是同一件事：跨帧关联处理同一物体的重复观测。任务聚类决定哪些片段或物品需要被分别表示。例如，“拿调料包”可以对应一组调料包；分别取某种调料时，则需要保留更细的区分。Clio 的一个节点不一定对应现实中的一件物品。细粒度与阈值Clio 后端主要做合并，细粒度仍依赖前端。如果两本书始终被分成同一个区域，后端不能凭空拆开；如果 CLIP 无法区分其特征，任务聚类也无法补出这种区别。论文中的钢琴、琴键、琴弦是动机示例，不是已验证的任意部件识别能力。几何阈值同样有局限。IoU 是交集体积与并集体积的比例，比固定距离阈值更能适应物体尺寸，因此杯子和椅子不一定必须使用不同的 IoU 阈值。但同样两厘米的定位误差，对杯子的影响显著大于椅子；遮挡会降低同一物体不同观测间的重叠，而包围盒可能在实际表面没有接触时发生重叠。Hydra 的质心包含规则也只是近似判断。两篇论文都没有解决按物体尺度、粒度和观测不确定性通用地调整关联阈值的问题。几何重叠可以作为身份关联的证据，不能单独保证关联正确。任务相关性与信息保留Clio 根据 CLIP 相似度构造任务相关性分布，并设置 null task 表示不相关内容。这里的相关性不是任务成功概率。刀和叉都与摆餐桌相关，也不代表可以把它们合并；论文明确指出了这类过度聚类风险。任务压缩的价值在于减少维护和使用表示的成本。如果成本可接受，没有必要因此丢弃原始证据。可以保留观测或 primitives，在上层按任务生成简化视图。存储的信息与当前决策使用的信息不必相同。这是可考虑的设计取舍，不是 Clio 已经验证的多视图方案。自由空间与房间层次Places 的构建Hydra 从深度和位姿增量构建局部 TSDF 与 ESDF：TSDF 支持表面重建，ESDF 提供位置到最近障碍物的距离。障碍判断主要依赖几何，不必先识别物品类别。系统在 ESDF 更新过程中提取广义 Voronoi 图（GVD），即由与多个障碍表面等距的位置构成的空间骨架，再将其稀疏化为 places 图。节点保留自由空间中的代表位置，边表示直线可通行关系。如果直线过度偏离原骨架，就插入中间节点。深度与位姿 → 局部 TSDF \u002F ESDF → GVD → places 图place 不是厨房、办公室等语义区域，也不只是机器人已经走过的轨迹点。它来自已观测自由空间。未知空间不能当作已确认可通行的区域。房间划分与物体归属Hydra 利用空间瓶颈划分房间：膨胀障碍物会先关闭狭窄的门口，使自由空间分成多个连通部分。在 places 图中，只需删除到障碍物距离不足的节点，就能近似实现这种操作。算法尝试多个膨胀距离，选择较稳定的连通分量作为房间核心，再通过带种子的社区发现分配其余节点。物体和 agent 节点则连接到活动窗口内最近的 place。物体归属可以沿下列关系查询：物体表面 → 物体节点 → place → 房间 → 建筑Hydra 这篇论文划分房间，但不自动将其命名为厨房或卧室。开放式功能区没有明显空间瓶颈，也可能无法分开。最近 place 规则还可能跨越薄墙，门口物品或跨区域家具的归属并不总是可靠。Clio 复用 places 构建方法，并用能够看到该位置的图像 CLIP 特征均值描述 place，再沿 places 图进行任务驱动聚类。因此 region 可以是没有墙壁隔断的工作区或小厨房区。正文没有详细展开物体到 region 的跨层归属规则，不能假定存在精确的区域包含判断。在自己的表示中，可以将观察关系、区域归属和交互位置分开。机器人从一个房间透过门看到物品，不意味着物品属于当前房间，也不意味着当前位置适合操作它。导航与计算成本Clio 的实机实验用语言匹配目标物体，再通过 Dijkstra 在 places 图中搜索路线。图搜索提供的是经过哪些位置，不是底层运动指令。机身尺寸、地面与落差、转弯约束、动态障碍仍需要导航和控制模块处理。三维地图不是在完整重建后一次性提取路径，而是边观测边更新。Hydra 用局部体素窗口限制计算，保留提取出的网格与图结构；局部感知较快，房间检测和全局优化异步运行。从成本上看，稀疏图搜索通常不是主要问题。需要衡量的是深度融合、距离场更新、视觉特征提取和全局维护：Hydra 的几何融合、places 提取和图优化主要运行在 CPU，依赖内存与带宽。二维语义分割以及 Clio 的 FastSAM、CLIP 主要使用 GPU。论文没有完整的资源利用率与功耗剖析，不能仅凭几个模块的耗时断言整套系统由 CPU 或 GPU 主导。局部扩散策略可以生成避障动作，但如果输入只有当前观测和目标方向，就未必知道如何绕过视野外的墙或避开死胡同。长期空间记忆、路线选择和局部动作生成是不同职责；它们可以组合，不必在扩散模型与场景图之间二选一。对于局部策略能够可靠完成的区域，只保留门口、分叉和关键位置可能已经足够。是否需要更密的 places，取决于相邻节点间的导航难度，而不是场景图必须具有固定层级或密度。策略驱动拓扑图一种替代几何构图的方案是利用局部策略提出路径，再根据执行结果扩展图。此时边表示“机器人及其策略在给定条件下能够完成这次移动”，而不只是几何上存在自由通路。预测与验证需要分开：策略生成路径 → 候选连接 当前深度或局部障碍检查 → 允许执行 实际成功通过 → 已验证连接节点可以放在门口、分叉、关键转弯、新区域入口或局部策略有效范围的边界，不必沿每个预测轨迹点建节点。实际轨迹构成已验证的连接，预测路径只提供扩展候选。仍需定位或地点识别，才能避免重复建图并建立回环。这可以减少对全局稠密重建的依赖，但不能省略局部安全检查，也不能直接得到完整的自由空间拓扑：模型未生成某条路径，不代表该处不可达。曾经成功通过的路，之后可能被人或关闭的门阻塞。A 到 B 可达不保证反向可达，连接可能需要方向与朝向条件。更换机器人或局部策略后，已有连接可能需要重新验证。边可以记录成功与失败次数、最近验证时间和临时阻塞状态。方案是否更省算力，需要将候选路径采样、定位和安全检查一起计入；大量扩散采样可能只是把成本转移到 GPU。应与几何拓扑比较导航成功率、探索覆盖、更新成本和错误恢复能力，而不只比较建图时间。探索与观测覆盖实际轨迹只记录走过的位置，不足以判断探索完成。站在门口看到半个房间，并不代表已经进入房间或检查了遮挡后的空间。需要分别记录：访问状态：哪些节点到达过、哪些边通过过。观测覆盖：传感器看到了哪些区域。待探索分支：未进入的通道、未确认的空间和需要补充视角的位置。Hydra 的轨迹与回环机制可以识别重访，但不负责判断探索是否充分。两篇论文都没有完整提出自主探索目标选择和停止机制。Clio 实机实验的建图阶段由人遥控，不能作为自主探索的验证。策略驱动拓扑需要额外记录未探索分支，否则目标导向的轨迹可能一直遗漏侧面的通道。可以从局部地图的已知／未知边界提取候选，再将其保存为拓扑状态。只检测语义上的门口不够，因为开放通道也可能通向未知区域。地图纠错定位漂移与重复结构机器人绕行后再次到达同一地点，里程计可能已产生偏差，使同一房间或物体被重复建图。Hydra 利用 places 的几何统计、物体类别统计和图像外观寻找回环候选，再通过视觉特征或物体配准验证相对位姿。确认约束后，系统优化由机器人位姿、网格控制点和 places 组成的变形图，使回环对齐，同时尽量保持局部结构。误差沿轨迹分布，并非统一平移整张地图即可消除。修正会传播到其他层：优化位姿、网格控制点与 places → 插值调整完整网格 → 重算物体质心与包围盒 → 合并重叠的 places 和同类物体 → 重新检测房间因此，回环不仅修正机器人轨迹，也可能改变物体身份和房间划分。错误回环与合并撤销相似房间可能造成错误回环。Hydra 使用 GNC 鲁棒优化识别与其他约束不一致的回环，并保留未合并的图版本。如果原先接受的回环后来被判为异常，就可以撤销相关的节点合并。这种恢复能力针对回环及其引起的结构合并，不是所有感知错误的通用回滚机制。持续的语义误识别、分割错误、真实物体移动，以及不合理的功能区划分，都不一定能通过几何优化解决。Clio 的受影响分量重新聚类，也不能替代这套一致性维护机制。对于策略驱动拓扑，仍需要处理重复地点、错误匹配及位置修正后的物品归属变化。可以保留原始观测与执行记录，将节点对应、合并和区域归属作为可修改的关联。直接用合并结果覆盖证据，会使后续纠错失去依据。参数与实验数据关联参数Hydra 的局部 TSDF \u002F ESDF 窗口半径为 8 米。局部处理受到窗口约束，但房间检测与全局优化仍受地图规模影响。Clio 物体实验的 track 视觉相似度阈值为 0.9，三维 IoU 阈值为 0.6；Khronos 对照设置为 0.7 和 0.4。这些是跨帧关联参数，不是任务聚类阈值。Clio 的物体 null task 阈值为 0.23；区域评估中设为 0，因为所有 places 均应对应某个提供的房间标签。Clio 在线物体聚类只重算受新观测影响的连通分量。若分量很大，增量更新仍可能昂贵，不能理解为计算量始终恒定。Hydra在 Xavier NX 上处理 uHumans2 Office 场景时，物体层、places 层和房间层分别耗时 75 ± 35、33 ± 6、55 ± 41 毫秒，目标更新频率为 5 Hz。这是嵌入式硬件上的仿真数据测试，数据提供深度和语义分割。分模块计时不等于相机到动作的端到端时延，也不能证明完整视觉系统在该硬件上达到相同频率。主要工作站为 24 核 Ryzen 9 3960X 与两张 RTX 3080。真实 SidPac 数据的两组场景中，物体层平均约 50–75 毫秒，places 约 3–4 毫秒，房间约 11–15 毫秒。真实数据主要由手持设备采集，不是完整自主导航实验。Clio使用 i9-12900K、RTX 3090 与 CLIP ViT-L\u002F14 时，Clio-online 的每处理帧平均耗时为：Cubicle 0.30 秒、Office 0.29 秒、Apartment 0.26 秒。在线运行允许丢帧，这些数据不是 Spot 上测得的控制频率。Spot 实验使用 i9-13950HX、64 GB 内存和 RTX 4090 Laptop GPU。统计的 7 轮、21 次抓取尝试中，笔记本位于机器人外，通过 Wi-Fi 连接；补充视频另展示了携带计算设备的连续运行。它证明了机载在线运行的可行性，不代表低算力或低功耗。21 次尝试的抓取成功率为 57%；排除 Spot 对正确目标的实际抓取失败后为 71%。图中选错目标只有一次。这不是纯导航成功率或碰撞安全率，建图阶段也由人遥控完成。原文定位页码为 PDF 顺序页，本地原文位于 assets\u002Fpapers\u002Frobots\u002F。Hydra: A Real-time Spatial Perception System for 3D Scene Graph Construction and Optimization，RSS 2022。§III，第 3–4 页：网格、物体、places 与房间构建。§IV，第 5–6 页：跨层关联、回环、变形图与合并撤销。§V–VI，第 6–9 页：架构、数据来源与计时。§VII，第 9–10 页：房间命名与关系表达的限制。Clio: Real-Time Task-Driven Open-Set 3D Scene Graphs，RA-L 2024，正式版本。§III–V，第 3–4 页：信息瓶颈、任务相关性、前后端与候选合并。§VI-A、Table I，第 5 页：参数、运行时间与任务过滤基线。Fig. 4–5、§VI-C，第 6 页：粒度变化与区域划分。§VI-D、§VII 及脚注，第 7 页：实机条件、遥控建图、任务结果与局限。",[127,128,129,130,131,132],"机器人","场景图","Hydra","Clio","导航","学习笔记",{"slug":26,"title":27,"description":28,"plainText":134,"keywords":135},"安装过程得益于OpenSUSE的Yast，安装过程可以说是相当的舒服，全部图形化操作，是我在安装过许多Linux发行版中，最方便最舒服的。这里就不详细写出安装步骤了（其实是我没有记录过程），但会提供官方文档。系统盘的制作这里我是不按官方的来了，中文wiki的方法其实已经不是很方便了，虽然谈不上过时，但对于小白来说相当复杂，而且不容易成功。这里我推荐新一代多系统启动U盘的方案——Ventory对于Ventory的使用也是相当简单，只需选择u盘，然后安装即可。然后再将相应的系统镜像拖入u盘即可。这里我使用的是OpenSUSE的Tumbleweed，这里比较推荐下载离线安装的版本，毕竟如果出现无法连接的时候就尴尬了。后面将不会再次说明OpenSUSE的版本，在本博客文章中，默认均为Tumbleweed。OpenSUSE的安装要想使用Ventory需要关闭bios的scurity boot功能。对于系统的安装就不详细说明了，OpenSUSE有许多其他发行版都没有的设置，极大丰富了自定义程度，具体可以看官方文档，或者可以参考别人的教程。安装时其实不是很建议选择中文和中文键盘布局，因为这样会导致很多问题，比如中文路径的问题，毕竟Linux对中文的支持也不是很好。这里主要讲述分区问题，个人比较建议使用专家模式进行分区。虽然官方推荐他默认的分区模式，但是对于一个成熟的Linux玩家，熟悉分区应当是基本操作。虽然专家分区名为专家，但其还是相当傻瓜的，甚至都不用手动选择挂载。这里我们选择专家模式后，他还会提供两个选项。一个是基于OpenSUSE提供默认设置来分区，一个是基于原本的分区来配置。如果要安装双系统且已经分好空闲容量建议后者。专家模式外观大致如下：虽然我在此较为详细讲述分区，但其实分区也没什么好说的，基本是二大二小的组合。二大指的是系统分区和数据分区，二小指的是EFI分区和交换分区。通常而言，建议分区顺序为EFI分区、系统分区、数据分区、交换分区，这样较为方便后续后悔时调整（虽然出现需求时大概率还得重装）。EFI分区：通常默认512MB系统分区：建议比数据盘大，因为软件基本都装在系统盘数据分区：个人喜好分配即可交换分区：建议与内存同等大小或偏大对于这四种分区，OpenSUSE都提供了相关的选项，对于玩家来说只需要选择文件系统类型，如：Brfs、etx4等，和设置分区标签（分区名字）。详细步骤：先删除所有分区EFI分区除外，再新建分区，根据上面的说明和个人需求及喜好分配硬盘空间，设置文件系统，设置分区标签。OpenSUSE的初步设置设置Wallet密码Wallet是KDE的一个组件，负责管理所有的密码和系统操作的授权。刚进入系统后，Wallet便会请求设置密码，选第一项后，设置好密码即可。中文支持由于在本博客中，默认使用英语作为第一语言，所以OpenSUSE不会安装中文字体，这会导致一些显示的问题。因此我们先要打开System Setting - language，在添加第二个语言，他便会自动下载中文字体并安装。重启后便可正常显示中文字体。换源OpenSUSE的换源相对麻烦，如果能正常更新其实不建议换。并且由于OpenSUSE的包管理器，到现在仍然是单线程下载，换源对速度的提升也不大。如果实在要换源，请参考清华源的相关说明，注意OpenSUSE的版本。输入法与键盘布局在Linux中，输入法一般是由输入框架（如：ibus、fcitx）、前端插件、用户界面和输入法引擎（如：rime）等组成；在输入法引擎中又可以包含许多输入方案，比如：雾凇输入法、幸运草输入法等。详细可参考fcitx官方wiki。在Linux中，ibus适配gnome更好一些，fcitx适配KDE更好一些。我个人是使用KDE作为桌面系统（废话，谁用OpenSUSE还不用KDE啊），并且fcitx5对fcitx进行了一些改进，因此我使用fcitx5 作为基本的输入法框架。而使用Rime，则是因为他是Linux最好的中文输入引擎，我觉得任何人应该都不能忍受默认自带的傻瓜中文输入引擎。雾凇输入法则是目前Github最受欢迎的中文输入方案。输入法框架与引擎由于不同输入框架会引起未知的冲突，因此我们先将OpenSUSE的输入框架卸载。sudo zypper remove ibus fcitx然后我们再安装fcitx5和rime的输入引擎。sudo zypper install fcitx5 fcitx5-rime雾凇输入法的安装对于雾凇输入法的安装其实相对简单，但是有大佬写了自动安装和自动更新脚本，那我们大可不必费力去折腾。先保证OpenSUSE中安装有Git和Ruby。sudo zypper install git ruby再在你喜欢的位置克隆脚本。git clone --depth=1 https:\u002F\u002Fgithub.com\u002FMark24Code\u002Frime-auto-deploy.git --branch latest进入项目目录。cd rime-auto-deploy执行自动化脚本。.\u002Finstaller.rb选择fcitx5框架，再选择自动安装模式，启动fcitx5，便可发现已完成雾凇输入法的安装。输入法相关设置到此可能很多人就觉得完成了，但实际上并没有，你会发现每次重启都需要手动启动fcitx5，并且有些软件无法使用输入法。这主要是由于一些软件十分固执，始终不支持新的前端插件。因此我们需要进行进一步的设置，值得注意的是wayland与x11显示管理的设置并不相同，请以官方wiki为准。在OpenSUSE中，默认使用x11，因此我们有许多方法可以进行设置。在这里我仅使用一种方法。先在home新建.xprofile文件，并编辑（这里我用vim，不懂使用的可以使用其他文本编辑器）。vim ~\u002F.xprofile向该文件添加以下内容，强制软件使用fcitx5框架，这里填fcitx主要为了兼容性。XMODIFIERS=@im=fcitx GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx重启后即可正常使用。输入法皮肤可以自行搜索皮肤主题安装，再通过fcitx的设置的ClassicUI进行皮肤和字体的相关设置。键盘映射的修复如果你是键圈的人，你便会发现，自己的键盘似乎每次重启都需要开启数字锁定，并且键盘上方的F区（功能键）变成了媒体键，这很令人烦躁。数字锁定这个问题，只需在设置里的input - keyboard中设置一下便可完成。功能键映射修复也许有些人喜欢媒体键更甚一点，但也有人不喜欢媒体键，我个人便是其中之一。这主要是由于目前的键圈大部分都用的是开源的方案，而Linux并没有对此方案进行适配。对于此情况的解决方案ArchLinux的Wiki有更加详细的解释，在此仅提供解决方法。echo 2 | sudo tee \u002Fsys\u002Fmodule\u002Fhid_apple\u002Fparameters\u002Ffnmode echo \"options hid_apple fnmode=2\" | sudo tee \u002Fetc\u002Fmodprobe.d\u002Fhid_apple.conf基本软件的安装在Linux中，很多软件并不提供rpm包（其实只是大部分国产软件不提供），并且对不同发行版的依赖问题也没有很好的处理。为解决不同发行版的软件包的不相互兼容的问题，一些公司和开发者提出自己的解决方案，比较出名的有：appimage、snap、flatpak。其中appimage就是将软件及其依赖直接打包为可执行文件，snap和flatpak都是运用容器化原理安装软件及其依赖。但这些解决方案都意味着每个软件将会比rpm所占空间更大，因为他们会把依赖也带入软件中（其实也没多大事，Windows不就一直这样吗）。但由于snap的限制较大且不开源，只能从官方源安装软件，下载速度相当慢，因此我个人不是很喜欢snap。考虑到KDE对flatpak 的支持也相对较好，因此在本博客中，对于不提供rpm的软件将会使用flatpak和appimage来代替，最不得已才考虑编译。Flatpak初始化检查系统是否已经安装Flatpak。sudo zypper install flatpak为Flatpak添加官方的Flathub源flatpak remote-add --if-not-exists flathub https:\u002F\u002Fdl.flathub.org\u002Frepo\u002Fflathub.flatpakrepoQQQQ官方实际上提供了rpm包，但官方很明显没有很好考虑不同发行版的依赖问题，如果OpenSUSE直接安装会报错。与此同时，本地安装的rpm包还意味着无法通过包管理器进行自动更新。因此我们考虑使用flatpak安装QQ。flatpak install flathub com.qq.QQ微信你在想屁吃呢？懂不懂微信团队的含金量啊。QQ桌面端三人团队，重写外观，跨平台支持；百人微信团队，屁都没有，究极摆烂。腾讯会议flatpak install flathub com.tencent.wemeet百度网盘百度说实话，能别装就别装，真的无语。别看官方提供了rpm和deb的软件包，但如果你使用的系统更新的较为频繁，那这玩意基本各种不兼容，安装了也是白装，而且一直没改。因此对于这种希望让用户擦屁股的厂商，我们完全不用客气。flatpak install flathub com.baidu.NetDiskEdge其实我本来不想用Edge的，但Firefox的多端同步，我不知道怎么回事一直无法正常同步；而且Firefox的插件还是太少了，无法满足我的正常需求。只能被迫使用Edge + Firefox。在这里我们导入微软的RPM签名并添加Edge的官方源，刷新一下获取软件列表后，就可以使用包管理正常更新和下载了。这里我安装的是正式版，当然你也可以安装Edge的测试版。sudo rpm --import https:\u002F\u002Fpackages.microsoft.com\u002Fkeys\u002Fmicrosoft.asc sudo zypper ar https:\u002F\u002Fpackages.microsoft.com\u002Fyumrepos\u002Fedge microsoft-edge sudo zypper refresh sudo zypper install microsoft-edge-stable",[136],"Linux",{"slug":34,"title":35,"description":36,"plainText":138,"keywords":139},"Introduction——前言>typora写作是很舒服，但是到了图片上传我简直太难了。上一篇博客我说我图床用的是imgchr，现在我屈服了，玩博客用imgchr简直是魔鬼好吧，图片上传完了，居然是乱序上传，导致我很难改图片地址。又因为imgchr每小时30张（根据服务器负载情况调整，大部分图床都有限制），而我图片都是一次全传完，所以就免不了被限制。 > >所以后来我就找了个法子——typora + picgo，自动上传图片，这样就可以避免图片都是同一时间传，本来我是想自己开发picgo + imgchr 的插件的，研究了半天，发现imgchr没有api，这就很尴尬了，所以最后只能选择SM.MS作为博客的主要图床了。 > >——2020\u002F04\u002F18时过境迁，物是人非，没想到没过几年这个教程就有些过时了；也没想到 smms 那么快就被墙了；更没想到 Typora 居然收费了。由于学业停更两年的我，终于又回归了。我在原来教程的基础上增加了Obsidian的设置，以及smms被墙的一个解决方案，其实这也是我重新开始所遇到的问题，希望对后来者有一定的帮助吧。Preparation——前期准备Obsidian新晋之星，Markdown 编辑器的清流。对个人不收费，但对团队收费。他对于多文档管理非常友好，但也因此对单文档异常不友好，必须新建库，才能编辑文件。但由于其携带大量插件，使得功能极大丰富，写作的舒适程度也不亚于 Typora ，也是一款能与传统笔记本界对抗的笔记软件。其特色功能——双向连接，使其具有 Notion 的风味。用来做大型教程或者官方文档翻译十分顺畅。Typora这个就不用多说了吧，Markdown写作神器，无需知道任何 Markdown 语法也能用的 Markdown 编辑器。与Obsidian较大的差别可能是付费且无双向连接，不太适用多个文件管理，但对于单个文件的处理较好。Node.jspicgo有两个版本，一个是CLI版本一个是GUI版本的(CLI版本即PicGo-Core，GUI版本即PicGo.app)。而我使用的是CLI版本的，也就是命令行版本的，可以减少常驻内存的占用。注意：根据Picgo官方文档的描述，要求Node.js 版本 >= 8。“##### PicGo.app和PicGo-Core之间的区别（命令行）”>“目前，PicGo（应用程序）仅中文。 - PicGo.app提供了一个GUI，因此与CLI版本相比更易于设置。 - 使用PicGo-Core（命令行）进行上传会消耗较少的计算资源，因为该过程仅在上传过程中运行，并且在上传成功或失败后将退出。通过PicGo.app上传时，PicGo.app将始终保持运行状态，不会自动退出。此外，PicGo.app是电子应用程序，它消耗了更多的计算资源。 - PicGo.app和PicGo-Core使用不同的配置文件，但是您可以将picBedPicGo.app的配置文件中key 下的json对象复制到PicGo的配置文件中。 - PicGo.app提供其他功能，例如上传历史记录，自动重命名等。”>“——typora官网文档”Picgopicgo-core以下简称picgo。安装在任意目录打开Git Bash或者cmd等等，输入命令安装。yarn global add picgo ## 或者 npm install picgo -g觉得安装慢的，我上一篇博客也有讲到，将 npm 切换到淘宝源或其他可用源即可。检验picgo -h ## 或者 picgo -v输出如图所示即为安装成功。“PicGo 本体支持如下图床：”>“ 微博图床 v1.0 微博图床从 2019 年 4 月开始进行防盗链，不建议继续使用 - 七牛图床 v1.0 - 腾讯云 COS v4\\v5版本 v1.1 & v1.5.0 - 又拍云 v1.2.0 - GitHub v1.5.0 - SM.MS v1.5.1 由于官方不再支持V1版本，暂时请使用smms-user插件 - 阿里云 OSS v1.6.0 - Imgur v1.6.0”>“本体不再增加默认的图床支持。你可以自行开发第三方图床插件。详见 PicGo-Core。”>“——PicGo”“为 PicGo 开发的一款插件，新增了SM.MS注册用户 图床。 使用SM.MS V2的API上传，适用于注册了SM.MS的用户。填写Authorization即可”>“——smms-user作者”由于 Picgo 本体不支持 SMMS API v2，因此我们得自行安装一个插件来解决此问题。根据PicGo文档的描述，我们得用picgo install [name]来安装插件，其中 [name] 代表的是插件名注意：picgo的插件普遍以picgo-plugin-[name]来命名，但安装插件时[name] 不是 picgo-plugin-[name]，它不需要前面的那一部分。上面也说了，本体自带的smms不能用了，想要用SM.MS图床就得安装smms-user插件。安装smms-user插件，只需执行picgo install smms-user安装即可。安装完后，默认是开启插件的，无需另外操作。SM.MS所以我们需要一个SM.MS的密钥。可以百度搜索，或直接进入SM.MS的官网，注册一个账号。登录后，进入个人的设置页面。进入API Token页面，生成密钥，新注册的是没有密钥的。生成完后就会出现在Secret Token里面。Setting——配置Picgo 配置方法一如果不想用 SMMS 可以通过 picgo use 切换图床，在此不详细说明。在命令行中输入 picgo set uploader 配置 SMMS API Token。上下方向键选择，回车确认，再根据提示填写密钥即可。不会，多尝试，大不了Ctrl + C 中断，再来一遍。Picgo 配置方法二在个人用户的文件夹进入 .picgo 文件夹，打开 config.json 进行配置。修改如下，注意将Authorization的参数换为SM.MS的密钥，即将Secret Token替换为密钥。{ \"picBed\": { \"current\": \"smms-user\", \"uploader\": \"smms-user\", \"transformer\": \"path\", \"smms-user\": { \"Authorization\": \"Secret Token\" } }, \"picgoPlugins\": { \"picgo-plugin-smms-user\": true } }关于 SMMS 被墙的解决方法由于某种不可抗力，sm.ms 目前被墙。但作者后来上架了备用域名，即 smms.app 。插件默认是使用备用域名的，但如果仍无法正常上传，那你可能需要修改一下 picgo 的配置文件。打开 picgo 的配置文件。搜索 sm.ms ，将网址更换为 smms.app 即可。Obsidian 的配置Obsidian 并没有自带图片上传的功能，我们需要自行去社区下载。打开 Obsidian 的设置，关闭安全模式，即可打开社区插件市场。如果你无法正常浏览插件，那么你可以安装ProxyGithub插件实现正常访问。GitHub 打不开也可以用 Gitee 。搜索 Image Auto Upload Plugin 安装插件，当然你也可以像 ProxyGithub 那样手动安装。最后将插件的默认上传器修改为 Picgo-Core 即可，如果你并非默认目录安装 Picgo ，那请记得修改路径。Typora打开typora，进入偏好设置。进入图像选项，将 插入图片时… 更改为上传图片，勾选对本地位置的图片应用上述规则、 插入时自动转义图片URL 。至于第二项我不推荐勾选，因为当你写博客时，想将上传好的图片更换位置，一复制一粘贴，它又给你上传了一遍…当然，如果你想用我也不会阻拦。“If you have “node” and “picgo” installed in system PATH directly, you could also fill “picgo upload” as the custom command directly.”>“——typora官方文档”根据官方文档的描述：因为我不是使用typora内置下载的picgo CLI版，所以我们得自定义命令。我们需要将上传服务改为Custom Command，然后在自定义命令里输入picgo upload 。至此所有的配置都完成了，之后就可以愉快的使用了。Usage——使用官方文档其实是有说明如何使用的，不过我还是演示一下吧。截图截图粘贴需要手动上传，当然啦这可能是为了避免我之前说描述的情况吧。拖拽拖拽就不用手动上传了，他会自动上传",[122,140],"Obsidian",{"slug":30,"title":31,"description":32,"plainText":142,"keywords":143},"Windows + Deepin 单硬盘双系统的安装与使用说实话我受够了Windows的环境搭建，想要精简就极度繁琐，想要方便就得用宇宙IDE，占十几二十多GB存储。因此，Linux就是最好的选择，除非你需要写Win或Mac的软件等特定情况。那为什么选择 Deepin ？首先因为它可以较为方便的下载和安装 Linux 软件，省去了较为麻烦的适配和编译过程。其次是因为它比较符合 Windows 的使用习惯，UI风格偏向于Mac，较为美观。我觉得作为一个初学者，Deepin 确实算是一个较为不错的选择。至于为何不用虚拟机。它虽然安全且方便，但由于性能的限制，会导致运行起来时不时卡顿，这实在谈不上是一个良好的体验。且似乎会导致物理本机性能下降（存在争议），因而不考虑用虚拟机。说是双系统，其实就是装系统，只不过多了一些步骤罢了。关于单硬盘双系统与多硬盘多系统。值得说明的是硬盘不等于磁盘，硬盘可以被分为多个磁盘，这个过程也就是所谓的分区。硬盘是物理上的，磁盘是硬盘被虚拟地划为多个区域，分别进行管理。多硬盘多系统指的是将多个系统装在大于等于两个硬盘内。系统的启动需要引导程序，每个系统都会有一个引导程序。一般而言，引导程序能够检测到同一硬盘下的所有系统，而难以检测到不同硬盘下的系统，因而多硬盘多系统需要额外的配置。（如果感兴趣可以自行搜索双硬盘双系统引导）准备相关物品及文件U盘对于速度无要求，但容量至少8GB以上，以保证能完成启动盘的制作。Deepin 的系统镜像Deepin 官网下载系统镜像。如果你不介意 bug 和生态，也可以下载预览版。Deepin 启动盘制作工具同样在Deepin 官网下载启动盘制作工具。启动盘的制作选择系统镜像。选择U盘和格式化，注意备份。然后就可以制作启动盘。系统盘分区使用分区助手对硬盘进行分区。（也可使用其他分区软件）具体容量建议为 32GB+32GB+8GB 以上。其中 32G 为系统盘，另外的32GB是资料盘，8G 为交换分区。交换分区是 Linux 的虚拟内存，一般容量与内存相同，内存 16G 以上可不设，但有交换分区可以使用休眠功能，可加快 Linux 启动速度，这取决于你用不用休眠。可以先拆分出一个分区。再将该分区删除。即可得到一个空白分区。Bios 的设置插入启动盘，启动时按 Delete 进入 Bios 界面。（不同主板有不同按键，请自行上网查询）进入启动界面，更改启动顺序，将启动盘启动顺序改为第一位，保存并重启即可正常进入启动盘安装 Deepin 。或者按 F12 手动选择启动盘启动。（不同主板按键不同）安装 DeepinDeepin 毕竟是国产系统，对中文支持比较完善，选择简体中文即可。硬盘分区部分选择手动安装。选择空白分区后，点击右方的编辑按钮。先建立一个 efi 引导分区。大小最低 300MB 即可。>其实不建立 Deepin 的引导分区也是可以的，但如果 Windows 的引导分区废了，那很可能两个系统都会无法启动。而且如果后续你还要安装 macOS 或者 Ubuntu ，可能会导致 Deepin 无法被引导程序发现。总之在国内互联网少有相关的资料的情况下，不建议这样做。建立一个交换分区。大小等同于内存大小。系统盘则分配 32GB，挂载在根目录，文件系统选择 ext4 。>其实剩下的都可以分配给系统盘，但我建议还是分一些给数据盘。这样系统盘崩溃后，不至于重装系统后，还要配置一次软件。剩下的分配给数据盘，挂载在 \u002Fhome 目录下。文件系统可以选择 ext4 或者其他，这里我想体验一下其他文件系统，因而选择 btrfs 。如果一切正常就点继续安装即可。但注意不要勾选集成NVIDIA闭源驱动，后面我们自己安装即可，这玩意存在问题。如果你不需要休眠功能，且内存也足够，并没有分配交换分区，则会出现提示，忽略即可。安装完成后，再次进入Bios，将硬盘启动顺序改为 Deepin 优先。初始化 Deepin重启后，在Grub界面等待几秒，自动进入 Deepin 。选择语言，简体中文，并勾选同意协议。键盘布局同样简体中文。时区选择北京。选择头像，输入用户名等信息。待其初始化完成后即可。Grub 美化可以在 gnome-look 上的 GRUB themes 上找到自己喜欢的主题。下载后根据说明进行安装即可。（基本就是运行脚本就可自动安装，除非你想自行安装）>如果不想切换终端工作目录，那就在主题目录右键打开终端。初识Linux的可以查看主题 README 给出的相关命令。如果无法下载，可以去寻找其 GitHub 镜像，常在标题处见。NVDIA驱动安装直接去官网上选择linux版下载即可。",[136],1793635305978]