前车查找算法设计与实现 oneLineCar 工单系统有个核心功能:根据当前车辆的位置,快速找到附近的前车(同线路的上一辆车)。这个看似简单的需求,背后涉及空间索引和算法优化。
需求场景
调度员需要知道每辆前车的位置
司机端需要显示前车距离
异常告警需要判断两车距离是否过近
初始方案:暴力遍历 最直接的思路:遍历所有车辆,计算距离,排序取最近的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function findPreviousCar (current : Car , allCars : Car [] ): Car | null { const candidates = allCars .filter (car => car.id !== current.id && car.lineId === current.lineId ) .filter (car => { const distance = calcDistance (current, car) return distance > 0 && distance < 5000 }) .sort ((a, b ) => { const distA = calcDistance (current, a) const distB = calcDistance (current, b) return distA - distB }) return candidates[0 ] || null }function calcDistance (a : Car , b : Car ): number { const R = 6371000 const dLat = toRad (b.lat - a.lat ) const dLon = toRad (b.lng - a.lng ) const x = Math .sin (dLat / 2 ) ** 2 + Math .cos (toRad (a.lat )) * Math .cos (toRad (b.lat )) * Math .sin (dLon / 2 ) ** 2 return R * 2 * Math .atan2 (Math .sqrt (x), Math .sqrt (1 - x)) }
100 辆车没问题,1000 辆车就开始卡了。
优化方案:GeoHash 空间索引 GeoHash 把二维经纬度编码成一维字符串,相同前缀的车辆在空间上相邻。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 class GeoHashIndex { private precision = 6 private buckets = new Map <string , Set <string >>() insert (car : Car ) { const hash = this .encode (car.lat , car.lng , this .precision ) if (!this .buckets .has (hash)) { this .buckets .set (hash, new Set ()) } this .buckets .get (hash)!.add (car.id ) } query (lat : lng : number , radius : number ): string [] { const centerHash = this .encode (lat, lng, this .precision ) const neighbors = this .getNeighbors (centerHash) const candidates = new Set <string >() for (const hash of [centerHash, ...neighbors]) { const cars = this .buckets .get (hash) if (cars) { for (const id of cars) { candidates.add (id) } } } return Array .from (candidates) } private encode (lat : number , lng : number , precision : number ): string { const chars = '0123456789bcdefghjkmnpqrstuvwxyz' let hash = '' let minLat = -90 , maxLat = 90 let minLng = -180 , maxLng = 180 let isLng = true let bit = 0 let ch = 0 while (hash.length < precision) { if (isLng) { const mid = (minLng + maxLng) / 2 if (lng >= mid) { ch |= (1 << (4 - bit)) minLng = mid } else { maxLng = mid } } else { const mid = (minLat + maxLat) / 2 if (lat >= mid) { ch |= (1 << (4 - bit)) minLat = mid } else { maxLat = mid } } isLng = !isLng if (bit < 4 ) { bit++ } else { hash += chars[ch] bit = 0 ch = 0 } } return hash } }
性能对比
方案
100 辆车
1000 辆车
10000 辆车
暴力遍历
2ms
18ms
850ms
GeoHash 索引
0.5ms
1.2ms
3.5ms
性能提升约 200 倍。
实际使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const geoIndex = new GeoHashIndex () allCars.forEach (car => geoIndex.insert (car))function findPreviousCarOptimized (current : Car ): Car | null { const nearbyIds = geoIndex.query (current.lat , current.lng , 5000 ) const nearbyCars = nearbyIds .map (id => carMap.get (id)!) .filter (car => car.lineId === current.lineId && car.id !== current.id ) return nearbyCars .sort ((a, b ) => calcDistance (current, a) - calcDistance (current, b))[0 ] || null }
经验总结
先量化再优化 — 暴力方案先上线,有性能问题再优化
空间索引是刚需 — 涉及地理位置查询,GeoHash 是性价比最高的方案
精度可调 — GeoHash 的 precision 参数控制索引精度,按场景调整
缓存热点数据 — 车辆位置变化频繁,可以用内存缓存 + 定时刷新