3.5 Searching Applications:集合、字典、索引与稀疏计算

3.5 · Searching Applications覆盖 6 个作者站正式主题,以章专属状态模型、逐步轨迹、反例恢复和独立预言机验收。

学习目标

  • 能解释“3.5 · Searching Applications”如何把集合、字典、倒排索引、稀疏向量与系统符号表归结为键值操作组合
  • 能逐项核对 查找应用、集合API、字典客户端、索引客户端、稀疏向量与矩阵、系统符号表,并区分作者站内容与本页独立补充
  • 能按“稀疏向量点积可按较小非零集合迭代,成本 O(nnz_small × lookup)”手算一个最小输入,逐步检查“应用结果必须与所选 Set/Map 语义一致,缺失键与显式零值不得混淆”
  • 能注入“在遍历索引时原地修改同一符号表,或把重复词频误压成集合存在性”,保存基线、首个分叉、恢复和同输入重放证据

来源、版次与独立重写边界

“3·5 · Searching Applications”对应 Robert Sedgewick 与 Kevin Wayne 的 Algorithms, Fourth Edition(Addison-Wesley Professional,2011)。对这一节,作者维护的本节页面提供与教材协同的浓缩正文、Java 实现、图示、习题和部分答案;全书作者站给出 6 章、30 节的完整结构,并明确区分在线资料与纸质教材的学习用途。

“3·5 · Searching Applications”的作者页公开经授权的在线节选和配套资源,但不是整本教材全文。因此“3·5 · Searching Applications”采用 independent-rewrite / authorized-sample:中文讲解、推导与实验独立组织,不声称逐段翻译;算法名称、API 和示例边界以作者页、官方代码索引官方勘误交叉核对。

作者站章节坐标:3.5 · Searching Applications

  • 1. 查找应用:在本页通过“选择集合或映射”连接解释、交互状态和练习验收。
  • 2. 集合API:在本页通过“规范化输入键”连接解释、交互状态和练习验收。
  • 3. 字典客户端:在本页通过“构建正向或倒排索引”连接解释、交互状态和练习验收。
  • 4. 索引客户端:在本页通过“执行查询组合”连接解释、交互状态和练习验收。
  • 5. 稀疏向量与矩阵:在本页通过“核对应用语义”连接解释、交互状态和练习验收。
  • 6. 系统符号表:在本页通过“选择集合或映射”连接解释、交互状态和练习验收。

从“同一张表,不同的key/value含义”开始

查找应用(searching applications)不是在3.1到3.4之外再发明一种表。它要求先回答:client究竟需要membership、one value、a set of values、ordered range,还是只存nonzero coordinates;再选择满足contract的API与implementation。

先预测“电影到演员”索引能否直接回答“某演员演过哪些电影”。若只建立 movie -> SET<person>,reverse query必须扫描所有movies。要让两个方向都按key查询,构建阶段必须同步维护 movie -> peopleperson -> movies 两张ST;额外space换来query cost从全量扫描降为一次lookup。

官方3.5依次展示set APIs、dictionary clients、indexing clients、sparse vectors and matrices与system symbol table。每个案例都可用同一四步读法:

  1. 定义key equality与value meaning。
  2. Build phase把external data转成associations。
  3. Query phase只通过API观察结果。
  4. 让implementation choice匹配所需ordering与cost guarantee。

3.5.1 Set APIs:只保留keys,不伪造dummy values

集合API(set APIs)适用于client不关心associated value的场景。Duplicate add不改变set;contains只返回membership。

典型stream clients只差一个predicate:

  • DeDup:第一次看到key时输出并add,以后contains为true就跳过。
  • Allowlist:policy SET contains才输出。
  • Blocklist:policy SET不contains才输出。
SET<String> seen = new SET<String>();
while (!StdIn.isEmpty()) {
    String key = StdIn.readString();
    if (!seen.contains(key)) {
        seen.add(key);
        StdOut.println(key);
    }
}

Set union遍历两边并add到result;intersection只需遍历一边并对另一边contains。若两set规模为A与B,应遍历较小者,成本约:

T=Θ(min(A,B)Ccontains)T_{\cap}=\Theta(\min(A,B)\cdot C_{\mathrm{contains}})

Ordered SET还可提供min/max/floor/ceiling;unordered HashSet不能承诺iteration顺序。API命名相似不代表capabilities相同,client若需要range就必须把ordering写入contract。

3.5.2 Dictionary clients:build once,lookup many

字典客户端(dictionary clients)是最直接的ST应用。LookupCSV从command line取得file、key field与value field,逐row提取association,再读取queries:

int keyField = Integer.parseInt(args[1]);
int valField = Integer.parseInt(args[2]);
ST<String, String> st = new ST<String, String>();
 
while (in.hasNextLine()) {
    String[] tokens = in.readLine().split(",");
    st.put(tokens[keyField], tokens[valField]);
}
 
while (!StdIn.isEmpty()) {
    String query = StdIn.readString();
    if (st.contains(query)) StdOut.println(st.get(query));
    else                    StdOut.println("Not found");
}

Key field不是无害configuration:若file里key重复,standard ST语义让later row覆盖earlier value。若domain需要保留全部records,value应改为list/SET,或duplicate直接报错。CSV example用简单split展示algorithm,但production CSV含quoted commas、escaping与newlines,应使用真正parser。

Build cost是R次put,R为records;query cost是Q次contains/get。Official client先contains再get可能做两次lookup;若API能以nullable或optional result区分absent,可一次get,但必须先确认null-value contract。

Dictionary correctness不只测happy hit。要覆盖missing key、duplicate policy、empty field、malformed row、field index越界、encoding与normalization。若key需要case-insensitive,必须在build与query两侧使用同一canonicalization。

3.5.3 Indexing clients:value本身可以是SET

索引客户端(indexing clients)表面上是“一个key对应多个values”,实际仍遵守one value per key:value是一个集合。

FileIndex对每个file读取words,并维护 ST<String, SET<File>>。Term第一次出现时创建empty SET,随后add file;同一term在同一file重复出现不会产生duplicate posting。

if (!index.contains(word)) index.put(word, new SET<File>());
index.get(word).add(file);

Term query是一张posting SET;AND query是sets intersection;OR是union。Multi-word AND应先按posting size排序,从smallest set开始过滤。若要支持phrase query,仅保存file membership不够,value需升级为per-file positions;若要rank,需保存term frequency等metadata。

MovieIndex或LookupIndex展示bidirectional relation。输入一行movie与多个performers时同步更新:

movie{people},person{movies}movie\mapsto\{people\}, \qquad person\mapsto\{movies\}

两张表必须在同一record处理内一致更新。Round-trip certificate可检查:若person出现在movieToPeople[movie],movie必须也出现在personToMovies[person]。只更新一侧不会立刻crash,却会让reverse results静默缺失。

Index build还要定义tokenization、case folding、stopwords、Unicode normalization与document identity。Client logic正确但normalization不一致,仍会把视觉相同的term拆成不同keys。

3.5.4 Sparse vectors and matrices:absence就是zero

稀疏向量与矩阵(sparse vectors and matrices)把dimension d与nonzero count nnz分开。Dense vector保存d个numbers;SparseVector保存 ST<Integer, Double> 中的nonzeros,memory proportional to nnz。

put(i, 0.0)必须delete association,否则zero entries会逐渐填满ST并让“sparse”退化。Get missing index返回0:

public void put(int i, double value) {
    if (i < 0 || i >= d) throw new IllegalArgumentException();
    if (value == 0.0) st.delete(i);
    else              st.put(i, value);
}
 
public double get(int i) {
    if (st.contains(i)) return st.get(i);
    return 0.0;
}

Dot product定义:

ab=i=0d1aibi\mathbf a\cdot\mathbf b=\sum_{i=0}^{d-1}a_i b_i

Dense loop需要d次multiplication,即使绝大多数项为zero。Sparse-sparse实现遍历nnz较少的一侧,对另一ST做contains/get:

Tdot=O ⁣(min(nnz(a),nnz(b))Clookup)T_{\mathrm{dot}}= O\!\left(\min(nnz(a),nnz(b))\cdot C_{\mathrm{lookup}}\right)

若另一operand是dense array,只遍历当前sparse ST keys并直接访问 that[i],成本proportional to nnz。Vector addition先copy this nonzeros,再遍历that并put sum;sum变0时put contract自动删除。

Sparse matrix可表示为rows array,每row是一条SparseVector。Matrix-vector multiply对每row做sparse dot;matrix addition逐row plus。是否按row、column或双索引存储取决于operation mix,不能看到“sparse”就默认一种layout。

3.5.5 System symbol table:从client contract选择容器

系统符号表(system symbol table)让production code通常不必重写BST或hash table,但实现选择仍决定语义:

  • Java TreeMap基于red-black tree,exact/ordered operations worst-case logarithmic,并提供navigation/range views。
  • HashMap提供expected constant exact lookup,不保证sorted iteration。
  • TreeSetHashSet分别对应ordered与hashed membership。
  • Concurrent variants还改变atomicity、iteration consistency与null policy。

Textbook ST约定null value等价delete,而Java collections可能允许null key/value;不能把一种API的absence convention直接搬到另一种。Map.get()返回null时,若null values合法,必须用containsKey区分absent。

Implementation choice应从required operations倒推:

Client requirementSuitable API propertyTypical implementation
exact get/put onlyunordered mapHashMap
floor/ceiling/rangeordered navigable mapTreeMap
membership onlysetHashSet or TreeSet
one-to-many relationmap to set/listHashMap plus HashSet
sparse coordinate iterationmap index to nonzeroTreeMap or hash map by traversal need

Never依赖unspecified iteration order。Test在small dataset看起来保持insert order,换runtime、resize或hash seed后可能改变。若output必须deterministic,应显式sort或选择ordered collection。

3.5.6 Composition patterns:一张表常是更大结构的index

Searching applications经常把ST与另一结构组合,而不是把全部行为塞进ST:

  • LRU cache:ST从item映射到doubly linked-list node;list维护recency order。
  • Bidirectional lookup:两张ST维护正反关系。
  • Unique queue:SET记录ever seen,queue维护FIFO。
  • Indirect priority queue:ST定位item,heap维护priority order。
  • Concordance:ST把word映射到positions SET/list。

关键是明确每个结构拥有哪个invariant。LRU中ST负责O(1) locate,list负责oldest/newest;任何access必须同时更新两者。组合结构应有cross-certificate,而不是只测各容器内部合法。

Space也应按logical relation计算。Inverted index至少保存unique term-file pairs;bidirectional index保存每条edge两次;sparse matrix保存nonzero coordinates与per-row overhead。为了query latency复制index是有意识的tradeoff,必须在ingestion mutation中同步维护。

统一验收:从domain relation回到API observable behavior

先预测,再操作三个本节实验

分步1 / 3

1. 对象、操作与成本模型

先在“3.5 · Searching Applications”的两个最小情境间切换,再逐项选择正式概念。预测“稀疏向量点积可按较小非零集合迭代,成本 O(nnz_small × lookup)”在哪个前提下成立,并解释输入、操作和证书之间的关系。

Section model

3.5 · Searching Applications:对象、操作与不变量

把集合、字典、倒排索引、稀疏向量与系统符号表归结为键值操作组合

选择最小情境

切换正式概念

输入合同操作证书algs4-3.5 · 先给前提,再执行,再验收当前概念:1/6
倒排索引
文档 d1=[A,B]、d2=[B,C],查询 B
当前观察
searching applications返回 postings d1 与 d2,并保持文档标识去重规则
稀疏向量点积可按较小非零集合迭代,成本 O(nnz_small × lookup)

本节易错边界与可重放合同

练习与答案

练习

问题 1:目录与状态映射。 对下列正式概念逐项指出正文解释、交互状态和练习证据:

  • 查找应用:在实验 1 中指出对应状态,并写出一个通过条件。
  • 集合API:在实验 2 中指出对应状态,并写出一个通过条件。
  • 字典客户端:在实验 3 中指出对应状态,并写出一个通过条件。
  • 索引客户端:在实验 1 中指出对应状态,并写出一个通过条件。
  • 稀疏向量与矩阵:在实验 2 中指出对应状态,并写出一个通过条件。
  • 系统符号表:在实验 3 中指出对应状态,并写出一个通过条件。

问题 2:最小推演。 怎样证明“稀疏向量点积可按较小非零集合迭代,成本 O(nnz_small × lookup)”不是孤立结论?

问题 3:故障恢复。 怎样证明“在遍历索引时原地修改同一符号表,或把重复词频误压成集合存在性”已经修复?

本章回顾

  1. Searching application先定义domain key/value relation,再选择ST或SET implementation。
  2. SET适合membership、dedup与filter,set algebra通过iteration加contains组合。
  3. Dictionary client分build与query两阶段,duplicate key policy必须显式。
  4. Index把key映射到SET/list;reverse query需要reverse index,不能凭空从forward ST得到。
  5. File inverted index的multiword query是posting sets的intersection,先处理smallest posting更高效。
  6. Sparse vector以absence表达zero,memory和arithmetic work随nnz而非dimension增长。
  7. TreeMap/HashMap等system symbol tables的ordering、null与cost contracts不同。
  8. 组合结构需同步维护cross-invariants,并用round-trip或reference implementation验收。

资料与写作方式声明

本章以Algorithms, Fourth Edition合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…