欢迎光临
我们一直在努力

从街道到高速公路-HNSW分层近邻搜索原理与工程实践

在这里插入图片描述

目录

  • 背景与动机
  • 前置知识:小世界网络(Navigable Small World)
  • HNSW 核心设计:分层结构
  • 搜索算法详解
  • 插入算法详解
  • 复杂度分析
  • 关键参数与调优
  • 与其他 ANN 算法的对比
  • 实现要点与工程实践

  • 1. 背景与动机

    1.1 问题定义

    给定一个包含 N 个 d 维向量的数据集

    D

    =

    {

    x

    1

    ,

    x

    2

    ,

    ,

    x

    N

    }

    D = \\{x_1, x_2, \\ldots, x_N\\}

    D={x1,x2,,xN},以及一个查询向量

    q

    q

    q,精确最近邻搜索的目标是:

    x

    =

    arg

    min

    x

    i

    D

    q

    x

    i

    x^* = \\arg\\min_{x_i \\in D} \\|q – x_i\\|

    x=argxiDminqxi

    当 N 达到百万甚至亿级别,且维度 d 较高(如 128~1024)时,暴力搜索需要计算 N 次距离,耗时无法满足在线服务需求(通常要求 < 10ms)。

    1.2 为什么需要近似而非精确?

    在实际应用中(推荐系统、语义搜索、图像检索),用户对"第 3 名和第 4 名顺序互换"并不敏感,但对"等待时间从 5ms 变成 500ms"非常敏感。因此,允许 1%~5% 的精度损失,换取 100~1000 倍的速度提升,是合理的工程取舍。

    1.3 已有方法的局限

    方法核心思想主要缺陷
    KD-Tree 沿坐标轴递归分割空间 维度超过 20 时效率急剧下降(维度灾难)
    LSH(局部敏感哈希) 相近向量大概率落入同一哈希桶 需要大量哈希表才能保证召回率,内存开销大
    Annoy 多棵随机投影树 静态构建,不支持增量插入;召回率依赖树的数量

    NSW(Navigable Small World) 在低层表现优异,但存在两个核心问题:

  • 搜索初期效率低:从随机入口点开始,需要经过大量"长途跳转"才能到达查询点附近区域
  • 内存开销大:每个节点需要维护较多邻居连接,总边数较多
  • HNSW 通过引入分层结构解决了这两个问题。


    2. 前置知识:小世界网络(Navigable Small World)

    2.1 什么是小世界网络

    "小世界"概念来自社会网络:地球上任意两个人之间,平均只需约 6 个中间人即可建立联系(六度分隔理论)。

    在图论中,一个可导航的小世界网络具有以下特征:

    • 大部分边是短程边(连接空间上相近的节点)
    • 少量长程边(连接空间上远离的节点)
    • 从任意节点出发,通过贪心策略(每步走向离目标更近的邻居)可以在

      O

      (

      log

      N

      )

      O(\\log N)

      O(logN) 步内到达目标

    #mermaid-svg-pJ9uTXHUlzbx1a0h{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pJ9uTXHUlzbx1a0h .error-icon{fill:#552222;}#mermaid-svg-pJ9uTXHUlzbx1a0h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pJ9uTXHUlzbx1a0h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .marker.cross{stroke:#333333;}#mermaid-svg-pJ9uTXHUlzbx1a0h svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pJ9uTXHUlzbx1a0h p{margin:0;}#mermaid-svg-pJ9uTXHUlzbx1a0h .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster-label text{fill:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster-label span{color:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster-label span p{background-color:transparent;}#mermaid-svg-pJ9uTXHUlzbx1a0h .label text,#mermaid-svg-pJ9uTXHUlzbx1a0h span{fill:#333;color:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .node rect,#mermaid-svg-pJ9uTXHUlzbx1a0h .node circle,#mermaid-svg-pJ9uTXHUlzbx1a0h .node ellipse,#mermaid-svg-pJ9uTXHUlzbx1a0h .node polygon,#mermaid-svg-pJ9uTXHUlzbx1a0h .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .rough-node .label text,#mermaid-svg-pJ9uTXHUlzbx1a0h .node .label text,#mermaid-svg-pJ9uTXHUlzbx1a0h .image-shape .label,#mermaid-svg-pJ9uTXHUlzbx1a0h .icon-shape .label{text-anchor:middle;}#mermaid-svg-pJ9uTXHUlzbx1a0h .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .rough-node .label,#mermaid-svg-pJ9uTXHUlzbx1a0h .node .label,#mermaid-svg-pJ9uTXHUlzbx1a0h .image-shape .label,#mermaid-svg-pJ9uTXHUlzbx1a0h .icon-shape .label{text-align:center;}#mermaid-svg-pJ9uTXHUlzbx1a0h .node.clickable{cursor:pointer;}#mermaid-svg-pJ9uTXHUlzbx1a0h .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .arrowheadPath{fill:#333333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pJ9uTXHUlzbx1a0h .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pJ9uTXHUlzbx1a0h .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pJ9uTXHUlzbx1a0h .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster text{fill:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h .cluster span{color:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pJ9uTXHUlzbx1a0h .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pJ9uTXHUlzbx1a0h rect.text{fill:none;stroke-width:0;}#mermaid-svg-pJ9uTXHUlzbx1a0h .icon-shape,#mermaid-svg-pJ9uTXHUlzbx1a0h .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pJ9uTXHUlzbx1a0h .icon-shape p,#mermaid-svg-pJ9uTXHUlzbx1a0h .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pJ9uTXHUlzbx1a0h .icon-shape .label rect,#mermaid-svg-pJ9uTXHUlzbx1a0h .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pJ9uTXHUlzbx1a0h .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pJ9uTXHUlzbx1a0h .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pJ9uTXHUlzbx1a0h :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    入口节点

    节点2

    节点3

    节点4

    节点5

    目标节点

    小世界网络中的贪心搜索路径:入口节点(蓝色)出发,每步选择离目标(红色)最近的邻居前进,最终收敛到目标节点。

    2.2 Delaunay 图与 NSW

    理想的近邻图是 Delaunay 图——每个节点的邻居是其在 Voronoi 单元中相邻的所有节点。Delaunay 图保证贪心搜索一定能找到全局最近邻,但构建复杂度为

    O

    (

    N

    d

    /

    2

    )

    O(N^{\\lceil d/2 \\rceil})

    O(Nd/2),在高维下完全不可行。

    NSW 是 Delaunay 图的近似:通过随机插入节点并连接其近邻,逐步构建一个"足够好"的图,使贪心搜索大概率能找到最近邻。

    2.3 NSW 的搜索过程

    #mermaid-svg-WjHyCaCJ1MzhXCyP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WjHyCaCJ1MzhXCyP .error-icon{fill:#552222;}#mermaid-svg-WjHyCaCJ1MzhXCyP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WjHyCaCJ1MzhXCyP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .marker.cross{stroke:#333333;}#mermaid-svg-WjHyCaCJ1MzhXCyP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WjHyCaCJ1MzhXCyP p{margin:0;}#mermaid-svg-WjHyCaCJ1MzhXCyP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster-label text{fill:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster-label span{color:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster-label span p{background-color:transparent;}#mermaid-svg-WjHyCaCJ1MzhXCyP .label text,#mermaid-svg-WjHyCaCJ1MzhXCyP span{fill:#333;color:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .node rect,#mermaid-svg-WjHyCaCJ1MzhXCyP .node circle,#mermaid-svg-WjHyCaCJ1MzhXCyP .node ellipse,#mermaid-svg-WjHyCaCJ1MzhXCyP .node polygon,#mermaid-svg-WjHyCaCJ1MzhXCyP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .rough-node .label text,#mermaid-svg-WjHyCaCJ1MzhXCyP .node .label text,#mermaid-svg-WjHyCaCJ1MzhXCyP .image-shape .label,#mermaid-svg-WjHyCaCJ1MzhXCyP .icon-shape .label{text-anchor:middle;}#mermaid-svg-WjHyCaCJ1MzhXCyP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .rough-node .label,#mermaid-svg-WjHyCaCJ1MzhXCyP .node .label,#mermaid-svg-WjHyCaCJ1MzhXCyP .image-shape .label,#mermaid-svg-WjHyCaCJ1MzhXCyP .icon-shape .label{text-align:center;}#mermaid-svg-WjHyCaCJ1MzhXCyP .node.clickable{cursor:pointer;}#mermaid-svg-WjHyCaCJ1MzhXCyP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .arrowheadPath{fill:#333333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WjHyCaCJ1MzhXCyP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WjHyCaCJ1MzhXCyP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WjHyCaCJ1MzhXCyP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster text{fill:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP .cluster span{color:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WjHyCaCJ1MzhXCyP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WjHyCaCJ1MzhXCyP rect.text{fill:none;stroke-width:0;}#mermaid-svg-WjHyCaCJ1MzhXCyP .icon-shape,#mermaid-svg-WjHyCaCJ1MzhXCyP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WjHyCaCJ1MzhXCyP .icon-shape p,#mermaid-svg-WjHyCaCJ1MzhXCyP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WjHyCaCJ1MzhXCyP .icon-shape .label rect,#mermaid-svg-WjHyCaCJ1MzhXCyP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WjHyCaCJ1MzhXCyP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WjHyCaCJ1MzhXCyP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WjHyCaCJ1MzhXCyP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    节点1

    节点2

    节点3

    节点4

    节点5

    节点6

    节点7

    节点8

    查询点 q

    NSW 单层搜索示意:边代表近邻连接,红色节点为查询点 q,搜索从入口点出发,每步移向离 q 最近的邻居,直到局部最优。

    NSW 的问题:入口点可能在图的"边缘",搜索初期需要经过大量节点才能进入"主干",这部分步数占总搜索步数的很大比例。


    3. HNSW 核心设计:分层结构

    3.1 灵感来源:跳表(Skip List)

    HNSW 的分层设计直接借鉴了跳表(Skip List)数据结构。跳表通过在链表上叠加多层"快速通道",使搜索复杂度从 O(N) 降至 O(log N)。

    #mermaid-svg-iiWDiGvibYgZJOj4{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iiWDiGvibYgZJOj4 .error-icon{fill:#552222;}#mermaid-svg-iiWDiGvibYgZJOj4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iiWDiGvibYgZJOj4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iiWDiGvibYgZJOj4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iiWDiGvibYgZJOj4 .marker.cross{stroke:#333333;}#mermaid-svg-iiWDiGvibYgZJOj4 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iiWDiGvibYgZJOj4 p{margin:0;}#mermaid-svg-iiWDiGvibYgZJOj4 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster-label text{fill:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster-label span{color:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster-label span p{background-color:transparent;}#mermaid-svg-iiWDiGvibYgZJOj4 .label text,#mermaid-svg-iiWDiGvibYgZJOj4 span{fill:#333;color:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 .node rect,#mermaid-svg-iiWDiGvibYgZJOj4 .node circle,#mermaid-svg-iiWDiGvibYgZJOj4 .node ellipse,#mermaid-svg-iiWDiGvibYgZJOj4 .node polygon,#mermaid-svg-iiWDiGvibYgZJOj4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iiWDiGvibYgZJOj4 .rough-node .label text,#mermaid-svg-iiWDiGvibYgZJOj4 .node .label text,#mermaid-svg-iiWDiGvibYgZJOj4 .image-shape .label,#mermaid-svg-iiWDiGvibYgZJOj4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-iiWDiGvibYgZJOj4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iiWDiGvibYgZJOj4 .rough-node .label,#mermaid-svg-iiWDiGvibYgZJOj4 .node .label,#mermaid-svg-iiWDiGvibYgZJOj4 .image-shape .label,#mermaid-svg-iiWDiGvibYgZJOj4 .icon-shape .label{text-align:center;}#mermaid-svg-iiWDiGvibYgZJOj4 .node.clickable{cursor:pointer;}#mermaid-svg-iiWDiGvibYgZJOj4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iiWDiGvibYgZJOj4 .arrowheadPath{fill:#333333;}#mermaid-svg-iiWDiGvibYgZJOj4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iiWDiGvibYgZJOj4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iiWDiGvibYgZJOj4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iiWDiGvibYgZJOj4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iiWDiGvibYgZJOj4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iiWDiGvibYgZJOj4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster text{fill:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 .cluster span{color:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iiWDiGvibYgZJOj4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iiWDiGvibYgZJOj4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-iiWDiGvibYgZJOj4 .icon-shape,#mermaid-svg-iiWDiGvibYgZJOj4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iiWDiGvibYgZJOj4 .icon-shape p,#mermaid-svg-iiWDiGvibYgZJOj4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iiWDiGvibYgZJOj4 .icon-shape .label rect,#mermaid-svg-iiWDiGvibYgZJOj4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iiWDiGvibYgZJOj4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iiWDiGvibYgZJOj4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iiWDiGvibYgZJOj4 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第 0层(底层)

    1

    2

    3

    4

    5

    6

    7

    8

    9

    第 1层

    1

    4

    7

    9

    第 2层(顶层)

    1

    9

    跳表示例:搜索目标值 5。从顶层逐层下降——第 2 层 1→9(9 超过 5,下降),第 1 层 1→4→7(7 超过 5,下降),第 0 层 4→5(命中目标,绿色节点)。

    HNSW 将这个思想从一维有序链表推广到了高维向量空间。

    3.2 分层结构

    HNSW 构建一个 L+1 层的多层图(从第 0 层到第 L 层):

    • 第 0 层:包含所有 N 个节点,边最密集,是"底层公路网"
    • 第 1 层:包含约 N/M 个节点,边较稀疏,是"省级公路"
    • 第 2 层:包含约 N/M² 个节点,边更稀疏,是"高速公路"
    • 第 L 层:通常只包含 1~2 个节点,是"顶层入口"

    #mermaid-svg-M0o8aY4ihXh2g87Z{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M0o8aY4ihXh2g87Z .error-icon{fill:#552222;}#mermaid-svg-M0o8aY4ihXh2g87Z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M0o8aY4ihXh2g87Z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M0o8aY4ihXh2g87Z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M0o8aY4ihXh2g87Z .marker.cross{stroke:#333333;}#mermaid-svg-M0o8aY4ihXh2g87Z svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M0o8aY4ihXh2g87Z p{margin:0;}#mermaid-svg-M0o8aY4ihXh2g87Z .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster-label text{fill:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster-label span{color:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster-label span p{background-color:transparent;}#mermaid-svg-M0o8aY4ihXh2g87Z .label text,#mermaid-svg-M0o8aY4ihXh2g87Z span{fill:#333;color:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z .node rect,#mermaid-svg-M0o8aY4ihXh2g87Z .node circle,#mermaid-svg-M0o8aY4ihXh2g87Z .node ellipse,#mermaid-svg-M0o8aY4ihXh2g87Z .node polygon,#mermaid-svg-M0o8aY4ihXh2g87Z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M0o8aY4ihXh2g87Z .rough-node .label text,#mermaid-svg-M0o8aY4ihXh2g87Z .node .label text,#mermaid-svg-M0o8aY4ihXh2g87Z .image-shape .label,#mermaid-svg-M0o8aY4ihXh2g87Z .icon-shape .label{text-anchor:middle;}#mermaid-svg-M0o8aY4ihXh2g87Z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M0o8aY4ihXh2g87Z .rough-node .label,#mermaid-svg-M0o8aY4ihXh2g87Z .node .label,#mermaid-svg-M0o8aY4ihXh2g87Z .image-shape .label,#mermaid-svg-M0o8aY4ihXh2g87Z .icon-shape .label{text-align:center;}#mermaid-svg-M0o8aY4ihXh2g87Z .node.clickable{cursor:pointer;}#mermaid-svg-M0o8aY4ihXh2g87Z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M0o8aY4ihXh2g87Z .arrowheadPath{fill:#333333;}#mermaid-svg-M0o8aY4ihXh2g87Z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M0o8aY4ihXh2g87Z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M0o8aY4ihXh2g87Z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M0o8aY4ihXh2g87Z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M0o8aY4ihXh2g87Z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M0o8aY4ihXh2g87Z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster text{fill:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z .cluster span{color:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M0o8aY4ihXh2g87Z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M0o8aY4ihXh2g87Z rect.text{fill:none;stroke-width:0;}#mermaid-svg-M0o8aY4ihXh2g87Z .icon-shape,#mermaid-svg-M0o8aY4ihXh2g87Z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M0o8aY4ihXh2g87Z .icon-shape p,#mermaid-svg-M0o8aY4ihXh2g87Z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M0o8aY4ihXh2g87Z .icon-shape .label rect,#mermaid-svg-M0o8aY4ihXh2g87Z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M0o8aY4ihXh2g87Z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M0o8aY4ihXh2g87Z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M0o8aY4ihXh2g87Z :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第 1 层 — 中等密度,'省道'

    第 2 层 — 稀疏连接,'高速公路'

    第 3 层(顶层)— 入口层,通常仅 1 个节点

    第 0 层 — 最密集,'街道网络'(包含所有 N 个节点)

    入口节点 ★

    节点A

    节点B

    节点C

    节点A

    节点B

    节点C

    节点D

    节点E

    HNSW 分层结构侧视:顶层(蓝色)仅 1 个入口节点,越往下节点越多、边越密。第 0 层包含全部数据节点,连接最为密集。

    3.3 节点出现在哪一层?

    每个节点在插入时,通过以下方式决定它出现在哪些层:

    设 ml = 1 / ln(M) (M 为最大连接数,通常取 16~64)

    对节点 x:
    生成随机数 r ∈ (0, 1)
    层数 l = floor(-ln(r) * ml)

    其中 ml = 1 / ln(M),因此:
    l = floor(-ln(r) / ln(M))

    等价于:P(x 出现在第 l 层) ≈ 1/M^l

    推导过程:节点出现在第 l 层的概率等于

    P

    (

    l

    1

    <

    ln

    (

    r

    )

    m

    l

    l

    )

    P(l-1 < -\\ln(r) \\cdot ml \\le l)

    P(l1<ln(r)mll),即

    P

    (

    ln

    (

    r

    )

    (

    (

    l

    1

    )

    /

    m

    l

    ,

    l

    /

    m

    l

    ]

    )

    P(-\\ln(r) \\in ((l-1)/ml, l/ml])

    P(ln(r)((l1)/ml,l/ml])。由于

    ln

    (

    r

    )

    -\\ln(r)

    ln(r) 服从指数分布

    E

    x

    p

    (

    1

    )

    Exp(1)

    Exp(1),其 CDF 为

    1

    e

    t

    1 – e^{-t}

    1et,因此:

    P

    (

    l

    )

    =

    e

    (

    l

    1

    )

    /

    m

    l

    e

    l

    /

    m

    l

    =

    e

    (

    l

    1

    )

    /

    m

    l

    (

    1

    e

    1

    /

    m

    l

    )

    P(l) = e^{-(l-1)/ml} – e^{-l/ml} = e^{-(l-1)/ml}(1 – e^{-1/ml})

    P(l)=e(l1)/mlel/ml=e(l1)/ml(1e1/ml)

    当 M=16 时,

    m

    l

    =

    1

    /

    ln

    (

    16

    )

    0.361

    ml = 1/\\ln(16) \\approx 0.361

    ml=1/ln(16)0.361,各层概率约为:

    #mermaid-svg-QmEp13BCF4URXdqA{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QmEp13BCF4URXdqA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QmEp13BCF4URXdqA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QmEp13BCF4URXdqA .error-icon{fill:#552222;}#mermaid-svg-QmEp13BCF4URXdqA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QmEp13BCF4URXdqA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QmEp13BCF4URXdqA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QmEp13BCF4URXdqA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QmEp13BCF4URXdqA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QmEp13BCF4URXdqA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QmEp13BCF4URXdqA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QmEp13BCF4URXdqA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QmEp13BCF4URXdqA .marker.cross{stroke:#333333;}#mermaid-svg-QmEp13BCF4URXdqA svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QmEp13BCF4URXdqA p{margin:0;}#mermaid-svg-QmEp13BCF4URXdqA :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    节点出现在各层的概率(M=16, ml≈0.361)

    第0层

    第1层

    第2层

    第3层

    第4层

    第5层

    100

    90

    80

    70

    60

    50

    40

    30

    20

    10

    0

    概率 (%)

    注:第 0 层包含所有节点(概率 100%)。第 l 层(l≥1)的节点数约为

    N

    /

    M

    l

    N/M^l

    N/Ml,当 M=16 时,第 1 层约 N/16 个节点,第 2 层约 N/256 个节点。

    直觉理解:大多数节点只在第 0 层(“街道级”),少数节点上升到高层(“高速公路级”),极少数节点到达顶层。高层节点充当"交通枢纽",为搜索提供远距离跳跃能力。

    3.4 各层独立搜索,逐层下降

    搜索不是同时在所有层进行,而是从顶层开始,逐层向下:

    #mermaid-svg-zT5oSYwyYZL8KpGe{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zT5oSYwyYZL8KpGe .error-icon{fill:#552222;}#mermaid-svg-zT5oSYwyYZL8KpGe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zT5oSYwyYZL8KpGe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zT5oSYwyYZL8KpGe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zT5oSYwyYZL8KpGe .marker.cross{stroke:#333333;}#mermaid-svg-zT5oSYwyYZL8KpGe svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zT5oSYwyYZL8KpGe p{margin:0;}#mermaid-svg-zT5oSYwyYZL8KpGe .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster-label text{fill:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster-label span{color:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster-label span p{background-color:transparent;}#mermaid-svg-zT5oSYwyYZL8KpGe .label text,#mermaid-svg-zT5oSYwyYZL8KpGe span{fill:#333;color:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe .node rect,#mermaid-svg-zT5oSYwyYZL8KpGe .node circle,#mermaid-svg-zT5oSYwyYZL8KpGe .node ellipse,#mermaid-svg-zT5oSYwyYZL8KpGe .node polygon,#mermaid-svg-zT5oSYwyYZL8KpGe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zT5oSYwyYZL8KpGe .rough-node .label text,#mermaid-svg-zT5oSYwyYZL8KpGe .node .label text,#mermaid-svg-zT5oSYwyYZL8KpGe .image-shape .label,#mermaid-svg-zT5oSYwyYZL8KpGe .icon-shape .label{text-anchor:middle;}#mermaid-svg-zT5oSYwyYZL8KpGe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zT5oSYwyYZL8KpGe .rough-node .label,#mermaid-svg-zT5oSYwyYZL8KpGe .node .label,#mermaid-svg-zT5oSYwyYZL8KpGe .image-shape .label,#mermaid-svg-zT5oSYwyYZL8KpGe .icon-shape .label{text-align:center;}#mermaid-svg-zT5oSYwyYZL8KpGe .node.clickable{cursor:pointer;}#mermaid-svg-zT5oSYwyYZL8KpGe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zT5oSYwyYZL8KpGe .arrowheadPath{fill:#333333;}#mermaid-svg-zT5oSYwyYZL8KpGe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zT5oSYwyYZL8KpGe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zT5oSYwyYZL8KpGe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zT5oSYwyYZL8KpGe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zT5oSYwyYZL8KpGe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zT5oSYwyYZL8KpGe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster text{fill:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe .cluster span{color:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zT5oSYwyYZL8KpGe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zT5oSYwyYZL8KpGe rect.text{fill:none;stroke-width:0;}#mermaid-svg-zT5oSYwyYZL8KpGe .icon-shape,#mermaid-svg-zT5oSYwyYZL8KpGe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zT5oSYwyYZL8KpGe .icon-shape p,#mermaid-svg-zT5oSYwyYZL8KpGe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zT5oSYwyYZL8KpGe .icon-shape .label rect,#mermaid-svg-zT5oSYwyYZL8KpGe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zT5oSYwyYZL8KpGe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zT5oSYwyYZL8KpGe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zT5oSYwyYZL8KpGe :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第 0 层(底层)

    第 1 层

    第 2 层

    第 3 层(顶层)

    ★ 入口点

    搜索从顶层入口点(蓝色)逐层下降,每层缩小搜索范围,最终在底层(红色)定位到最近邻。


    4. 搜索算法详解

    4.1 算法伪代码

    函数 search(q, efSearch):
    // q: 查询向量
    // efSearch: 搜索时维护的候选列表大小(通常 32~512)

    1. 从顶层入口点 ep 开始
    2. 对每一层 l,从顶层到第1层:
    a. 在当前层执行贪心搜索
    b. 维护一个大小为 1 的优先队列(当前最近邻)
    c. 直到无法找到更近的节点
    d. 将当前层找到的最近邻作为下一层的入口点
    3. 在第0层执行贪心搜索:
    a. 维护一个大小为 efSearch 的优先队列
    b. 返回 efSearch 个最近邻

    4.2 单层贪心搜索详细步骤

    函数 greedySearch(q, entryPoint, ef):
    candidates = 最小堆(按到 q 的距离排序)
    visited = 集合
    result = 最大堆(按到 q 的距离排序) // 保存当前最近的 ef 个节点

    dist = distance(q, entryPoint)
    candidates.push(entryPoint, dist)
    result.push(entryPoint, dist)
    visited.add(entryPoint)

    当 candidates 不为空时循环:
    c = candidates.pop()

    // 终止条件:c 比 result 中最远的节点还远,后续不可能更优
    如果 distance(q, c) > distance(q, result.peek()):
    跳出循环

    对于 c 的每个邻居 e:
    如果 e 未被访问过:
    标记 e 为已访问
    dist_e = distance(q, e)

    如果 dist_e < distance(q, result.peek()) 或 result.size < ef:
    candidates.push(e, dist_e)
    result.push(e, dist_e)

    如果 result.size > ef:
    result.pop()

    返回 result 中的 ef 个节点

    4.3 搜索过程图解

    #mermaid-svg-6yQsEw29uz9KGHQC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6yQsEw29uz9KGHQC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6yQsEw29uz9KGHQC .error-icon{fill:#552222;}#mermaid-svg-6yQsEw29uz9KGHQC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6yQsEw29uz9KGHQC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6yQsEw29uz9KGHQC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6yQsEw29uz9KGHQC .marker.cross{stroke:#333333;}#mermaid-svg-6yQsEw29uz9KGHQC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6yQsEw29uz9KGHQC p{margin:0;}#mermaid-svg-6yQsEw29uz9KGHQC .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6yQsEw29uz9KGHQC .cluster-label text{fill:#333;}#mermaid-svg-6yQsEw29uz9KGHQC .cluster-label span{color:#333;}#mermaid-svg-6yQsEw29uz9KGHQC .cluster-label span p{background-color:transparent;}#mermaid-svg-6yQsEw29uz9KGHQC .label text,#mermaid-svg-6yQsEw29uz9KGHQC span{fill:#333;color:#333;}#mermaid-svg-6yQsEw29uz9KGHQC .node rect,#mermaid-svg-6yQsEw29uz9KGHQC .node circle,#mermaid-svg-6yQsEw29uz9KGHQC .node ellipse,#mermaid-svg-6yQsEw29uz9KGHQC .node polygon,#mermaid-svg-6yQsEw29uz9KGHQC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6yQsEw29uz9KGHQC .rough-node .label text,#mermaid-svg-6yQsEw29uz9KGHQC .node .label text,#mermaid-svg-6yQsEw29uz9KGHQC .image-shape .label,#mermaid-svg-6yQsEw29uz9KGHQC .icon-shape .label{text-anchor:middle;}#mermaid-svg-6yQsEw29uz9KGHQC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6yQsEw29uz9KGHQC .rough-node .label,#mermaid-svg-6yQsEw29uz9KGHQC .node .label,#mermaid-svg-6yQsEw29uz9KGHQC .image-shape .label,#mermaid-svg-6yQsEw29uz9KGHQC .icon-shape .label{text-align:center;}#mermaid-svg-6yQsEw29uz9KGHQC .node.clickable{cursor:pointer;}#mermaid-svg-6yQsEw29uz9KGHQC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6yQsEw29uz9KGHQC .arrowheadPath{fill:#333333;}#mermaid-svg-6yQsEw29uz9KGHQC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6yQsEw29uz9KGHQC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6yQsEw29uz9KGHQC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yQsEw29uz9KGHQC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6yQsEw29uz9KGHQC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yQsEw29uz9KGHQC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6yQsEw29uz9KGHQC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6yQsEw29uz9KGHQC .cluster text{fill:#333;}#mermaid-svg-6yQsEw29uz9KGHQC .cluster span{color:#333;}#mermaid-svg-6yQsEw29uz9KGHQC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6yQsEw29uz9KGHQC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6yQsEw29uz9KGHQC rect.text{fill:none;stroke-width:0;}#mermaid-svg-6yQsEw29uz9KGHQC .icon-shape,#mermaid-svg-6yQsEw29uz9KGHQC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yQsEw29uz9KGHQC .icon-shape p,#mermaid-svg-6yQsEw29uz9KGHQC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6yQsEw29uz9KGHQC .icon-shape .label rect,#mermaid-svg-6yQsEw29uz9KGHQC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yQsEw29uz9KGHQC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6yQsEw29uz9KGHQC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6yQsEw29uz9KGHQC :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    搜索过程

    A (入口点)

    B

    C

    D

    E

    F

    G

    H

    ★ 近邻1

    ★ 近邻2

    ★ 近邻3

    查询点 q

    第 0 层搜索过程:从入口点 A(蓝色)出发逐步扩展邻居,最终收敛到查询点 q(红色)附近的 3 个最近邻(绿色)。

    4.4 efSearch 的作用

    efSearch 控制搜索的"宽度":

    • efSearch 小(如 16):搜索快,但可能漏掉真正的最近邻
    • efSearch 大(如 256):搜索慢,但召回率高

    #mermaid-svg-sUu02rms2ShTQuf3{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sUu02rms2ShTQuf3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sUu02rms2ShTQuf3 .error-icon{fill:#552222;}#mermaid-svg-sUu02rms2ShTQuf3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sUu02rms2ShTQuf3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sUu02rms2ShTQuf3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sUu02rms2ShTQuf3 .marker.cross{stroke:#333333;}#mermaid-svg-sUu02rms2ShTQuf3 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sUu02rms2ShTQuf3 p{margin:0;}#mermaid-svg-sUu02rms2ShTQuf3 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-sUu02rms2ShTQuf3 .cluster-label text{fill:#333;}#mermaid-svg-sUu02rms2ShTQuf3 .cluster-label span{color:#333;}#mermaid-svg-sUu02rms2ShTQuf3 .cluster-label span p{background-color:transparent;}#mermaid-svg-sUu02rms2ShTQuf3 .label text,#mermaid-svg-sUu02rms2ShTQuf3 span{fill:#333;color:#333;}#mermaid-svg-sUu02rms2ShTQuf3 .node rect,#mermaid-svg-sUu02rms2ShTQuf3 .node circle,#mermaid-svg-sUu02rms2ShTQuf3 .node ellipse,#mermaid-svg-sUu02rms2ShTQuf3 .node polygon,#mermaid-svg-sUu02rms2ShTQuf3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sUu02rms2ShTQuf3 .rough-node .label text,#mermaid-svg-sUu02rms2ShTQuf3 .node .label text,#mermaid-svg-sUu02rms2ShTQuf3 .image-shape .label,#mermaid-svg-sUu02rms2ShTQuf3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-sUu02rms2ShTQuf3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sUu02rms2ShTQuf3 .rough-node .label,#mermaid-svg-sUu02rms2ShTQuf3 .node .label,#mermaid-svg-sUu02rms2ShTQuf3 .image-shape .label,#mermaid-svg-sUu02rms2ShTQuf3 .icon-shape .label{text-align:center;}#mermaid-svg-sUu02rms2ShTQuf3 .node.clickable{cursor:pointer;}#mermaid-svg-sUu02rms2ShTQuf3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sUu02rms2ShTQuf3 .arrowheadPath{fill:#333333;}#mermaid-svg-sUu02rms2ShTQuf3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sUu02rms2ShTQuf3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sUu02rms2ShTQuf3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sUu02rms2ShTQuf3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sUu02rms2ShTQuf3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sUu02rms2ShTQuf3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sUu02rms2ShTQuf3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sUu02rms2ShTQuf3 .cluster text{fill:#333;}#mermaid-svg-sUu02rms2ShTQuf3 .cluster span{color:#333;}#mermaid-svg-sUu02rms2ShTQuf3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sUu02rms2ShTQuf3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sUu02rms2ShTQuf3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-sUu02rms2ShTQuf3 .icon-shape,#mermaid-svg-sUu02rms2ShTQuf3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sUu02rms2ShTQuf3 .icon-shape p,#mermaid-svg-sUu02rms2ShTQuf3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sUu02rms2ShTQuf3 .icon-shape .label rect,#mermaid-svg-sUu02rms2ShTQuf3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sUu02rms2ShTQuf3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sUu02rms2ShTQuf3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sUu02rms2ShTQuf3 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    efSearch=3(探索更多路径,更可能找到全局最优)

    ●入口

    ★目标

    efSearch=1(纯贪心,路径短但可能陷入局部最优)

    ●入口

    ★目标

    efSearch 控制搜索宽度:efSearch=1 只保留 1 个候选,路径短但可能错过全局最优;efSearch=3 保留 3 个候选,探索更多路径,召回率更高。


    5. 插入算法详解

    5.1 算法概述

    插入新节点时,需要完成两件事:

  • 确定节点出现在哪些层
  • 在每一层中,找到该节点的最近邻并建立双向连接
  • 5.2 插入过程伪代码

    函数 insert(q, M, efConstruction):
    // 参数:q 为待插入向量,M 为最大连接数,efConstruction 为候选列表大小

    1. 确定 q 的最高层 l(随机采样)

    2. 从顶层入口点逐层下降到第 l 层:
    在每一层找到离 q 最近的 1 个节点作为下一层入口

    3. 从第 l 层到第 0 层逐层插入:
    a. 在当前层搜索 q 的 efConstruction 个最近邻
    b. 从中选择最近的 M 个作为 q 的邻居
    // 第 0 层最多选 M_max0 = 2M 个,第 1 层及以上最多选 M 个
    c. 建立双向边(q ↔ 邻居)
    d. 邻居度数超过上限时执行修剪
    e. 将 q 作为下一层入口

    4. 如果 l > 当前最高层,将 q 设为新的顶层入口点

    5.3 插入过程图解

    #mermaid-svg-07Iahi1XUtasQLff{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-07Iahi1XUtasQLff .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-07Iahi1XUtasQLff .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-07Iahi1XUtasQLff .error-icon{fill:#552222;}#mermaid-svg-07Iahi1XUtasQLff .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-07Iahi1XUtasQLff .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-07Iahi1XUtasQLff .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-07Iahi1XUtasQLff .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-07Iahi1XUtasQLff .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-07Iahi1XUtasQLff .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-07Iahi1XUtasQLff .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-07Iahi1XUtasQLff .marker{fill:#333333;stroke:#333333;}#mermaid-svg-07Iahi1XUtasQLff .marker.cross{stroke:#333333;}#mermaid-svg-07Iahi1XUtasQLff svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-07Iahi1XUtasQLff p{margin:0;}#mermaid-svg-07Iahi1XUtasQLff .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-07Iahi1XUtasQLff .cluster-label text{fill:#333;}#mermaid-svg-07Iahi1XUtasQLff .cluster-label span{color:#333;}#mermaid-svg-07Iahi1XUtasQLff .cluster-label span p{background-color:transparent;}#mermaid-svg-07Iahi1XUtasQLff .label text,#mermaid-svg-07Iahi1XUtasQLff span{fill:#333;color:#333;}#mermaid-svg-07Iahi1XUtasQLff .node rect,#mermaid-svg-07Iahi1XUtasQLff .node circle,#mermaid-svg-07Iahi1XUtasQLff .node ellipse,#mermaid-svg-07Iahi1XUtasQLff .node polygon,#mermaid-svg-07Iahi1XUtasQLff .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-07Iahi1XUtasQLff .rough-node .label text,#mermaid-svg-07Iahi1XUtasQLff .node .label text,#mermaid-svg-07Iahi1XUtasQLff .image-shape .label,#mermaid-svg-07Iahi1XUtasQLff .icon-shape .label{text-anchor:middle;}#mermaid-svg-07Iahi1XUtasQLff .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-07Iahi1XUtasQLff .rough-node .label,#mermaid-svg-07Iahi1XUtasQLff .node .label,#mermaid-svg-07Iahi1XUtasQLff .image-shape .label,#mermaid-svg-07Iahi1XUtasQLff .icon-shape .label{text-align:center;}#mermaid-svg-07Iahi1XUtasQLff .node.clickable{cursor:pointer;}#mermaid-svg-07Iahi1XUtasQLff .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-07Iahi1XUtasQLff .arrowheadPath{fill:#333333;}#mermaid-svg-07Iahi1XUtasQLff .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-07Iahi1XUtasQLff .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-07Iahi1XUtasQLff .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-07Iahi1XUtasQLff .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-07Iahi1XUtasQLff .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-07Iahi1XUtasQLff .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-07Iahi1XUtasQLff .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-07Iahi1XUtasQLff .cluster text{fill:#333;}#mermaid-svg-07Iahi1XUtasQLff .cluster span{color:#333;}#mermaid-svg-07Iahi1XUtasQLff div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-07Iahi1XUtasQLff .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-07Iahi1XUtasQLff rect.text{fill:none;stroke-width:0;}#mermaid-svg-07Iahi1XUtasQLff .icon-shape,#mermaid-svg-07Iahi1XUtasQLff .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-07Iahi1XUtasQLff .icon-shape p,#mermaid-svg-07Iahi1XUtasQLff .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-07Iahi1XUtasQLff .icon-shape .label rect,#mermaid-svg-07Iahi1XUtasQLff .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-07Iahi1XUtasQLff .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-07Iahi1XUtasQLff .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-07Iahi1XUtasQLff :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Step 3:在第 0 层搜索最近邻并建立双向连接

    第 0 层(插入后,q 用 ★ 表示)

    ○ — ★ q — ○

    第 0 层(插入前)

    Step 2:在第 1 层搜索最近邻并建立连接

    第 1 层(插入后,q 用 ★ 表示)

    ★ q

    第 1 层(插入前)

    Step 1:从顶层定位到 q 的最高层

    第 1 层

    第 2 层

    第 3 层

    ★ 入口

    插入过程分三步:Step 1 从顶层定位到 q 的最高层(第 1 层),Step 2 在第 1 层搜索最近邻并建立连接,Step 3 在第 0 层搜索最近邻并建立双向连接(红色节点为 q)。

    5.4 邻居修剪(Shrink)

    当一个节点已有 M 个邻居,此时新邻居要加入,需要决定保留哪些边:

    #mermaid-svg-818MMQjn7XayVbmP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-818MMQjn7XayVbmP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-818MMQjn7XayVbmP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-818MMQjn7XayVbmP .error-icon{fill:#552222;}#mermaid-svg-818MMQjn7XayVbmP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-818MMQjn7XayVbmP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-818MMQjn7XayVbmP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-818MMQjn7XayVbmP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-818MMQjn7XayVbmP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-818MMQjn7XayVbmP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-818MMQjn7XayVbmP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-818MMQjn7XayVbmP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-818MMQjn7XayVbmP .marker.cross{stroke:#333333;}#mermaid-svg-818MMQjn7XayVbmP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-818MMQjn7XayVbmP p{margin:0;}#mermaid-svg-818MMQjn7XayVbmP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-818MMQjn7XayVbmP .cluster-label text{fill:#333;}#mermaid-svg-818MMQjn7XayVbmP .cluster-label span{color:#333;}#mermaid-svg-818MMQjn7XayVbmP .cluster-label span p{background-color:transparent;}#mermaid-svg-818MMQjn7XayVbmP .label text,#mermaid-svg-818MMQjn7XayVbmP span{fill:#333;color:#333;}#mermaid-svg-818MMQjn7XayVbmP .node rect,#mermaid-svg-818MMQjn7XayVbmP .node circle,#mermaid-svg-818MMQjn7XayVbmP .node ellipse,#mermaid-svg-818MMQjn7XayVbmP .node polygon,#mermaid-svg-818MMQjn7XayVbmP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-818MMQjn7XayVbmP .rough-node .label text,#mermaid-svg-818MMQjn7XayVbmP .node .label text,#mermaid-svg-818MMQjn7XayVbmP .image-shape .label,#mermaid-svg-818MMQjn7XayVbmP .icon-shape .label{text-anchor:middle;}#mermaid-svg-818MMQjn7XayVbmP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-818MMQjn7XayVbmP .rough-node .label,#mermaid-svg-818MMQjn7XayVbmP .node .label,#mermaid-svg-818MMQjn7XayVbmP .image-shape .label,#mermaid-svg-818MMQjn7XayVbmP .icon-shape .label{text-align:center;}#mermaid-svg-818MMQjn7XayVbmP .node.clickable{cursor:pointer;}#mermaid-svg-818MMQjn7XayVbmP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-818MMQjn7XayVbmP .arrowheadPath{fill:#333333;}#mermaid-svg-818MMQjn7XayVbmP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-818MMQjn7XayVbmP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-818MMQjn7XayVbmP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-818MMQjn7XayVbmP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-818MMQjn7XayVbmP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-818MMQjn7XayVbmP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-818MMQjn7XayVbmP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-818MMQjn7XayVbmP .cluster text{fill:#333;}#mermaid-svg-818MMQjn7XayVbmP .cluster span{color:#333;}#mermaid-svg-818MMQjn7XayVbmP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-818MMQjn7XayVbmP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-818MMQjn7XayVbmP rect.text{fill:none;stroke-width:0;}#mermaid-svg-818MMQjn7XayVbmP .icon-shape,#mermaid-svg-818MMQjn7XayVbmP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-818MMQjn7XayVbmP .icon-shape p,#mermaid-svg-818MMQjn7XayVbmP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-818MMQjn7XayVbmP .icon-shape .label rect,#mermaid-svg-818MMQjn7XayVbmP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-818MMQjn7XayVbmP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-818MMQjn7XayVbmP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-818MMQjn7XayVbmP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    方案二:启发式修剪(HNSW 采用,保留视野更广的 M=3 个)

    节点 C

    A (距离=1)

    E (距离=3)

    D (距离=4, 但方向不同,保留)

    B (距离=2, 但被 A 覆盖,移除)

    方案一:简单截断(保留最近的 M=3 个)

    节点 C

    A (距离=1)

    B (距离=2)

    E (距离=3)

    D (距离=4, 被移除)

    邻居修剪策略对比:方案一简单保留距离最近的 M 个邻居;方案二(HNSW 采用)考虑邻居之间的覆盖性——即使某个邻居距离稍远,只要它指向不同方向,仍会被保留,从而让节点的"视野"更广。

    HNSW 论文中的启发式修剪算法:

    函数 shrink(neighbors, M):
    // neighbors 为候选邻居列表(已按距离排序),M 为最大连接数

    如果 neighbors.size ≤ M:
    返回 neighbors

    result = 空列表
    对于 neighbors 中的每个节点 e:
    如果 result.size ≥ M:
    跳出循环

    // 覆盖性检查:若 result 中已有比 e 更接近当前节点的邻居,则 e 多余
    dominated = false
    对于 result 中的每个已有邻居 r:
    如果 distance(r, e) < distance(e, currentNode):
    dominated = true
    跳出循环

    如果 not dominated:
    result.push(e)

    返回 result

    5.5 增量构建的特点

    HNSW 的一个关键优势是支持增量插入——不需要预先知道所有数据,可以逐条插入新节点。新节点会自动连接到真正的近邻,已有的图结构不会被破坏,只会增加少量新边。相比之下,Annoy 需要一次性构建所有树,IVF-PQ 需要预先聚类,都不支持这种增量模式。


    6. 复杂度分析

    6.1 搜索复杂度

    设数据集大小为 N,最大连接数为 M:

    搜索过程:

    顶层到第 1 层(共 L 层,每层约 M 步):
    L ≈ log_{1/ml}(N) = ln(N) / ln(1/ml) = ln(N) × M
    步数 ≈ L × M = O(M² × ln(N))

    第 0 层(efSearch 个候选):
    步数 ≈ efSearch × M

    总计:O(M² × ln(N) + efSearch × M)

    实际中 M 和 efSearch 是常数,因此简化为 O(log N)

    6.2 插入复杂度

    插入过程:

    定位层数:O(M × log N) // 从顶层下降到第 l 层
    每层搜索:O(efConstruction × M × log N)
    共 l+1 层,平均 l ≈ ln(M) – 1 // 节点最高层的期望值

    总计:O(efConstruction × M × log N)

    6.3 空间复杂度

    采用有向图视角(每个节点独立维护自己的邻居列表,q ↔ 邻居 意味着两条有向边):

    每个节点在第 0 层最多 M_max0 个出边邻居(通常 M_max0 = 2M)
    每个节点在第 l 层(l≥1)最多 M 个出边邻居

    第 0 层总边数 ≈ N × M_max0
    第 1 层总边数 ≈ (N/M) × M = N (仅约 N/M 个节点出现在第 1 层)
    第 2 层总边数 ≈ (N/M²) × M = N/M

    总边数 ≈ N × M_max0 + N × (1 + 1/M + 1/M² + …)
    ≈ N × 2M + N × M/(M-1)
    ≈ N × (2M + M/(M-1))

    当 M=16 时,总边数 ≈ N × (32 + 16/15) ≈ 33.1N

    空间占用 = 向量存储 + 图结构(边列表,每条边存一个 int32 邻居 ID)
    = N × d × 4 bytes + 33.1N × 4 bytes
    ≈ (4d + 132) × N bytes

    注:hnswlib 等实际实现中,第 0 层每个节点最多 2M 个邻居,第 1 层及以上最多 M 个邻居。这保证了第 0 层有更密集的连接以支持精确搜索。


    7. 关键参数与调优

    7.1 参数总览

    参数含义典型取值影响
    M 每个节点的最大双向连接数 8~64 越大越准,但内存和构建时间增加
    efConstruction 构建时的候选列表大小 100~500 越大索引质量越高,但构建越慢
    efSearch 搜索时的候选列表大小 16~512 越大召回率越高,但搜索越慢
    ml 层级分配因子,通常设为 1/ln(M) 自动 控制节点在各层的分布密度

    7.2 M 的选择

    #mermaid-svg-ACYHLDBcS3cZ8svj{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ACYHLDBcS3cZ8svj .error-icon{fill:#552222;}#mermaid-svg-ACYHLDBcS3cZ8svj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ACYHLDBcS3cZ8svj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ACYHLDBcS3cZ8svj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ACYHLDBcS3cZ8svj .marker.cross{stroke:#333333;}#mermaid-svg-ACYHLDBcS3cZ8svj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ACYHLDBcS3cZ8svj p{margin:0;}#mermaid-svg-ACYHLDBcS3cZ8svj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    M 值对性能的影响(定性趋势)

    M=4

    M=8

    M=16

    M=32

    M=64

    100

    90

    80

    70

    60

    50

    40

    30

    20

    10

    0

    相对值

    M 值构建速度内存占用召回率推荐场景
    4 快速原型验证
    16 中等 中等 大多数场景的推荐值
    32 较慢 较大 很高 高精度需求
    64 最高 小规模高精度场景

    经验法则:低维数据(d<32)取 M=8~16;中维数据(32≤d≤256)取 M=16~32;高维数据(d>256)取 M=32~64。

    7.3 efSearch 与召回率的关系

    #mermaid-svg-WJJS2woNLqYO4NGk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WJJS2woNLqYO4NGk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WJJS2woNLqYO4NGk .error-icon{fill:#552222;}#mermaid-svg-WJJS2woNLqYO4NGk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WJJS2woNLqYO4NGk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WJJS2woNLqYO4NGk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WJJS2woNLqYO4NGk .marker.cross{stroke:#333333;}#mermaid-svg-WJJS2woNLqYO4NGk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WJJS2woNLqYO4NGk p{margin:0;}#mermaid-svg-WJJS2woNLqYO4NGk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    efSearch 与召回率的关系(典型曲线)

    50

    100

    150

    200

    250

    300

    350

    400

    450

    500

    efSearch

    100

    98

    96

    94

    92

    90

    88

    86

    84

    82

    80

    78

    76

    召回率 (%)

    efSearch 与召回率的关系:efSearch 越大召回率越高,但搜索越慢。经验上,efSearch = 4×M 通常达到 95%+ 召回率,efSearch = 8×M 达到 99%+。

    7.4 距离度量选择

    HNSW 本身不限制距离函数,但实际使用中:

    • 内积(Inner Product):适合归一化向量的相似度搜索
    • L2 距离(欧氏距离):最通用的选择
    • 余弦相似度:等价于归一化后的内积

    注意:HNSW 要求距离函数满足三角不等式(是度量空间),否则贪心搜索的理论保证不成立。


    8. 与其他 ANN 算法的对比

    8.1 综合对比表

    维度HNSWIVF-PQAnnoyLSHKD-Tree
    搜索速度 O(log N) O(N/nprobe) O(log N) O(1)~O(N) O(log N)~O(N)
    召回率 95~99% 85~95% 85~95% 80~95% 100%
    内存占用
    增量插入 支持 不支持 不支持 部分支持 不支持
    构建时间 中等 快(需聚类) 中等
    维度上限 ~1024 ~1024 ~1024 ~256 ~20
    距离度量 任意度量 L2/内积 任意 特定函数族 L2

    8.2 选型决策树

    #mermaid-svg-D3xEv48d3iSZsPXu{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D3xEv48d3iSZsPXu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D3xEv48d3iSZsPXu .error-icon{fill:#552222;}#mermaid-svg-D3xEv48d3iSZsPXu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D3xEv48d3iSZsPXu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D3xEv48d3iSZsPXu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D3xEv48d3iSZsPXu .marker.cross{stroke:#333333;}#mermaid-svg-D3xEv48d3iSZsPXu svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D3xEv48d3iSZsPXu p{margin:0;}#mermaid-svg-D3xEv48d3iSZsPXu .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-D3xEv48d3iSZsPXu .cluster-label text{fill:#333;}#mermaid-svg-D3xEv48d3iSZsPXu .cluster-label span{color:#333;}#mermaid-svg-D3xEv48d3iSZsPXu .cluster-label span p{background-color:transparent;}#mermaid-svg-D3xEv48d3iSZsPXu .label text,#mermaid-svg-D3xEv48d3iSZsPXu span{fill:#333;color:#333;}#mermaid-svg-D3xEv48d3iSZsPXu .node rect,#mermaid-svg-D3xEv48d3iSZsPXu .node circle,#mermaid-svg-D3xEv48d3iSZsPXu .node ellipse,#mermaid-svg-D3xEv48d3iSZsPXu .node polygon,#mermaid-svg-D3xEv48d3iSZsPXu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D3xEv48d3iSZsPXu .rough-node .label text,#mermaid-svg-D3xEv48d3iSZsPXu .node .label text,#mermaid-svg-D3xEv48d3iSZsPXu .image-shape .label,#mermaid-svg-D3xEv48d3iSZsPXu .icon-shape .label{text-anchor:middle;}#mermaid-svg-D3xEv48d3iSZsPXu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D3xEv48d3iSZsPXu .rough-node .label,#mermaid-svg-D3xEv48d3iSZsPXu .node .label,#mermaid-svg-D3xEv48d3iSZsPXu .image-shape .label,#mermaid-svg-D3xEv48d3iSZsPXu .icon-shape .label{text-align:center;}#mermaid-svg-D3xEv48d3iSZsPXu .node.clickable{cursor:pointer;}#mermaid-svg-D3xEv48d3iSZsPXu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D3xEv48d3iSZsPXu .arrowheadPath{fill:#333333;}#mermaid-svg-D3xEv48d3iSZsPXu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D3xEv48d3iSZsPXu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D3xEv48d3iSZsPXu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D3xEv48d3iSZsPXu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D3xEv48d3iSZsPXu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D3xEv48d3iSZsPXu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D3xEv48d3iSZsPXu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D3xEv48d3iSZsPXu .cluster text{fill:#333;}#mermaid-svg-D3xEv48d3iSZsPXu .cluster span{color:#333;}#mermaid-svg-D3xEv48d3iSZsPXu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D3xEv48d3iSZsPXu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D3xEv48d3iSZsPXu rect.text{fill:none;stroke-width:0;}#mermaid-svg-D3xEv48d3iSZsPXu .icon-shape,#mermaid-svg-D3xEv48d3iSZsPXu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D3xEv48d3iSZsPXu .icon-shape p,#mermaid-svg-D3xEv48d3iSZsPXu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D3xEv48d3iSZsPXu .icon-shape .label rect,#mermaid-svg-D3xEv48d3iSZsPXu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D3xEv48d3iSZsPXu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D3xEv48d3iSZsPXu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D3xEv48d3iSZsPXu :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    < 100 万

    100 万 ~ 1 亿

    > 1 亿

    需要增量插入?

    数据规模?

    内存受限?

    维度 > 256?

    维度 < 20?

    选择 HNSW

    选择 HNSW(最简单,效果最好)

    选择 IVF-PQ + HNSW 混合(IVF 分簇,簇内 HNSW)

    选择 IVF-PQ

    选择 HNSW

    选择 HNSW(KD-Tree 和 LSH 不适合高维)

    KD-Tree 也可考虑

    选择 HNSW(默认最佳选择)

    ANN 算法选型决策树:HNSW 是大多数场景的默认选择;超大规模且内存受限时选 IVF-PQ;极低维场景可考虑 KD-Tree。


    9. 实现要点与工程实践

    9.1 核心数据结构

    # HNSW 索引的核心数据结构(伪代码)

    class HNSWIndex:
    def __init__(self, dim, M=16, ef_construction=200, ml=None):
    self.dim = dim # 向量维度
    self.M = M # 第 1 层及以上每个节点的最大连接数
    self.M_max0 = M * 2 # 第 0 层每个节点的最大连接数(通常更大)
    self.ef_construction = ef_construction
    self.ml = ml or 1.0 / math.log(M)

    self.vectors = {} # 节点 ID → 向量
    self.graphs = {} # 层数 → {节点 ID → [邻居 ID 列表]}
    self.entry_point = None # 顶层入口节点 ID
    self.max_level = 0

    class Node:
    def __init__(self, id, vector):
    self.id = id
    self.vector = vector
    self.neighbors = {} # 层数 → [邻居 ID 列表]
    self.level = 0 # 节点出现的最高层

    9.2 距离计算的优化

    距离计算是 HNSW 中最频繁的操作,通常占总时间的 80% 以上:

    优化手段:

    1. SIMD 向量化
    – 使用 AVX2/AVX512 指令集
    – 一次计算 8~16 个 float32 的乘加

    2. 提前终止(Early Termination)
    – 计算 L2 距离时,逐个维度累加
    – 当部分和已经超过当前最优值时,提前终止

    3. 向量量化
    – float32 → float16(精度损失极小,速度提升约 2x)
    – 或配合 PQ 进一步压缩

    4. 缓存友好性
    – 邻居列表连续存储
    – 向量数据按访问模式对齐

    9.3 并发控制

    HNSW 的并发挑战:

    读操作(搜索):
    – 只读图结构,天然支持并发
    – 多个搜索请求可以并行执行

    写操作(插入):
    – 插入新节点会修改图结构(增加边)
    – 需要加写锁或使用读写锁(RWLock)

    工程实践:
    1. 读写分离:搜索不加锁,插入加写锁
    2. 批量插入:累积一批数据后统一插入,减少锁竞争
    3. 副本机制:写操作在副本上进行,定期合并到主索引

    9.4 主流开源实现

    实现语言特点
    hnswlib C++ / Python 官方参考实现,性能最优
    FAISS C++ / Python Meta 出品,支持 GPU 加速,IVF-PQ + HNSW 混合
    Milvus Go + C++ 完整向量数据库,底层使用 HNSW/IVF-PQ
    Qdrant Rust 内存安全,性能优秀,支持过滤搜索
    Weaviate Go 内置向量数据库,支持混合搜索
    pgvector C/PostgreSQL PostgreSQL 扩展,使用 HNSW
    Elasticsearch Java 8.0+ 版本支持原生向量搜索

    9.5 典型性能基准

    数据集:SIFT-1M(100万个 128 维向量)
    硬件:Intel Xeon 2.4GHz,64GB 内存

    hnswlib 性能(M=16, efConstruction=200):

    构建时间:约 45 秒
    内存占用:约 1.2 GB
    搜索延迟(efSearch=64):约 0.5 ms
    召回率(efSearch=64):约 99.2%

    对比暴力搜索:
    搜索延迟:约 50 ms
    召回率:100%

    HNSW 加速比:约 100 倍,代价是损失 0.8% 的精确度


    附录:HNSW 搜索算法完整流程图

    #mermaid-svg-Q93VmIPJJIpaKjuo{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q93VmIPJJIpaKjuo .error-icon{fill:#552222;}#mermaid-svg-Q93VmIPJJIpaKjuo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q93VmIPJJIpaKjuo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q93VmIPJJIpaKjuo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q93VmIPJJIpaKjuo .marker.cross{stroke:#333333;}#mermaid-svg-Q93VmIPJJIpaKjuo svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q93VmIPJJIpaKjuo p{margin:0;}#mermaid-svg-Q93VmIPJJIpaKjuo .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster-label text{fill:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster-label span{color:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster-label span p{background-color:transparent;}#mermaid-svg-Q93VmIPJJIpaKjuo .label text,#mermaid-svg-Q93VmIPJJIpaKjuo span{fill:#333;color:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo .node rect,#mermaid-svg-Q93VmIPJJIpaKjuo .node circle,#mermaid-svg-Q93VmIPJJIpaKjuo .node ellipse,#mermaid-svg-Q93VmIPJJIpaKjuo .node polygon,#mermaid-svg-Q93VmIPJJIpaKjuo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q93VmIPJJIpaKjuo .rough-node .label text,#mermaid-svg-Q93VmIPJJIpaKjuo .node .label text,#mermaid-svg-Q93VmIPJJIpaKjuo .image-shape .label,#mermaid-svg-Q93VmIPJJIpaKjuo .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q93VmIPJJIpaKjuo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q93VmIPJJIpaKjuo .rough-node .label,#mermaid-svg-Q93VmIPJJIpaKjuo .node .label,#mermaid-svg-Q93VmIPJJIpaKjuo .image-shape .label,#mermaid-svg-Q93VmIPJJIpaKjuo .icon-shape .label{text-align:center;}#mermaid-svg-Q93VmIPJJIpaKjuo .node.clickable{cursor:pointer;}#mermaid-svg-Q93VmIPJJIpaKjuo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q93VmIPJJIpaKjuo .arrowheadPath{fill:#333333;}#mermaid-svg-Q93VmIPJJIpaKjuo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q93VmIPJJIpaKjuo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q93VmIPJJIpaKjuo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q93VmIPJJIpaKjuo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q93VmIPJJIpaKjuo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q93VmIPJJIpaKjuo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster text{fill:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo .cluster span{color:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q93VmIPJJIpaKjuo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q93VmIPJJIpaKjuo rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q93VmIPJJIpaKjuo .icon-shape,#mermaid-svg-Q93VmIPJJIpaKjuo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q93VmIPJJIpaKjuo .icon-shape p,#mermaid-svg-Q93VmIPJJIpaKjuo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q93VmIPJJIpaKjuo .icon-shape .label rect,#mermaid-svg-Q93VmIPJJIpaKjuo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q93VmIPJJIpaKjuo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q93VmIPJJIpaKjuo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q93VmIPJJIpaKjuo :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    查询向量 q

    从顶层入口点 ep 开始

    确定当前层数 L

    在当前层执行贪心搜索维护大小为 1 的候选队列直到无法找到更近的节点

    当前层是否是第 0 层?

    将当前层找到的最近邻作为下一层的入口点

    下降到下一层

    在第 0 层执行贪心搜索维护大小为 efSearch 的候选队列返回 efSearch 个最近邻

    返回结果

    HNSW 搜索算法完整流程:从顶层入口点逐层下降,顶层到第 1 层每层只保留 1 个最近邻作为下一层入口,到达第 0 层后用 efSearch 维护候选队列并返回结果。


    赞(0)
    未经允许不得转载:171主机测评 » 从街道到高速公路-HNSW分层近邻搜索原理与工程实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址