← 返回 Day 8 正文

Day 8 · 图解

Day 008 — 字典(Dict)图解

本文件包含核心概念的 ASCII / Mermaid 图解


1. 哈希表核心原理图

                       字典插入过程
                       ============

   键: "name"                     键: "age"
      │                              │
      ▼                              ▼
  ┌──────────┐                  ┌──────────┐
  │ hash()   │                  │ hash()   │
  └────┬─────┘                  └────┬─────┘
       │                             │
       ▼                             ▼
  哈希值: 12345                 哈希值: 67890
       │                             │
       ▼                             ▼
  取模: 12345 % 8 = 1          取模: 67890 % 8 = 3
       │                             │
       ▼                             ▼
  ┌─────────────────────────────────────────────┐
  │    哈希表数组(8 槽位)                       │
  ├──────┬──────────┬──────────┬────────────────┤
  │ 索引  │ hash     │ key      │ value          │
  ├──────┼──────────┼──────────┼────────────────┤
  │  0   │  —       │  empty   │  —             │
  │  1   │  12345   │ "name"   │ "Alice"        │ ← 槽位1被占
  │  2   │  —       │  empty   │  —             │
  │  3   │  67890   │ "age"    │ 25             │ ← 槽位3被占
  │  4   │  —       │  empty   │  —             │
  │  5   │  —       │  empty   │  —             │
  │  6   │  —       │  empty   │  —             │
  │  7   │  —       │  empty   │  —             │
  └──────┴──────────┴──────────┴────────────────┘


                    字典查找过程
                    ============

  查找: d["age"]
      │
      ▼
  hash("age") → 67890
      │
      ▼
  索引: 67890 % 8 = 3
      │
      ▼
  检查槽位3的key == "age"?   ──→ 是!返回值 25
      │
      ▼
  ✅ O(1) 完成!

2. 哈希碰撞与开放寻址

  假设 "key1" 和 "key2" 的哈希值冲突(取模后都指向索引2)

     hash("key1") % 8 = 2         hash("key2") % 8 = 2
          │                              │
          ▼                              ▼
     ┌──────────────────────────────────────────┐
     │  索引 0  │  empty                         │
     │  索引 1  │  "something"                   │
     │  索引 2  │  "key1":"val1" ← 先插入的      │ ← 碰撞!
     │  索引 3  │  empty                         │ ← 线性探测下一个空位
     │  索引 4  │  empty                         │
     │  索引 5  │  empty                         │
     │  索引 6  │  empty                         │
     │  索引 7  │  empty                         │
     └──────────────────────────────────────────┘
                │
                ▼
         二次探测过程(简化):
         
         第一次尝试: idx = 2  → 被占用,key不匹配
         第二次尝试: idx = 2 + 1² = 3  → 空!插入
         
     ┌──────────────────────────────────────────┐
     │  索引 2  │  "key1":"val1"                │
     │  索引 3  │  "key2":"val2" ← 最终位置     │
     └──────────────────────────────────────────┘


  查找 "key2":
    1. hash("key2") % 8 → 2
    2. 槽位2的key == "key2"? → 否,继续探测
    3. 槽位3的key == "key2"? → 是!返回值

3. Python 3.7+ 紧凑字典布局

                   传统字典(Python 3.5-)
                   ====================
    
    稀疏表:每个槽位存储 (hash, key, value)
    ┌──────┬──────────────────────┐
    │  0   │ (h1, "a", 1)         │
    │  1   │ (h2, "b", 2)         │
    │  2   │ empty                │
    │  3   │ (h3, "c", 3)         │
    │  4   │ empty                │
    │  5   │ empty                │
    │  6   │ (h4, "d", 4)         │
    │  7   │ empty                │
    └──────┴──────────────────────┘
    内存浪费 ≈ 50%(空槽位也占用完整空间)


                  紧凑字典(Python 3.6+)
                  =====================

    索引表(稀疏,存储索引):
    ┌──────┬──────┐
    │  0   │  1   │  ← 哈希值 → 索引表的槽位
    │  1   │  -1  │  ← -1 表示空
    │  2   │  -1  │
    │  3   │  2   │
    │  4   │  -1  │
    │  5   │  -1  │
    │  6   │  3   │
    │  7   │  -1  │
    └──────┴──────┘

    条目表(紧凑,按插入顺序存储):
    ┌──────┬──────────────────┐
    │  0   │ (h1, "a", 1)     │  ← 先插入
    │  1   │ (h2, "b", 2)     │  ← 第二插入
    │  2   │ (h3, "c", 3)     │  ← 第三插入
    │  3   │ (h4, "d", 4)     │  ← 第四插入
    └──────┴──────────────────┘

    优点:
    ✅ 更少的内存占用(条目紧凑排列)
    ✅ 保持插入顺序
    ✅ 迭代更快(顺序内存访问)

4. 可哈希 vs 不可哈希 分类树

            这个对象能做字典键吗?
                    │
                    ▼
          ┌─────────────────────┐
          │ 实现了 __hash__() ?  │
          └─────────┬───────────┘
                    │
          ┌─────────┴─────────┐
          │                    │
         是                  否
          │                    │
          ▼                    ▼
   ┌──────────────┐   ┌──────────────┐
   │ 哈希值会变吗?│   │ 不可哈希      │
   └──────┬───────┘   │              │
          │            │  ❌ list    │
    ┌─────┴─────┐      │  ❌ set     │
    │           │      │  ❌ dict    │
   不会        会      │  ❌ 普通对象 │
    │           │      └──────────────┘
    ▼           ▼
 ┌──────┐  ┌──────────┐
 │可哈希 │  │不可哈希   │
 │      │  │          │
 │ ✅   │  │ ❌ set   │
 │ int  │  │ ❌ list  │
 │ str  │  │ ❌ dict  │
 │ tuple│  │          │
 │(纯元 │  │ 自定义    │
 │ 组)  │  │ 对象默认  │
 │ froz-│  │ 可哈希    │
 │ enset│  │(id为哈希)│
 │      │  │ 但如果    │
 │ 自   │  │ 实现了    │
 │ 定义  │  │ __eq__   │
 │ 对象  │  │ 但没实现  │
 │ (实  │  │ __hash__ │
 │ 现了 │  │ → 不可哈希│
 │ 约   │  │          │
 │ 定)  │  │          │
 └──────┘  └──────────┘

5. 常用字典操作复杂度一览

╔══════════════════════╦══════════╦══════════════════════════╗
║       操作           ║ 平均复杂度 ║ 最坏情况                  ║
╠══════════════════════╬══════════╬══════════════════════════╣
║ d[key] = value      ║  O(1)   ║  O(n) 哈希碰撞严重时       ║
║ d[key] 读取          ║  O(1)   ║  O(n)                    ║
║ key in d             ║  O(1)   ║  O(n)                    ║
║ del d[key]           ║  O(1)   ║  O(n)                    ║
║ d.get(key)           ║  O(1)   ║  O(n)                    ║
║ d.pop(key)           ║  O(1)   ║  O(n)                    ║
║ d.copy()             ║  O(n)   ║  O(n)                    ║
║ d.clear()            ║  O(n)   ║  O(n)  (标记所有槽位空)   ║
║ d.update(d2)         ║  O(m)   ║  O(n+m)   m = d2大小     ║
║ d.items() 遍历       ║  O(n)   ║  O(n)                    ║
║ d.keys() | view      ║  O(1)   ║  O(1)   (创建视图)      ║
║ 字典推导式            ║  O(n)   ║  O(n)                    ║
║ 字典合并 |            ║  O(n+m) ║  O(n+m)  (Python 3.9+)  ║
╚══════════════════════╩══════════╩══════════════════════════╝

n = 字典当前大小
m = 操作涉及的外部字典大小

内存使用 ≈ O(n),负载因子 ≈ 2/3 时扩容

6. 词频统计器数据流

flowchart LR A[原始文本] --> B[分词] B --> C[清洗: 去标点/小写化] C --> D[去停用词] D --> E[词频统计] E --> F[排序] F --> G{输出} G --> H[控制台表格] G --> I[直方图] G --> J[CSV 导出] G --> K[JSON 导出] subgraph 统计方法 E1[defaultdict[int]] E2[Counter] end E --> E1 E --> E2 style E1 fill:#e1f5fe style E2 fill:#fce4ec
flowchart LR
    A[原始文本] --> B[分词]
    B --> C[清洗: 去标点/小写化]
    C --> D[去停用词]
    D --> E[词频统计]
    E --> F[排序]
    F --> G{输出}
    G --> H[控制台表格]
    G --> I[直方图]
    G --> J[CSV 导出]
    G --> K[JSON 导出]
    
    subgraph 统计方法
        E1[defaultdict[int]]
        E2[Counter]
    end
    
    E --> E1
    E --> E2
    
    style E1 fill:#e1f5fe
    style E2 fill:#fce4ec

7. dict vs list 查找性能对比

                             查找性能: 列表 vs 字典
                           =========================

查找次数: 1,000,000
数据集大小: 100,000 元素
         │
         ▼
  ┌──────────────────────────────────────────────────────────┐
  │  时间 (秒)                                               │
  │  4.0 ┤                                                    │
  │      │      ████                                          │
  │  3.0 ┤      ████                                          │
  │      │      ████                                          │
  │  2.0 ┤      ████                                          │
  │      │      ████      ██                                  │
  │  1.0 ┤      ████      ██                                  │
  │      │      ████      ██         ██                       │
  │  0.0 ┤━━━━━━████━━━━━━██━━━━━━━━██━━━━━━━━━━━━━━━━━━━━━━  │
  │             列表      字典      集合                       │
  │           (O(n))    (O(1))    (O(1))                     │
  └──────────────────────────────────────────────────────────┘

结论:数据量大时,字典查找比列表快 100~1000 倍!

8. Counter 类继承关系

classDiagram class dict { +__getitem__(key) +__setitem__(key, value) +__contains__(key) +keys() +values() +items() +get(key, default) +update(other) } class Counter { +most_common(n) +elements() +subtract(other) +__add__(other) +__sub__(other) +__and__(other) +__or__(other) +total() } dict <|-- Counter : 继承 note for Counter: "专为计数优化\n不存在的键返回0\n支持数学运算"
classDiagram
    class dict {
        +__getitem__(key)
        +__setitem__(key, value)
        +__contains__(key)
        +keys()
        +values()
        +items()
        +get(key, default)
        +update(other)
    }
    
    class Counter {
        +most_common(n)
        +elements()
        +subtract(other)
        +__add__(other)
        +__sub__(other)
        +__and__(other)
        +__or__(other)
        +total()
    }
    
    dict <|-- Counter : 继承
    
    note for Counter: "专为计数优化\n不存在的键返回0\n支持数学运算"