以下是对SLAM算法岗位面试常见问题的详细解答,涵盖理论知识、C++基础及算法题等核心内容,结合工程实践与数学原理展开说明:
面试问题主要参考:https://zhuanlan.zhihu.com/p/27904353682
一、SLAM算法理论知识
2.1 基础数学部分
- 请谈谈对流形的理解?
流形(Manifold)是一类局部具有欧几里得空间(欧式空间)性质的拓扑空间,核心特征是“局部欧式”:即流形上的每一点都存在一个邻域,该邻域可与某个低维欧式空间(如ℝⁿ)通过连续可逆映射(同胚)对应。
在SLAM中,流形是描述旋转、位姿等非欧几里得状态的关键工具。例如:
- 三维旋转矩阵构成的SO(3)群是一个3维流形(局部可与ℝ³同胚,可用李代数so(3)的3维向量表示);
- 刚性变换矩阵构成的SE(3)群是一个6维流形(局部对应ℝ⁶,用李代数se(6)表示)。
流形的“非线性”特性决定了SLAM中状态优化需用特殊方法(如李代数线性化),而非直接在欧式空间中运算。
- 什么是李群?什么是李代数?
-
李群(Lie Group):是兼具“群结构”和“光滑流形结构”的数学对象。
- 群结构:满足封闭性、结合律、单位元、逆元(如SO(3)中旋转矩阵相乘仍为旋转矩阵,存在单位矩阵和逆矩阵);
- 光滑流形结构:群运算(乘法、逆)是光滑映射(可无限次微分)。
实际意义:SLAM中刚体运动(旋转、平移)的集合天然构成李群(如SO(3)、SE(3)),用于描述状态的“变换规则”。
-
李代数(Lie Algebra):是与李群关联的线性空间,定义为“李群在单位元处的切空间”,并配备双线性“李括号”运算。
- 切空间:描述李群在单位元附近的局部线性近似(如SO(3)的单位元是 Identity 矩阵,其切空间so(3)是3×3反对称矩阵的集合);
- 李括号:刻画元素的“非交换性”(如so(3)的李括号为[φ, ψ] = φ×ψ,反映旋转的非交换性)。
实际意义:将李群的非线性运算转化为李代数的线性运算,简化SLAM中的状态优化(如用so(3)的向量表示旋转的微小变化)。
- 请谈谈李群和李代数的关系?
李群与李代数是“全局非线性结构”与“局部线性近似”的对应关系,核心关联是指数映射和对数映射:
- 指数映射:将李代数元素映射到李群(如so(3)的向量φ通过指数映射exp(φ^)生成SO(3)的旋转矩阵R,具体为罗德里格斯公式);
- 对数映射:将李群元素(单位元附近)映射回李代数(如旋转矩阵R通过对数映射log®得到so(3)的向量φ)。
- 局部等价性:李代数完全描述李群在单位元附近的局部结构,SLAM中利用这一特性将大范围的李群运算转化为小范围内的李代数线性运算(如EKF中的状态更新)。
- 实例:SO(3) ↔ so(3)、SE(3) ↔ se(3)是SLAM中最常用的对应关系,分别描述旋转和刚体变换的建模。
- 雅可比矩阵、扰动模型和BCH近似公式的理解
- 雅可比矩阵:是多元函数偏导数的矩阵形式,描述输入微小变化对输出的影响。在SLAM中,用于非线性误差函数的线性化(如重投影误差对相机位姿的导数),是优化和滤波中不可或缺的工具。
- 扰动模型:通过对李群状态(如旋转R)施加微小扰动(如φ∈so(3)),将李群上的非线性变换转化为李代数上的线性运算,简化雅可比计算。例如:对R∈SO(3),扰动后R’=R·exp(φ^)≈R + R·φ^,此时误差函数对φ的导数可直接推导,避免直接对R求导的复杂性。
- BCH近似公式:用于处理李群乘法的指数映射分解。由于李群乘法不满足交换律(如R1·R2≠R2·R1),BCH公式给出近似:
exp(φ)·exp(ψ) ≈ exp( (φ + ψ + [φ,ψ]/2 + … )^ )
其中[φ,ψ]是李括号。在SLAM中,当扰动较小时(如局部优化),可忽略高阶项,简化状态更新(如EKF中用一阶近似合并多个旋转)。
2.2 多传感器融合的相关问题
- 请简述KF、EKF、ESKF的流程及区别
-
KF(卡尔曼滤波):适用于线性系统,流程为:
- 预测:用状态方程预测当前状态和协方差(x̂ₖ⁻ = Aₖx̂ₖ₋₁ + Bₖuₖ,Pₖ⁻ = AₖPₖ₋₁Aₖᵀ + Qₖ);
- 更新:用观测方程修正预测(Kₖ = Pₖ⁻Hₖᵀ(HₖPₖ⁻Hₖᵀ + Rₖ)⁻¹,x̂ₖ = x̂ₖ⁻ + Kₖ(zₖ - Hₖx̂ₖ⁻),Pₖ = (I - KₖHₖ)Pₖ⁻)。
-
EKF(扩展卡尔曼滤波):适用于非线性系统,核心是对非线性函数(状态方程f、观测方程h)在当前状态处线性化(用雅可比矩阵F、H近似),后续流程同KF。
-
ESKF(误差状态卡尔曼滤波):专为带噪声参数(如IMU零偏)的系统设计,将“误差状态”(真实状态与标称状态的差)作为滤波对象,标称状态用传感器原始数据积分更新,误差状态通过EKF估计并反馈修正标称状态。
-
区别:
- KF:线性系统,无近似;
- EKF:非线性系统,直接线性化状态,易因非线性强导致发散;
- ESKF:误差状态线性化(更稳定),分离标称状态与误差,适合IMU等带漂移的传感器。
- Kalman filter中Q和R怎么调?
-
Q(过程噪声协方差):描述系统模型的不确定性(如IMU的漂移、运动模型误差)。
- 若模型精确(如高精度IMU、无打滑轮速计),Q调小;
- 若模型粗糙(如低成本IMU、复杂地形),Q调大(允许更大的预测误差)。
-
R(观测噪声协方差):描述观测数据的不确定性(如LiDAR配准误差、相机重投影误差)。
- 若观测精确(如RTK定位、高精度点云匹配),R调小;
- 若观测嘈杂(如动态环境的视觉数据),R调大(降低观测权重)。
-
工程实践:
- 初始用经验值(如传感器手册的噪声参数);
- 结合自适应方法(如Sage-Husa滤波),根据残差(zₖ - Hₖx̂ₖ⁻)动态调整:残差大则增大对应Q或R;
- 针对典型场景(如长廊、转弯)手动微调,平衡鲁棒性与精度。
- ESKF中bias的reset方法及区别
-
硬重置(Hard Reset):直接将bias估计值置零,同时将其协方差重置为初始值(如传感器手册的零偏不确定性)。
- 优点:简单快速,适合bias严重漂移(如IMU受冲击后);
- 缺点:丢失历史估计信息,可能导致状态跳变。
-
软重置(Soft Reset):通过观测更新逐步修正bias,不直接清零,而是利用新观测的约束调整bias的后验估计(如增大观测权重,迫使bias向零收敛)。
- 优点:平滑过渡,避免状态突变;
- 缺点:收敛较慢,需持续有效的观测。
- 传感器的标定及时间戳同步
-
传感器标定:
- 内参标定:传感器自身参数(如相机焦距、畸变系数;LiDAR的角分辨率、测距误差;IMU的尺度因子、轴间耦合);
- 外参标定:传感器间的相对位姿(如相机与LiDAR的旋转R和平移t),常用方法有手眼标定、基于优化的联合标定。
-
时间戳同步:
- 硬件同步:通过同步脉冲(如PTP协议、GPIO触发)使多传感器共享同一时钟,时间戳误差<1ms;
- 软件同步:对异步数据按时间戳插值(如将IMU数据线性插值到相机帧时间戳,或用滑动窗口对齐),适用于无硬件同步的场景,误差取决于采样频率(如100Hz IMU同步到10Hz相机,误差<10ms)。
- IMU的噪声模型及数学表达
-
测量噪声:加速度计和陀螺仪的瞬时噪声,服从零均值高斯白噪声(与时间无关)。
- 数学表达:测量值 = 真实值 + 噪声,即
a_meas = a_true + n_a(n_a ~ N(0, σₐ²I)),
ω_meas = ω_true + n_ω(n_ω ~ N(0, σ_ω²I)),
其中σₐ、σ_ω为噪声密度(单位:m/s²/√Hz、rad/s/√Hz)。
- 数学表达:测量值 = 真实值 + 噪声,即
-
零偏(Bias):缓慢变化的系统性误差,服从随机游走模型(布朗运动,方差随时间累积)。
- 数学表达:bias_k = bias_{k-1} + w(w ~ N(0, σ_b²Δt)),
其中σ_b为零偏随机游走系数(单位:m/s³/√Hz、rad/s²/√Hz),Δt为采样时间。
- 数学表达:bias_k = bias_{k-1} + w(w ~ N(0, σ_b²Δt)),
- IMU的初始化校准及技术规格书参数
-
初始化校准:
- 零偏校准:静止时,假设ω_true=0、a_true=g(重力),通过滑动窗口平均计算初始零偏(b_ω₀ = mean(ω_meas),b_a₀ = mean(a_meas) - g);
- 重力校准:静止时,加速度计读数应沿重力方向,用最小二乘拟合a_meas = R·g + b_a,求解重力向量g的方向。
-
技术规格书参数:噪声密度(σₐ、σ_ω)、零偏不稳定性(bias instability)、量程等,直接指导Q矩阵配置(如σ_ω大则Q中对应角速度噪声项调大)。
- LiDAR数据获取原理及机械式与固态式对比
-
数据获取原理:发射激光束到物体表面,通过反射光的飞行时间(ToF)计算距离,结合扫描角度得到点云坐标(x,y,z)。
-
机械式LiDAR:通过旋转电机带动激光发射器/接收器,逐行扫描环境(如16线、32线)。
- 优点:视场角大(360°水平)、技术成熟、点云密度均匀;
- 缺点:体积大、机械磨损寿命短(<10000小时)、旋转部件易受振动影响。
-
固态式LiDAR:无机械旋转,通过MEMS微振镜(小角度偏转)或OPA光学相控阵(电子转向)控制激光方向。
- 优点:体积小、寿命长(>100000小时)、抗振动;
- 缺点:视场角小(如±30°水平)、点云密度不均(中心密边缘疏)、成本高。
- GNSS/RTK的数据特点
- GNSS:通过卫星信号计算绝对位置,精度为米级(民用GPS约5-10m),受天气、遮挡影响大(如城市峡谷、室内失效)。
- RTK(实时动态差分):通过基准站与移动站的载波相位差分,消除卫星钟差、电离层延迟等误差,精度达厘米级(1-5cm),但依赖基准站信号,无信号时失效快(<10s)。
- GNSS误差来源及影响
-
误差来源:
- 电离层/对流层延迟:信号传播速度变化,导致距离误差(可达数十米);
- 多路径效应:信号经建筑物反射后被接收,引入米级误差;
- 卫星钟差/轨道误差:卫星时间或位置不准,导致定位漂移;
- 接收机噪声:硬件精度限制,引入亚米级误差。
-
影响:定位跳变(多路径)、长期漂移(电离层延迟)、完全失效(遮挡),需融合IMU/LiDAR弥补。
- 规避GNSS误差的方法
- 多传感器融合:用IMU(短期高精度)、LiDAR(相对定位)弥补GNSS失效时段;
- 组合导航:RTK+INS(惯性导航系统),INS平滑GNSS跳变,GNSS校准INS漂移;
- 环境感知:通过语义分割识别遮挡区域(如隧道),主动降低GNSS权重;
- 抗干扰技术:使用抗多路径天线、自适应滤波剔除异常GNSS数据。
- 图优化与滤波方法的优缺点
-
滤波方法(如EKF):
- 优点:递归更新,计算量小(O(n²),n为状态维度),实时性好;
- 缺点:只保留当前状态,误差累积(尤其长轨迹),对非线性模型敏感。
-
图优化方法(如g2o、ceres):
- 优点:全局优化所有状态(历史+当前),通过回环检测消除累积误差,精度高;
- 缺点:计算量大(O(n³)),实时性差(需滑动窗口优化平衡)。
- 图优化与滤波方法的数学统一性
- 本质上,两者均基于最大后验概率估计(MAP):
- 滤波:递归计算p(xₖ|z₁:ₖ) ∝ p(zₖ|xₖ)p(xₖ|xₖ₋₁)p(xₖ₋₁|z₁:ₖ₋₁),等价于对历史状态做马尔可夫假设(只依赖前一状态);
- 图优化:直接建模p(x₁:ₖ|z₁:ₖ) ∝ ∏p(zᵢ|xᵢ,xⱼ)∏p(xᵢ|xᵢ₋₁),无马尔可夫假设,保留所有状态关联。
- 当图优化的约束仅包含相邻帧(无回环)时,等价于滤波的MAP估计,可通过贝叶斯公式推导证明两者在高斯噪声假设下一致。
- 优化库的使用(ceres/g2o/gtsam)
- 均支持自定义残差函数(因子/边),核心是定义误差计算方式:
- ceres:通过
CostFunction
或自动求导(AutoDiffCostFunction
)定义残差,如重投影误差:residual =观测 - 投影(参数)
; - g2o:定义
Edge
类,重写computeError()
和linearizeOplus()
(雅可比); - gtsam:定义
Factor
类,如BetweenFactor
描述相对位姿约束。
- ceres:通过
- 工程中需根据精度需求选择求导方式(自动/数值/解析),解析求导精度最高但实现复杂。
- 紧耦合与松耦合的区别
-
松耦合:先独立处理各传感器的状态估计(如视觉里程计输出位姿、IMU输出位姿),再融合结果(如用EKF融合两个位姿)。
- 优点:模块独立,易调试;
- 缺点:依赖子模块精度,子模块误差会累积到融合结果。
-
紧耦合:直接融合传感器原始数据(如视觉特征点、IMU测量值),共同构建约束(如用IMU预积分辅助视觉特征匹配)。
- 优点:充分利用原始信息,精度高,对单个传感器噪声更鲁棒;
- 缺点:模块耦合度高,实现复杂,需处理多传感器时间同步。
2.3 点云配准的相关问题
- 点云配准的三大假设
- 点云来自同一刚性物体(无形变);
- 存在唯一刚性变换(旋转+平移)使两点云对齐;
- 对应点间的误差服从高斯分布(或可通过鲁棒核函数处理)。
-
点云配准的基本流程
-
预处理:去噪(统计滤波)、下采样(体素网格),减少数据量;
-
特征提取与匹配:提取关键点(如ISS)和描述子(如FPFH),通过最近邻匹配找对应点;
-
变换估计:用SVD或LM优化求解使对应点距离最小的变换矩阵(R,t);
-
迭代优化:用估计的变换更新源点云,重复匹配-估计步骤,直至收敛(如ICP迭代)。
-
ICP、GICP、NDT的差异与优缺点
-
ICP(迭代最近点):
- 原理:通过最近邻找对应点,最小化点到点距离;
- 优点:简单直观,实现容易;
- 缺点:对初始位姿敏感(易陷入局部最优),对噪声和离群点鲁棒性差。
-
GICP(广义ICP):
- 原理:考虑点云法向量,用马氏距离(考虑协方差)加权对应点误差,更符合激光点云的噪声分布;
- 优点:比ICP鲁棒,收敛速度快;
- 缺点:需计算点云法向量,预处理复杂。
-
NDT(正态分布变换):
- 原理:将目标点云转换为体素概率分布(高斯分布),通过优化变换使源点云在目标分布中的似然最大;
- 优点:无需精确对应点,对初始位姿不敏感,速度快;
- 缺点:体素大小影响精度(大则粗糙,小则计算量大)。
- 影响点云配准精度的关键因素
- 初始位姿误差(过大则不收敛);
- 点云噪声与离群点(破坏对应点准确性);
- 场景特征丰富度(如纯平面场景,自由度高,易歧义);
- 采样密度(过疏丢失细节,过密计算量大)。
- 影响点云配准鲁棒性的关键因素
- 离群点处理(如RANSAC剔除误匹配);
- 鲁棒核函数(如Huber核、Cauchy核,降低异常值权重);
- 特征独特性(重复场景易导致误匹配);
- 迭代策略(如多尺度迭代,从粗到精优化)。
- 动态障碍物剔除与离群点滤除
-
动态障碍物剔除:
- 运动检测:对比连续帧点云,计算点的位移,超过阈值则视为动态;
- 语义分割:用深度学习(如PointNet)识别车辆、行人等动态类别,直接剔除。
-
离群点滤除算法:
- 统计滤波:计算点到邻域点的平均距离,超过均值+3σ则剔除;
- 半径滤波:若点的邻域内点数少于阈值,则视为离群点;
- RANSAC:拟合平面/直线模型,剔除不满足模型的点。
- 点云处理操作与算法
- 去噪:统计滤波、双边滤波(保边去噪);
- 滤波:体素网格下采样(降采样)、直通滤波(按坐标范围筛选);
- 分割:RANSAC平面分割(提取地面)、区域生长(聚类相似点);
- 特征提取:FPFH(快速点特征直方图)、VFH(视角特征直方图),用于配准和回环检测。
2.4 激光SLAM算法的相关问题
- AMCL、Hector、Karto、Cartographer的差异与贡献
-
AMCL(自适应蒙特卡洛定位):
- 流程:基于粒子滤波,用激光扫描匹配更新粒子权重,估计机器人在已知地图中的位姿;
- 贡献:解决2D激光在动态环境中的定位鲁棒性问题。
-
Hector SLAM:
- 流程:无需里程计,直接用高斯牛顿优化激光扫描匹配(基于栅格地图);
- 贡献:适用于无里程计场景,但依赖平坦地面假设。
-
Karto SLAM:
- 流程:基于图优化,用扫描匹配构建节点(位姿)和边(相对约束),通过SPA(稀疏 Pose Adjustment)求解;
- 贡献:首次将图优化大规模应用于激光SLAM,奠定后端优化框架。
-
Cartographer:
- 流程:前端用分支定界匹配(Scan Matching)生成局部轨迹,后端用图优化(带回环检测)全局优化;
- 贡献:支持2D/3D,实时性与精度平衡,广泛用于工程实践。
- Autoware中的SLAM算法(LOAM、LIO-SAM、FAST-LIO)
-
LOAM(Lidar Odometry and Mapping):
- 流程:分离里程计(低帧率,精匹配)和建图(高帧率,粗匹配),用边缘和平面特征配准;
- 贡献:首次实现激光里程计与建图分离,为后续融合IMU奠定基础。
-
LIO-SAM:
- 流程:融合LiDAR与IMU,用IMU预积分提供初始位姿,激光特征匹配构建因子图,加入回环检测;
- 贡献:紧耦合融合,鲁棒性强,支持大规模室外场景。
-
FAST-LIO:
- 流程:基于迭代卡尔曼滤波(IKF),紧耦合LiDAR点与IMU数据,无需特征提取,直接用点云残差更新状态;
- 贡献:实时性优异(100Hz+),适合嵌入式平台。
- 回环检测算法及影响因素
-
算法:
- 词袋模型(BoW):将点云特征编码为“词”,通过词频匹配判断回环;
- 点云相似度:计算两帧点云的重叠区域、特征距离(如FPFH距离);
- 位置一致性:结合GNSS粗定位,只在相近位置检测回环。
-
影响因素:
- 场景变化(光照、季节变化导致特征差异);
- 特征独特性(重复场景如走廊易误检测);
- 时间跨度(太久导致场景变化大)。
- scan2scan、scan2map、map2map的配准方式
- scan2scan:相邻帧点云匹配(如LOAM前端),累积误差大,适合短距离;
- scan2map:当前帧与全局地图匹配(如Hector),累积误差小,适合长距离;
- map2map:子图间匹配(如Cartographer回环),用于消除全局误差。
- 倾向:优先scan2map(平衡精度与实时性),结合map2map(回环检测)。
- 占栅格地图生成原理
- 将环境划分为栅格(如5cm×5cm),通过激光测距判断栅格“占据概率”:
- 激光击中的栅格:占据概率增加(P_occ);
- 激光未击中但路径经过的栅格:占据概率降低(P_free);
- 初始概率为0.5(未知),通过贝叶斯更新公式迭代更新:
logit§ = log(P/(1-P)) = logit(P_prev) + log(P_occ/(1-P_occ)) (击中)
或 + log(P_free/(1-P_free)) (未击中)。
- 激光雷达精度与SLAM定位精度的矛盾
- 不矛盾:激光雷达±2cm是单帧测量精度,而SLAM通过多帧数据融合(如优化、回环检测)消除累积误差:
- 局部优化:多帧点云配准平均误差,降低单帧噪声影响;
- 回环检测:全局调整轨迹,消除长期漂移;
- 因此,全局优化后的定位精度(±1cm)可高于单帧传感器精度。
- 基于LiDAR的三维重建算法及Gaussian Splatting
- 三维重建算法:泊松重建(从点云生成网格)、TSDF(截断符号距离函数,融合多帧点云)。
- Gaussian Splatting:
- 原理:用大量3D高斯分布表示场景,每个高斯有位置、协方差(形状)、颜色,通过渲染损失优化高斯参数;
- 优势:重建质量高,支持自由视角渲染;
- 挑战:计算量大,动态场景处理难。
- 影响GS-SLAM质量的因素及规避方法
- 因素:高斯数量(太少细节丢失,太多计算量大)、动态物体(导致高斯参数混淆)、传感器噪声(影响高斯位置估计)。
- 规避:自适应高斯数量(密集区域多,稀疏区域少)、动态物体掩码(剔除动态点)、融合IMU减少运动模糊。
二、C++相关基础问题
- 面向过程与面向对象编程的优缺点
-
面向过程:
- 优点:流程清晰,适合简单任务,执行效率高;
- 缺点:数据与函数分离,扩展性差(修改一处影响全局)。
-
面向对象:
- 优点:封装(数据隐藏)、继承(代码复用)、多态(接口统一),易维护扩展;
- 缺点:结构复杂,简单任务效率略低。
- 指针和引用的区别
- 指针是变量,存储内存地址,可空(nullptr)、可修改指向;
- 引用是别名,必须初始化且不可空,指向不可修改,更安全;
- sizeof(指针)是地址长度(如8字节),sizeof(引用)是被引用对象的大小。
- 多态、虚函数与纯虚函数
- 多态:同一接口(如基类函数)在不同派生类中有不同实现,通过基类指针/引用调用时自动匹配派生类实现。
- 虚函数:基类中用
virtual
声明,有默认实现,派生类可重写; - 纯虚函数:
virtual 返回类型 函数名() = 0
,无实现,派生类必须重写,用于定义接口(抽象类)。
- 基类析构函数设为虚函数的原因
- 防止“基类指针指向派生类对象”时,销毁对象只调用基类析构函数,导致派生类资源未释放(内存泄漏)。
- 例:
Base* p = new Derived; delete p;
若Base析构非虚,只调用Base::~Base(),Derived的资源泄漏。
-
C++类型转换及特点
| 转换方式 | 特点 |
|----------------|----------------------------------------------------------------------|
| static_cast | 编译时转换,支持基本类型、派生类→基类,不检查安全性(如基类→派生类可能出错)。 |
| dynamic_cast | 运行时转换,需基类有虚函数,基类→派生类时检查有效性,失败返回nullptr。 |
| const_cast | 移除变量的const属性,仅用于指针/引用。 |
| reinterpret_cast | 低层次重解释(如指针→整数),依赖平台,不安全。 | -
基类指针调用派生类对象的类型转换
- 用
dynamic_cast
,需基类有虚函数(确保运行时类型信息RTTI可用):
Derived* d = dynamic_cast<Derived*>(b);
(b是Base*)。
- static_cast与dynamic_cast的效率
- static_cast效率高:编译时完成转换,无运行时开销;
- dynamic_cast效率低:运行时需查询RTTI(类型信息),判断转换合法性,有额外开销。
- 智能指针及区别
- unique_ptr:独占所有权,不可复制(只能移动),内存占用小,适合管理单个对象;
- shared_ptr:共享所有权,引用计数,计数为0时释放,支持复制,有额外内存开销(控制块);
- weak_ptr:不增加引用计数,解决shared_ptr的循环引用问题(如A→B,B→A时计数无法归零)。
- unique_ptr的赋值允许情况
- 源为右值(临时对象)时允许,通过
std::move
转移所有权:
unique_ptr<int> u1(new int); unique_ptr<int> u2 = move(u1);
(u1此后为nullptr)。
- C++锁及适用场景
- mutex:基础互斥锁,保证临界区独占访问;
- recursive_mutex:可重入锁,同一线程可多次锁定(避免死锁);
- lock_guard:RAII机制,构造时加锁,析构时解锁,适合简单场景;
- unique_lock:更灵活,支持延迟加锁、超时、移动,适合条件变量(condition_variable)。
-
堆与栈的区别
| 维度 | 栈(Stack) | 堆(Heap) |
|------------|--------------------------------------|-------------------------------------|
| 内存管理 | 编译器自动分配释放(函数调用时) | 手动分配释放(new/malloc/free/delete)|
| 大小 | 小(通常几MB) | 大(可达GB级) |
| 速度 | 快(直接地址访问) | 慢(需遍历空闲链表) |
| 数据结构 | 后进先出(LIFO) | 无序(类似链表/树) |
| 存储内容 | 局部变量、函数参数、返回地址 | 动态分配的对象、数组 | -
STD容器及特点
- vector:动态数组,随机访问O(1),尾部插入O(1),中间插入O(n);
- list:双向链表,插入删除O(1),随机访问O(n);
- deque:双端队列,头尾插入O(1),随机访问O(1);
- map:红黑树,键值有序,插入/查找O(logn),支持范围查询;
- unordered_map:哈希表,键值无序,插入/查找平均O(1),哈希冲突时退化O(n);
- set/unordered_set:分别对应map/unordered_map,仅存键(无值)。
- vector与vector的区别
- vector是特化版本,每个元素仅占1位(空间优化),而非1字节;
- 不支持迭代器算术运算(如it+1),访问效率低;
- 通常建议用
vector<char>
替代(空间稍大但操作更直观)。
- vector的resize和reserve的区别
- resize(n):改变元素个数为n,新增元素默认初始化(如int为0),
size()
变为n; - reserve(n):预分配容量(capacity)至少为n,不改变元素个数(
size()
不变),避免多次扩容(提升性能)。
- map与unordered_map的区别
| 特性 | map | unordered_map |
|--------------|-------------------------|-------------------------|
| 底层实现 | 红黑树 | 哈希表 |
| 有序性 | 键有序(按<排序) | 键无序 |
| 查找复杂度 | O(logn) | 平均O(1),最坏O(n) |
| 插入/删除 | O(logn) | 平均O(1) |
| 内存开销 | 小(无哈希表开销) | 大(哈希表+链表) |
- 时间效率优先:选unordered_map(如SLAM中特征点ID查询)。
- 插入中间且访问频繁的容器建议
- 用list(插入中间O(1))配合vector(访问O(1)):低频插入时先存在vector,高频访问;高频插入时用list,需访问时拷贝到vector;
- 或用deque(平衡插入与访问效率),避免vector(中间插入O(n))。
- 左值、右值及右值引用的场景
- 左值:可寻址的变量(有名称),如
int a; a是左值
; - 右值:临时对象(无名称),如
3+4
、move(a)
; - 右值引用(&&) 场景:
- 移动语义:转移资源(如
unique_ptr
的赋值),避免深拷贝; - 完美转发:保持参数的左值/右值属性(如模板函数中
forward<T>(t)
)。
- 移动语义:转移资源(如
三、算法题思路
4.1 SLAM工程实践相关算法题
- 自定义残差函数(以ceres为例)
struct MyCostFunction {
MyCostFunction(double x, double y) : x_(x), y_(y) {}
template <typename T>
bool operator()(const T* const a, const T* const b, T* residual) const {
residual[0] = y_ - (a[0] * x_ + b[0]); // 线性模型残差:y - (ax + b)
return true;
}
double x_, y_;
};
// 使用:
ceres::Problem problem;
problem.AddResidualBlock(
new ceres::AutoDiffCostFunction<MyCostFunction, 1, 1, 1>(
new MyCostFunction(x, y)),
nullptr, &a, &b); // a和b是待优化参数
- 高效碰撞检测(分离轴定理)
对两个凸体,若存在一条轴使两者投影不重叠,则无碰撞。步骤:
- 提取两物体的所有边的法向量(候选轴);
- 分别投影两物体到各轴,检查是否重叠;
- 所有轴均重叠则碰撞。
- 三角形内随机数生成
用 barycentric 坐标:
- 生成两个[0,1)随机数u、v,若u + v > 1,则u=1-u,v=1-v;
- 三角形三点A、B、C,随机点P = (1-u-v)A + uB + vC。
- 位姿插值
- 旋转插值:用Slerp(球面线性插值):
R = R0 * Slerp(0, 1, t)
; - 平移插值:线性插值:
t = t0 + (t1 - t0) * s
(s为时间比例);
TimedPose interpolate(const TimedPose& p0, const TimedPose& p1, double t) {
double s = (t - p0.time_stamp) / (p1.time_stamp - p0.time_stamp);
Sophus::SE3d pose = p0.pose.slerp(s, p1.pose); // Sophus库实现
return {pose, t};
}
- submap相似度计算
- 用哈希表存储各submap的特征值;
- 交集大小:遍历一个submap的特征,计数在另一submap中出现的次数;
- 并集大小 = 特征总数 - 交集大小;
- 相似度 = 交集 / 并集,排序后输出。
- rosbag读写
#include <rosbag/bag.h>
#include <rosbag/view.h>
#include <sensor_msgs/PointCloud2.h>
void copyBag(const std::string& in_path, const std::string& out_path) {
rosbag::Bag in_bag(in_path, rosbag::bagmode::Read);
rosbag::Bag out_bag(out_path, rosbag::bagmode::Write);
rosbag::View view(in_bag);
for (const auto& msg : view) {
out_bag.write(msg.getTopic(), msg.getTime(), msg); // 直接写入
}
in_bag.close();
out_bag.close();
}
- 均值和协方差计算
#include <Eigen/Dense>
std::pair<Eigen::Vector3d, Eigen::Matrix3d> computeMeanCov(
const std::vector<Eigen::Vector3d>& points) {
int n = points.size();
Eigen::Vector3d mean = Eigen::Vector3d::Zero();
for (const auto& p : points) mean += p;
mean /= n;
Eigen::Matrix3d cov = Eigen::Matrix3d::Zero();
for (const auto& p : points) {
Eigen::Vector3d diff = p - mean;
cov += diff * diff.transpose();
}
cov /= (n - 1); // 无偏估计
return {mean, cov};
}
-
高维均值和协方差
类似低维,用Eigen::MatrixXd存储高维点,协方差矩阵维度为d×d(d为维度)。 -
空间平面拟合(SVD)
平面方程:ax + by + cz + d = 0,步骤:
- 计算点云均值μ;
- 构建中心化矩阵A(每行是p_i - μ);
- 对A进行SVD分解,A = UΣVᵀ,V的最后一列是平面法向量(a,b,c);
- d = -(aμ.x() + bμ.y() + cμ.z())。
-
点到平面距离
已知平面ax + by + cz + d = 0,点(x,y,z),距离为|ax + by + cz + d| / sqrt(a² + b² + c²)
。 -
三维点的operator<
按x、y、z依次比较:
struct Point3D { double x, y, z; };
bool operator<(const Point3D& a, const Point3D& b) {
if (a.x != b.x) return a.x < b.x;
if (a.y != b.y) return a.y < b.y;
return a.z < b.z;
}
4.2 LeetCode算法题(核心思路)
- 旋转图像(48):先转置矩阵,再反转每行;
- 岛屿数量(200):DFS/BFS遍历,遇到’1’则计数并标记为’0’;
- 课程表(207):拓扑排序(入度表+队列),检测有向图是否无环;
- 最小覆盖子串(76):滑动窗口+哈希表,记录窗口内字符频率;
- 合并区间(56):排序后遍历,重叠则合并;
- 直线上最多的点(149):枚举每个点,计算其他点的斜率(用分数表示避免浮点数误差),统计最大频次。
以上内容覆盖SLAM面试核心考点,建议结合工程实践(如阅读LOAM、LIO-SAM源码)加深理解,同时针对性练习算法题与C++特性。