跑步建议算法

算法概述

RunAdvisorService 是应用的核心算法服务,根据用户个人信息和天气状况生成三种不同强度的跑步建议方案。算法综合考虑 BMI 体质指数、年龄和天气适宜性三个维度,动态调整距离、配速和心率区间。

三种跑步方案

方案 runType 颜色 强度 定位
轻松跑 easy #4CAF50 (绿) 适合初学者和恢复训练
标准跑 standard #2196F3 (蓝) 适合日常锻炼
耐力跑 endurance #FF9800 (橙) 适合进阶跑者

九步算法详解

第一步:计算基础参数

从 UserProfile 中提取 BMI 和最大心率:

const bmi: number = profile.getBMI();
const maxHR: number = profile.getMaxHR();

BMI 计算公式:weight / (height/100)^2

最大心率公式:220 - age

第二步:判断天气适宜性

const isGoodWeather: boolean =
  weather !== undefined ? weather.isGoodForRunning() : true;

天气不适宜的条件(任一满足即判定为不宜):

  • 温度 < -10C 或 > 35C
  • 风速 > 15 m/s
  • 湿度 > 90%

第三步:计算心率区间

基于最大心率,按不同比例划分三个训练区间:

const easyHRMin: number = Math.round(maxHR * 0.60);
const easyHRMax: number = Math.round(maxHR * 0.65);
const stdHRMin: number  = Math.round(maxHR * 0.65);
const stdHRMax: number  = Math.round(maxHR * 0.70);
const endHRMin: number  = Math.round(maxHR * 0.70);
const endHRMax: number  = Math.round(maxHR * 0.80);

心率区间对照表:

方案 心率下限 心率上限 最大心率占比
轻松跑 60% HRmax 65% HRmax 0.60 - 0.65
标准跑 65% HRmax 70% HRmax 0.65 - 0.70
耐力跑 70% HRmax 80% HRmax 0.70 - 0.80

示例:30 岁用户 (maxHR = 190)

方案 心率区间
轻松跑 114 - 123 bpm
标准跑 123 - 133 bpm
耐力跑 133 - 152 bpm

第四步:设置基础距离与配速

为三种方案设定默认距离和配速:

let easyDist: number = 3;
let easyPaceMin: number = 7;
let easyPaceSec: number = 30;

let stdDist: number = 5;
let stdPaceMin: number = 6;
let stdPaceSec: number = 0;

let endDist: number = 10;
let endPaceMin: number = 5;
let endPaceSec: number = 30;

基础方案参数:

方案 默认距离 默认配速 含义
轻松跑 3 km 7’30"/km 慢跑恢复
标准跑 5 km 6’00"/km 中等有氧
耐力跑 10 km 5’30"/km 节奏跑

第五步:BMI 调整

根据 BMI 分类调整距离和配速,BMI 越高运动难度越大:

if (bmi > 28) {
  easyDist = 2;   easyPaceMin = 8;  easyPaceSec = 0;
  stdDist = 3;    stdPaceMin = 7;   stdPaceSec = 0;
  endDist = 5;    endPaceMin = 6;   endPaceSec = 30;
} else if (bmi < 18.5) {
  easyDist = 4;   easyPaceMin = 6;  easyPaceSec = 30;
  stdDist = 6;    stdPaceMin = 5;   stdPaceSec = 30;
  endDist = 12;   endPaceMin = 5;   endPaceSec = 0;
}

BMI 调整对照表:

BMI 范围 分类 轻松跑距离 标准跑距离 耐力跑距离
< 18.5 偏瘦 4 km 6 km 12 km
18.5 - 28 正常/偏胖 3 km 5 km 10 km
> 28 肥胖 2 km 3 km 5 km

第六步:年龄调整

根据年龄段微调配速和距离:

if (profile.age > 50) {
  easyPaceMin += 1;
  stdPaceMin += 1;
  endPaceMin += 1;
  easyDist = Math.max(2, easyDist - 1);
  stdDist = Math.max(3, stdDist - 1);
  endDist = Math.max(5, endDist - 2);
} else if (profile.age < 25) {
  easyPaceMin = Math.max(6, easyPaceMin - 1);
  stdPaceMin = Math.max(5, stdPaceMin - 1);
}

年龄调整逻辑:

年龄段 配速调整 距离调整 说明
< 25 岁 配速加快 1 min/km 不变 年轻体能好
25 - 50 岁 不变 不变 基准区间
> 50 岁 配速减慢 1 min/km 距离缩短 1-2km 安全优先

注意:距离调整使用 Math.max() 保证下限值,防止减至不合理数值。

第七步:天气调整

天气不适宜时缩短运动距离:

if (!isGoodWeather) {
  easyDist = Math.max(2, easyDist - 1);
  stdDist = Math.max(3, stdDist - 1);
  endDist = Math.max(4, endDist - 2);
}
方案 正常距离缩减 最小保障距离
轻松跑 -1 km 2 km
标准跑 -1 km 3 km
耐力跑 -2 km 4 km

第八步:计算时长与建议

根据调整后的距离和配速计算预计运动时长:

const easyDuration: number = Math.round(
  easyDist * easyPaceMin + easyDist * easyPaceSec / 60
);
const stdDuration: number = Math.round(
  stdDist * stdPaceMin + stdDist * stdPaceSec / 60
);
const endDuration: number = Math.round(
  endDist * endPaceMin + endDist * endPaceSec / 60
);

时长计算公式:距离 x 配速分钟 + 距离 x 配速秒 / 60

同时生成基础建议文本:

let easyTips: string = '轻松跑是恢复和基础训练的好方式,保持均匀呼吸';
let stdTips: string = '标准跑适合日常锻炼,注意心率不要过高';
let endTips: string = '耐力跑挑战较大,确保充分热身和补水';

如果天气数据可用,在建议前拼接天气提示:

if (weather !== undefined) {
  easyTips = weather.getWeatherAdvice() + '。' + easyTips;
  stdTips = weather.getWeatherAdvice() + '。' + stdTips;
  endTips = weather.getWeatherAdvice() + '。' + endTips;
}

第九步:生成 RunAdvice 对象

将所有计算结果封装为三个 RunAdvice 对象返回:

advices.push(RunAdvice.create(
  Constants.RUN_EASY, Constants.RUN_EASY_NAME, easyDist,
  easyPaceMin, easyPaceSec, easyHRMin, easyHRMax,
  easyDuration, 5, 5, easyTips, '低',
  Constants.RUN_EASY_COLOR,
  '适合初学者和恢复训练,低心率轻松跑'
));

advices.push(RunAdvice.create(
  Constants.RUN_STANDARD, Constants.RUN_STANDARD_NAME, stdDist,
  stdPaceMin, stdPaceSec, stdHRMin, stdHRMax,
  stdDuration, 8, 5, stdTips, '中',
  Constants.RUN_STANDARD_COLOR,
  '适合日常锻炼,中等强度有氧跑'
));

advices.push(RunAdvice.create(
  Constants.RUN_ENDURANCE, Constants.RUN_ENDURANCE_NAME, endDist,
  endPaceMin, endPaceSec, endHRMin, endHRMax,
  endDuration, 10, 8, endTips, '高',
  Constants.RUN_ENDURANCE_COLOR,
  '适合进阶跑者,长距离耐力训练'
));

热身和放松时长预设值:

方案 热身 放松
轻松跑 5 分钟 5 分钟
标准跑 8 分钟 5 分钟
耐力跑 10 分钟 8 分钟

算法示例

示例 1:正常体型青年

用户:体重 65kg,身高 175cm,年龄 25 岁,男性,天气晴好

BMI = 65 / 1.75^2 = 21.2 (正常)
maxHR = 220 - 25 = 195

轻松跑: 3km, 6'30"/km, HR 117-127, 20min
标准跑: 5km, 5'00"/km, HR 127-137, 25min
耐力跑: 10km, 5'30"/km, HR 137-156, 55min

示例 2:肥胖中年,天气不佳

用户:体重 95kg,身高 170cm,年龄 55 岁,男性,高温 36C

BMI = 95 / 1.70^2 = 32.9 (肥胖)
maxHR = 220 - 55 = 165

BMI调整: easy 2km/8'00", std 3km/7'00", end 5km/6'30"
年龄调整: +1 min/km 配速,距离缩短
天气调整: 距离再缩短

轻松跑: 2km, 9'00"/km, HR 99-107, 18min
标准跑: 3km, 8'00"/km, HR 107-116, 24min
耐力跑: 4km, 7'30"/km, HR 116-132, 30min

Tips: 高温预警,避免户外跑步,注意防暑。...

示例 3:偏瘦年轻人

用户:体重 50kg,身高 168cm,年龄 22 岁,女性,天气适宜

BMI = 50 / 1.68^2 = 17.7 (偏瘦)
maxHR = 220 - 22 = 198

BMI调整: easy 4km/6'30", std 6km/5'30", end 12km/5'00"
年龄调整: 配速减1 (5'30"->4'30" 但 max(6,6-1)=5 for easy)

轻松跑: 4km, 6'30"/km, HR 119-129, 26min
标准跑: 6km, 5'30"/km, HR 129-139, 33min
耐力跑: 12km, 5'00"/km, HR 139-158, 60min

建议页面展示

页面结构

+----------------------------------+
| 跑步建议                         |
|                                  |
| +------------------------------+ |
| | 轻松跑                       | |
| | 适合初学者和恢复训练...      | |
| |                    3km       | |
| |                    7'30"/km  | |
| | 目标心率: 114-123 bpm       | |
| | 预计时长: 23分钟            | |
| | 热身5分钟 | 放松5分钟       | |
| | 天气适宜跑步...             | |
| | [查看路线]                   | |
| +------------------------------+ |
|                                  |
| +------------------------------+ |
| | 标准跑                       | |
| | ...                          | |
| | [查看路线]                   | |
| +------------------------------+ |
|                                  |
| +------------------------------+ |
| | 耐力跑                       | |
| | ...                          | |
| | [查看路线]                   | |
| +------------------------------+ |
+----------------------------------+

查看路线跳转

点击"查看路线"按钮跳转到对应路线的地图详情页:

Button('查看路线')
  .backgroundColor(advice.color)
  .onClick(() => {
    this.navStack.pushPath({
      name: 'MapRoute',
      param: advice.runType  // 'easy' | 'standard' | 'endurance'
    });
  })

算法优化方向

  1. 引入跑步历史数据,基于用户实际配速动态调整
  2. 加入风向因素,逆风时增加配速建议
  3. 考虑海拔变化对配速的影响
  4. 引入心率变异性 (HRV) 判断恢复状态
  5. 基于天气预报提供未来几天训练计划

算法设计哲学

渐进安全原则

跑步建议算法的核心设计原则是"渐进安全"(Progressive Safety):宁可建议偏保守,也不能让用户冒险。这一原则体现在多个设计决策中:

  1. 距离调整使用 Math.max() 设置下限,确保缩减后仍为合理值
  2. 天气不适宜时一律缩短距离,不做"虽然高温但可以坚持"的判断
  3. 年龄超过 50 岁时配速和距离双重缩减,而非仅调整其一
  4. 心率区间基于最大心率百分比,而非绝对值,自动适配不同年龄

多维度交叉影响

算法的三个输入维度(BMI、年龄、天气)不是简单的加权叠加,而是按顺序逐层调整,每层的调整基于前一层的输出。这种串行调整模式确保了各维度的影响不会相互抵消:

基础参数 -> BMI调整 -> 年龄调整 -> 天气调整 -> 最终结果

例如,一个肥胖且年长的用户在恶劣天气下,三个维度的缩减会叠加,最终建议会非常保守(如轻松跑仅 2km、9’00"/km),这正是渐进安全原则的体现。

为什么提供三种方案

提供轻松跑、标准跑、耐力跑三种方案而非单一建议,基于以下考量:

  1. 用户状态每日不同(恢复日选轻松跑,状态好选耐力跑)
  2. 单一建议容易产生"要么跑要么不跑"的二元决策压力
  3. 三种方案覆盖了 60%-80% 最大心率区间,对应不同的训练效果
  4. 与三条预置路线(easy/standard/endurance)一一对应,实现建议到行动的闭环

BMI 分类与运动建议关系

BMI 分类标准

本应用采用中国肥胖问题工作组 (WGCPN) 的 BMI 分类标准,与 WHO 标准略有差异:

分类 BMI 范围 (中国标准) BMI 范围 (WHO标准) 差异说明
偏瘦 < 18.5 < 18.5 一致
正常 18.5 - 23.9 18.5 - 24.9 中国标准更严格
偏胖 24.0 - 27.9 25.0 - 29.9 更低的阈值
肥胖 >= 28.0 >= 30.0 更低的阈值

中国标准对超重和肥胖的判定阈值低于 WHO,反映了亚洲人体脂分布特点(相同 BMI 下亚洲人内脏脂肪比例更高,健康风险更大)。

BMI 对跑步建议的影响机制

BMI 通过影响距离和配速两个维度作用于建议:

BMI 分类 距离调整 配速调整 生理依据
偏瘦 (< 18.5) 增加 1-2km 加快 1 min/km 体重轻,关节负荷小,可承受更大运动量
正常 (18.5-28) 保持默认 保持默认 标准体质,适用默认参数
肥胖 (> 28) 减少 1-5km 减慢 0.5-1.5 min/km 体重增加关节冲击,心肺负荷大

偏瘦用户的特殊考虑

BMI < 18.5 的用户被建议增加运动距离和加快配速,但这并不意味着偏瘦人群应该大量运动。偏瘦用户通常需要的是增肌增重,而过量有氧运动反而会消耗更多热量。当前算法的建议更偏向"以运动促进食欲"的思路,未来可引入"增肌期建议减少有氧、增加力量训练"的提示。

年龄调整的科学依据

最大心率公式

本应用采用 220 - age 公式计算最大心率。这是最广泛使用的估算公式,由 Fox 等人于 1971 年提出。需要注意的是,该公式是统计平均值,个体差异可达 ±10 bpm。更精确的公式包括:

公式 计算 适用人群
Fox (1971) 220 - age 通用(本应用采用)
Tanaka (2001) 208 - 0.7 * age 中老年人更准确
Gellish (2007) 207 - 0.7 * age 活跃人群

当前选择 Fox 公式是因为其计算简单、认知度高。未来可考虑增加用户自测最大心率输入,绕过估算公式。

年龄分段阈值

年龄段 配速调整 距离调整 科学依据
< 25 岁 加快 1 min/km 不变 VO2max 峰值期,恢复能力强
25-50 岁 不变 不变 基准区间,运动能力稳定
> 50 岁 减慢 1 min/km 减少 1-2km 最大心率下降,关节退变风险增加

50 岁为调整阈值,基于以下医学考量:50 岁后最大心率平均每年下降约 1 bpm,肌肉量每十年减少约 8%,关节软骨磨损累积。适度降低运动强度有助于安全锻炼。

Math.max 下限保护

年龄调整中,距离缩减使用 Math.max 保证下限值:

easyDist = Math.max(2, easyDist - 1);
stdDist = Math.max(3, stdDist - 1);
endDist = Math.max(5, endDist - 2);

各方案的下限值不同:轻松跑最低 2km(约 15 分钟),标准跑最低 3km(约 20 分钟),耐力跑最低 5km(约 30 分钟)。这些下限值确保了即使多重缩减叠加,每种方案仍具有最低限度的运动量,避免出现"建议跑 0km"的无意义结果。

天气调整的运动生理学基础

高温对跑步的影响

气温超过 35C 时,人体核心体温上升,皮肤血管扩张以增加散热,导致流向肌肉的血液减少,运动能力下降。此外,大量出汗导致电解质流失,增加抽筋和中暑风险。因此高温天气下必须缩短运动距离。

低温对跑步的影响

气温低于 -10C 时,吸入的冷空气刺激气道,可能诱发运动性哮喘。肌肉在低温下黏滞性增加,弹性和收缩力下降,拉伤风险显著升高。关节滑液变稠,灵活性降低。

风速的影响

风速 > 15 m/s(约 7 级风)时,跑步姿态受影响,平衡控制困难,尤其对中老年跑者构成安全风险。逆风跑步时,空气阻力与速度的平方成正比,能耗大幅增加。

湿度的影响

湿度 > 90% 时,汗液蒸发几乎停止,人体主要散热机制失效。即使气温不高,高湿度也会导致核心体温快速上升,产生闷热不适感。

天气调整的保守策略

当天气判定为不适宜时,算法不是完全禁止跑步(isGoodForRunning 仅影响距离,不阻止建议生成),而是缩短距离保留运动机会。这符合"有运动总比没有好"的公共健康原则,同时通过 tips 文本提醒用户注意风险。

配速与时长的数学关系

时长计算公式

时长(分钟) = 距离(km) * 配速分钟 + 距离(km) * 配速秒 / 60

以标准跑(5km, 6’00"/km)为例:

  • 时长 = 5 * 6 + 5 * 0 / 60 = 30 分钟

以轻松跑(3km, 7’30"/km)为例:

  • 时长 = 3 * 7 + 3 * 30 / 60 = 21 + 1.5 = 22.5 -> Math.round = 23 分钟

配速与速度的换算

配速是"每公里所需时间",与速度互为倒数关系:

配速 速度 说明
5’00"/km 12 km/h 跑者水平
6’00"/km 10 km/h 慢跑
7’00"/km 8.57 km/h 轻松跑
7’30"/km 8 km/h 入门跑
8’00"/km 7.5 km/h 快走/慢跑过渡
9’00"/km 6.67 km/h 快走

心率训练区间详解

三个心率区间的训练效果

区间 心率范围 主要能量来源 训练效果
轻松区 (60-65%) 有氧区 脂肪为主 基础耐力、恢复、脂肪代谢
标准区 (65-70%) 有氧-无氧过渡 糖原+脂肪 心肺功能、有氧耐力
耐力区 (70-80%) 无氧阈附近 糖原为主 乳酸阈值提升、速度耐力

心率区间的实际应用

轻松跑(60-65% HRmax):这个区间的跑步应该可以轻松交谈,不会气喘。它是建立有氧基础的关键训练,也是恢复日的最佳选择。长期坚持轻松跑可以增加线粒体密度,提高脂肪氧化能力。

标准跑(65-70% HRmax):这个区间是日常训练的主力,呼吸明显加快但仍可断续说话。有效提升心脏泵血能力和毛细血管密度。

耐力跑(70-80% HRmax):这个区间接近乳酸阈值,说话困难,只能说出短句。训练效果是提升乳酸阈值,使跑者能在更高速度下维持更久。

心率区间计算示例

30 岁用户 (maxHR = 190 bpm):

方案 下限计算 上限计算 结果区间
轻松跑 190 * 0.60 = 114 190 * 0.65 = 123.5 -> 124 114-124
标准跑 190 * 0.65 = 123.5 -> 124 190 * 0.70 = 133 124-133
耐力跑 190 * 0.70 = 133 190 * 0.80 = 152 133-152

注意 Math.round 导致的边界重叠(124 同时是轻松跑上限和标准跑下限),在实际使用中这不构成问题,因为心率本身就有波动。

热身与放松时长设定

时长设定依据

方案 热身时长 放松时长 依据
轻松跑 5 分钟 5 分钟 低强度运动,短热身即可
标准跑 8 分钟 5 分钟 中强度需更充分热身
耐力跑 10 分钟 8 分钟 高强度需充分热身和较长放松

热身的作用是逐步提高心率和肌肉温度,增加关节灵活性,预防运动损伤。放松的作用是逐步降低心率,促进乳酸代谢,减少运动后延迟性肌肉酸痛 (DOMS)。

热身内容建议

当前算法仅提供热身时长,未提供具体动作建议。未来可扩展为:

  • 轻松跑热身:5 分钟快走 + 动态拉伸
  • 标准跑热身:5 分钟慢跑 + 动态拉伸 + 2 组跨步跑
  • 耐力跑热身:8 分钟渐进跑 + 动态拉伸 + 3 组跨步跑 + 2 组加速跑

建议文本生成策略

基础建议文本

三种方案各有一句基础建议:

轻松跑: "轻松跑是恢复和基础训练的好方式,保持均匀呼吸"
标准跑: "标准跑适合日常锻炼,注意心率不要过高"
耐力跑: "耐力跑挑战较大,确保充分热身和补水"

天气提示拼接

如果天气数据可用,在基础建议前拼接 getWeatherAdvice() 的结果:

天气适宜: "天气适宜跑步,享受运动吧!轻松跑是恢复和基础训练的好方式,保持均匀呼吸"
高温天气: "高温预警,避免户外跑步,注意防暑。轻松跑是恢复和基础训练的好方式,保持均匀呼吸"
大风天气: "风力较大,跑步时逆风会较吃力。标准跑适合日常锻炼,注意心率不要过高"

这种拼接方式确保了即使基础建议不变,天气提示也能为用户提供当日特定的运动指导。

算法边界情况分析

极端 BMI 值

BMI 分类 算法行为
15 严重偏瘦 偏瘦分支:距离增加,配速加快
18.4 临界偏瘦 偏瘦分支
18.5 正常下限 正常分支:默认参数
27.9 临界肥胖 正常分支
28.0 肥胖下限 肥胖分支:距离缩短,配速减慢
40+ 重度肥胖 肥胖分支(与BMI 28相同调整)

当前算法在 BMI > 28 后没有进一步分级。重度肥胖(BMI > 35)用户可能需要更保守的建议,如将轻松跑距离降至 1km 或建议步行替代。这是未来优化方向。

极端年龄值

年龄 算法行为
< 18 未成年,配速加快但无特殊警告
22 年轻分支:配速加快 1 min/km
25 基准区间开始
50 基准区间结束
51 年长分支:配速减慢,距离缩短
80+ 年长分支(与51岁相同调整)

算法没有对未成年人和极高龄人群做特殊处理。未成年人正在发育期,运动建议应考虑骨骼发育状况;80 岁以上用户可能存在心血管疾病风险,需要更保守的参数。这些边界情况在当前版本中未覆盖。

天气数据缺失

当 weather 参数为 undefined 时,算法按天气适宜处理(isGoodWeather = true)。这确保了网络不可用时仍能生成合理建议,只是缺少天气调整维度。

const isGoodWeather: boolean =
  weather !== undefined ? weather.isGoodForRunning() : true;

建议页面的路线联动

runType 到路线的映射

建议页面的"查看路线"按钮通过 advice.runType 传递参数,与 RouteService 的路线类型一一对应:

runType 建议 路线
easy 轻松跑 route_easy (3.5km, 入门)
standard 标准跑 route_standard (5.8km, 中等)
endurance 耐力跑 route_endurance (10.2km, 挑战)

地图页面参数传递

Button('查看路线')
  .backgroundColor(advice.color)
  .onClick(() => {
    this.navStack.pushPath({
      name: 'MapRoute',
      param: advice.runType
    });
  })

MapRoutePageNav 接收 param 后调用 RouteService.getRouteByType(param as string) 获取路线数据。这种参数传递方式简洁高效,避免了序列化整个 RouteInfo 对象。

算法完整流程图

getAdvice(profile, weather)
  |
  +-- 1. 计算 BMI 和 maxHR
  |     bmi = weight / (height/100)^2
  |     maxHR = 220 - age
  |
  +-- 2. 判断天气适宜性
  |     isGoodWeather = weather?.isGoodForRunning() ?? true
  |
  +-- 3. 计算心率区间
  |     easy: 60%-65% maxHR
  |     standard: 65%-70% maxHR
  |     endurance: 70%-80% maxHR
  |
  +-- 4. 设置基础距离与配速
  |     easy: 3km, 7'30"/km
  |     standard: 5km, 6'00"/km
  |     endurance: 10km, 5'30"/km
  |
  +-- 5. BMI 调整
  |     bmi>28: 减距离+减配速
  |     bmi<18.5: 增距离+增配速
  |
  +-- 6. 年龄调整
  |     age>50: 减距离+减配速
  |     age<25: 增配速
  |
  +-- 7. 天气调整
  |     !isGoodWeather: 减距离
  |
  +-- 8. 计算时长与生成建议文本
  |     duration = distance * pace
  |     tips = weatherAdvice + baseTips
  |
  +-- 9. 封装 RunAdvice 对象
        返回 [easyAdvice, standardAdvice, enduranceAdvice]
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐