专栏文章
专栏文章
地图导航算法系列
1. 地图导航算法 #01:A* 算法 2. 地图导航算法 #02:双向搜索 3. 地图导航算法 #03:Contraction Hierarchies 4. 地图导航算法 #04:Hub Labeling 5. 地图导航算法 #05:Customizable Contraction Hierarchies

地图导航算法 #03:Contraction Hierarchies

发布于 2026-06-08 09:03 👁 9 次阅读

Contraction Hierarchies 通过「收缩不重要节点、添加快捷边」对路网进行预处理,将查询时的双向搜索空间从亿级压缩到数千节点,单次查询 < 1ms,是 OSRM、GraphHopper 等工业导航引擎的核心算法。

相关文章双向搜索 · Hub Labeling · Customizable Contraction Hierarchies

ch contraction

目录

章节 说明
核心思想 节点重要性分层,快捷边跳过低层节点
预处理:节点收缩 重要性计算,收缩顺序,快捷边生成
查询:双向层级搜索 只向更高层级扩展,搜索空间骤降
节点重要性指标 Edge Difference、契约度等指标
性能数据 预处理时间、查询时间、与其他算法对比
实际应用 OSRM、GraphHopper、高德
局限与演进 静态权重限制,引出 CCH
参考资料 论文与文档

核心思想

Dijkstra 和 A* 在亿级路网上仍然太慢,根本原因是搜索空间太大。CH 的突破在于:

把路网分成「重要性层级」,查询时只走高速公路层级,自动跳过小路细节。

类比:从北京开车到上海,你不会考虑胡同,只关心高速公路。CH 将这个直觉形式化:

预处理(离线,一次性):
  按重要性从低到高,逐步"收缩"不重要节点
  → 每次收缩添加快捷边,保证路径长度不变
  → 形成多层级叠加的增强图

查询(在线,< 1ms):
  双向搜索,但只允许向更高层级节点扩展
  → 搜索空间从亿级压缩到数千节点

预处理:节点收缩

收缩一个节点

收缩节点 v 的步骤:

1. 枚举所有以 v 为中间节点的路径:u → v → w
2. 检查是否有其他绕过 v 的最短路 u ↝ w(witness path)
3. 若无更短绕路:添加快捷边 u → w,权重 = dist(u,v) + dist(v,w)
4. 从图中删除 v 及其关联边

快捷边的语义u → w (cost=5) 表示「原图中 u 到 w 的最短路经过已收缩节点,代价为 5」。

Witness Path 搜索

添加快捷边前,用局部 Dijkstra 搜索绕过 v 的最短路:

// 从 u 出发,不经过 v,能否找到 u→w 代价 ≤ dist(u,v)+dist(v,w)?
// 搜索深度限制(hop_limit)避免搜索过深
boolean hasWitness(int u, int v, int w, double maxDist, int hopLimit) {
    // 局部 Dijkstra,排除节点 v,限制跳数
    ...
}

优化:hop_limit 通常设为 5,在收缩速度和快捷边数量之间取平衡。

收缩顺序决定质量

节点的收缩顺序直接影响快捷边数量和查询性能。使用优先队列按重要性从低到高收缩:

priority(v) = w₁·EdgeDifference(v) + w₂·DeletedNeighbors(v) + w₃·Depth(v)

每次收缩后,受影响邻居节点的优先级需要重新计算(lazy update 近似)。


查询:双向层级搜索

层级约束

预处理后,每个节点都有一个层级编号(order)。查询时:

前向搜索(从 S 出发):只沿 order[v] > order[u] 的边扩展
后向搜索(从 G 出发):只沿 order[v] > order[u] 的边扩展(在反向图上)

这保证两个搜索前沿都只"向上爬",最终在层级最高的节点附近相遇。

查询流程

初始化:distF[S]=0, distB[G]=0
双向优先队列交替展开:

  前向取出 u(distF 最小):
    for 每条正向边 u→v(包括快捷边):
      if order[v] > order[u]:       ← 只向上扩展
        松弛 distF[v]

  后向取出 u(distB 最小):
    for 每条反向边 u→v(包括快捷边):
      if order[v] > order[u]:
        松弛 distB[v]

  当两个队首之和 ≥ best 时停止(同双向 Dijkstra 终止条件)

路径解压

CH 返回的路径经过快捷边,需要递归解压还原为原始节点序列:

void unpack(int u, int w, List<Integer> path) {
    if (!isShortcut(u, w)) {
        path.add(w);
        return;
    }
    int mid = shortcutMidpoint(u, w);  // 快捷边跳过的中间节点
    unpack(u, mid, path);
    unpack(mid, w, path);
}

节点重要性指标

指标 含义 作用
Edge Difference 收缩后新增快捷边数 − 删除边数 核心指标,越小越优先收缩
Deleted Neighbors 已被收缩的邻居数量 避免连续收缩相邻节点
Shortcut Cover 快捷边覆盖的原始节点数 衡量收缩影响范围
Depth 距最近叶节点的层数 防止层级过深

典型权重配比示例:EdgeDifference × 190 + DeletedNeighbors × 120 + Depth × 10(各实现权重不同,此为教学版参考值;实际系统通常用 lazy update 动态重计算优先级而非固定系数)。


性能数据

以西欧道路网络(约 1800 万节点,来自 Geisberger 2008 原论文基准)为参考:

算法 预处理时间 查询时间 内存
Dijkstra ~10s 基准
A* ~2s 基准
双向 A* ~0.8s 基准
CH ~5 分钟 < 1ms 基准 × 2.5
Hub Labeling ~1 小时 ~0.1ms 基准 × 20

CH 是预处理时间 / 查询速度权衡的最佳甜点:预处理一次,查询百亿次。


实际应用

OSRM(Open Source Routing Machine)

GraphHopper

高德 / 百度地图


局限与演进

CH 的核心局限:快捷边在预处理时固化了边权

若道路权重变化(拥堵、限速调整),必须重新预处理整张图,耗时数分钟——无法支持实时路况的秒级更新。

演进方向:

CH(静态权重,< 1ms 查询)
  ↓ 需要支持实时路况
Customizable CH (CCH)
  ↓ 将图结构与边权分离
  预处理:只处理图结构(一次性,数分钟)
  定制化:路况变化时只更新边权层(毫秒级)
  查询:同 CH(< 1ms)

详见 Customizable Contraction Hierarchies


参考资料

参考资料

  • Geisberger et al., Contraction Hierarchies: Faster and Simpler Hierarchical Routing in Road Networks (WEA 2008) — CH 原论文
  • 双向搜索(CH 查询阶段的内核机制)
  • Hub Labeling(查询更快但内存更重的方案)
  • Customizable Contraction Hierarchies(支持实时路况的 CH 演进)
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论