专栏文章
专栏文章
出行规划算法系列
1. 出行规划算法 #01:RAPTOR 2. 出行规划算法 #02:多标签 Pareto Dijkstra 3. 出行规划算法 #03:可拼票 DP 4. 出行规划算法 #04:Connection Scan Algorithm

出行规划算法 #04:Connection Scan Algorithm

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

Connection Scan Algorithm(CSA)按出发时间对所有班次连接排序,从给定出发时间起线性扫描,维护每个站点的最早到达时间;查询复杂度接近线性,对「最早到达」单目标查询比 RAPTOR 更快,被 Rome2Rio 等系统用于快速预过滤。

相关文章RAPTOR · 多标签 Pareto Dijkstra

目录

章节 说明
核心思想 时间轴顺序扫描,无需图结构
数据结构与预处理 连接数组,按出发时间排序
算法流程 扫描循环,到达时间更新
Java 实现 最早到达查询完整代码
与 RAPTOR 对比 速度、功能、适用场景
扩展:换乘与路径 最少换乘、路径重建
实际应用 Rome2Rio、DB Navigator
参考资料 论文与文档

核心思想

CSA 的洞察极其简单:

按时间顺序扫描所有班次,维护每站最早可达时间。比当前时间早的连接跳过,比目标时间晚的连接停止扫描。

不需要构建图,不需要优先队列,不需要 Dijkstra——一次线性扫描即可回答「最早到达」查询。

排序后的连接数组(按出发时间):

时间轴 ─────────────────────────────────────────────►
 8:00  8:30  9:00  9:30 10:00 10:30 11:00 ...
  │     │     │     │     │     │     │
 C1    C2    C3    C4    C5    C6    C7  ...

查询:从站点 S 出发,出发时间 ≥ 09:00,到站点 G 最早几点到?
→ 跳过 C1、C2(出发时间 < 09:00)
→ 从 C3 开始扫描,更新各站最早到达时间
→ 直到所有连接扫描完或提前终止

数据结构与预处理

连接(Connection)

一个连接代表「乘坐某趟班次,从站 A 到站 B 的一段」:

record Connection(
    int    depStop,       // 出发站 ID
    int    arrStop,       // 到达站 ID
    int    depTime,       // 出发时间(秒,距当天 0 点)
    int    arrTime,       // 到达时间
    String tripId         // 所属班次 ID(用于换乘判断)
) {}

预处理:全局排序

connections.sort(Comparator.comparingInt(Connection::depTime));

一次性离线完成,O(N log N),N 为总连接数(全国高铁约 50 万条)。

辅助数组

int[] T  = new int[numStops];     // T[s] = 站点 s 的最早到达时间(初始 ∞)
String[] inTrip = new String[numStops];  // 当前搭乘的班次(用于判断是否需要换乘)

算法流程

输入:出发站 src,目标站 dst,出发时间 depTime

初始化:
  T[src] = depTime
  其余 T[s] = ∞

二分定位:
  start = 二分查找第一个 depTime >= depTime 的连接索引

扫描:
  for i from start to len(connections)-1:
    c = connections[i]

    // 若出发站不可达,或出发时间比已知到达时间早 → 跳过
    if T[c.depStop] > c.depTime:
      continue

    // 更新到达站最早到达时间
    if c.arrTime < T[c.arrStop]:
      T[c.arrStop] = c.arrTime

    // 提前终止:若目标站已有确定到达时间,且当前连接出发时间 > T[dst]
    // (后续连接更晚出发,不可能更早到达)
    if c.depTime > T[dst]:
      break

返回 T[dst]

关键剪枝


Java 实现

import java.util.*;

public class CSA {

    record Connection(int depStop, int arrStop, int depTime, int arrTime, String tripId) {}

    private final List<Connection> connections;  // 已按 depTime 排序
    private final int numStops;

    public CSA(List<Connection> connections, int numStops) {
        this.connections = connections;
        this.numStops = numStops;
        this.connections.sort(Comparator.comparingInt(Connection::depTime));
    }

    /**
     * 查询从 src 出发(时间 >= departureTime)到 dst 的最早到达时间
     * @return 最早到达时间(秒),不可达返回 Integer.MAX_VALUE
     */
    public int earliestArrival(int src, int dst, int departureTime) {
        int[] T = new int[numStops];
        Arrays.fill(T, Integer.MAX_VALUE);
        T[src] = departureTime;

        // 二分定位起始索引
        int start = lowerBound(departureTime);

        for (int i = start; i < connections.size(); i++) {
            Connection c = connections.get(i);

            // 出发站不可达,跳过
            if (T[c.depStop] == Integer.MAX_VALUE || T[c.depStop] > c.depTime) {
                continue;
            }

            // 更新到达站最早到达时间
            if (c.arrTime < T[c.arrStop]) {
                T[c.arrStop] = c.arrTime;
            }

            // 提前终止
            if (T[dst] != Integer.MAX_VALUE && c.depTime > T[dst]) {
                break;
            }
        }

        return T[dst];
    }

    // 二分查找第一个 depTime >= target 的连接索引
    private int lowerBound(int target) {
        int lo = 0, hi = connections.size();
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (connections.get(mid).depTime < target) lo = mid + 1;
            else hi = mid;
        }
        return lo;
    }
}

与 RAPTOR 对比

维度 CSA RAPTOR
核心操作 线性扫描连接数组 按轮次扫描线路
最早到达查询 ✅ 极快(接近线性) ✅ 快(O(K×T))
最少换乘查询 🟡 需要扩展 ✅ 天然支持(轮次=换乘数)
Pareto 多目标 🟡 需要多标签扩展 ✅ McRAPTOR 已有支持
实现复杂度 极低(50 行核心代码) 中等
内存占用 O(C),C=连接数 O(K×P),P=站点数
适用场景 最早到达单目标、快速预筛选 多目标、最少换乘

实践搭配:CSA 做快速最早到达预过滤,RAPTOR 做精确多目标规划。


扩展:换乘与路径

换乘时间约束

// 需要换乘判断:若当前连接的 tripId 与上一段不同,则需要换乘时间
if (!c.tripId.equals(currentTrip[c.depStop])) {
    // 需要至少 MIN_TRANSFER 分钟换乘时间
    if (T[c.depStop] + MIN_TRANSFER > c.depTime) continue;
}

路径重建

// 记录父连接
Connection[] parent = new Connection[numStops];

// 在更新 T[c.arrStop] 时同步记录
if (c.arrTime < T[c.arrStop]) {
    T[c.arrStop] = c.arrTime;
    parent[c.arrStop] = c;
}

// 回溯路径
List<Connection> path = new ArrayList<>();
int cur = dst;
while (parent[cur] != null) {
    path.add(0, parent[cur]);
    cur = parent[cur].depStop;
}

实际应用

Rome2Rio

Rome2Rio 在首次展示出行方案时,用 CSA 快速计算各模式(飞机/火车/巴士)的最早到达时间,生成"可行方案候选集"。然后对候选集用完整 RAPTOR 做精确多目标规划。

DB 的路线规划后端使用 CSA 变体,在全欧铁路时刻表(约 5000 万条连接)上实现 < 100ms 的最早到达查询。


参考资料

参考资料

  • Dibbelt et al., Connection Scan Algorithm (ACM Journal of Experimental Algorithmics, 2018) — CSA 原论文
  • RAPTOR(多目标路由首选,CSA 的功能超集)
  • 多标签 Pareto Dijkstra(扩展 CSA 到多目标的理论基础)
← 返回列表

评论 (0)

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

发表评论