每日算法 — 使用java实现自行车:路径规划与能量最优策略

引言

自行车骑行是一项兼顾运动健身与绿色出行的活动,长距离骑行中的路径选择与能量管理直接影响骑行效率和体验。本文用Java实现自行车骑行的路径规划与能量最优策略,讲解地形建模、能量消耗计算与Dijkstra最短路径算法在户外运动优化中的应用。

物理模型建立

骑行力学分析

自行车骑行时的总阻力由四个分量组成:

  1. 滚动阻力:轮胎与地面的摩擦
  2. 空气阻力:与速度平方成正比
  3. 坡度阻力:重力沿坡面的分量
  4. 加速阻力:改变速度所需的惯性力

能量消耗模型

骑行功率输出:

$$P = F_{total} \cdot v = (F_{roll} + F_{air} + F_{grade} + F_{accel}) \cdot v$$

其中:
– $F_{roll} = C_{rr} \cdot m \cdot g \cdot \cos(\theta)$
– $F_{air} = \frac{1}{2} \cdot \rho \cdot C_d \cdot A \cdot v^2$
– $F_{grade} = m \cdot g \cdot \sin(\theta)$
– $F_{accel} = m \cdot a$

核心算法设计

1. 地形与路径数据结构

import java.util.*;

/**
 * 路径节点
 * 表示地图上的一个位置点
 */
class PathNode {
    int id;
    // 经纬度或平面坐标
    double x, y;
    // 海拔高度 (m)
    double elevation;
    // 节点名称
    String name;

    public PathNode(int id, double x, double y, double elevation, String name) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.elevation = elevation;
        this.name = name;
    }

    /**
     * 计算到另一个节点的距离
     */
    public double distanceTo(PathNode other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        double dz = this.elevation - other.elevation;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     * 计算到另一个节点的水平距离
     */
    public double horizontalDistanceTo(PathNode other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 计算到另一个节点的坡度角
     */
    public double slopeAngleTo(PathNode other) {
        double horizontalDist = horizontalDistanceTo(other);
        if (horizontalDist == 0) return 0;
        double elevationDiff = other.elevation - this.elevation;
        return Math.atan2(elevationDiff, horizontalDist);
    }
}

/**
 * 路径边
 * 连接两个节点的道路段
 */
class PathEdge {
    int from;
    int to;
    // 道路类型
    RoadType roadType;
    // 路面质量 0~1
    double surfaceQuality;
    // 是否为单行道
    boolean oneWay;

    public PathEdge(int from, int to, RoadType roadType, double surfaceQuality) {
        this.from = from;
        this.to = to;
        this.roadType = roadType;
        this.surfaceQuality = surfaceQuality;
        this.oneWay = false;
    }
}

/**
 * 道路类型枚举
 * 不同道路类型有不同的摩擦系数和通行速度
 */
enum RoadType {
    HIGHWAY(0.004, 50, 1.0),      // 高速公路
    MAIN_ROAD(0.006, 30, 0.9),    // 主干道
    SECONDARY(0.008, 20, 0.8),    // 次干道
    RESIDENTIAL(0.010, 15, 0.7),  // 居民区道路
    PATH(0.015, 10, 0.6),         // 小路
    TRAIL(0.020, 8, 0.5),         // 山地小径
    CYCLEWAY(0.005, 25, 1.0);     // 专用自行车道

    // 滚动阻力系数
    final double rollingCoeff;
    // 建议速度 (km/h)
    final double suggestedSpeed;
    // 骑行舒适度
    final double comfortFactor;

    RoadType(double rollingCoeff, double suggestedSpeed, double comfortFactor) {
        this.rollingCoeff = rollingCoeff;
        this.suggestedSpeed = suggestedSpeed;
        this.comfortFactor = comfortFactor;
    }
}

/**
 * 骑行地图类
 */
class CyclingMap {
    List<PathNode> nodes;
    // 邻接表:节点ID -> 出边列表
    Map<Integer, List<PathEdge>> adjacency;

    public CyclingMap() {
        this.nodes = new ArrayList<>();
        this.adjacency = new HashMap<>();
    }

    public void addNode(PathNode node) {
        nodes.add(node);
        adjacency.put(node.id, new ArrayList<>());
    }

    public void addEdge(PathEdge edge) {
        adjacency.get(edge.from).add(edge);
        if (!edge.oneWay) {
            // 双向边(添加反向)
            PathEdge reverse = new PathEdge(edge.to, edge.from, edge.roadType, edge.surfaceQuality);
            adjacency.get(edge.to).add(reverse);
        }
    }

    /**
     * 创建示例骑行地图
     */
    public static CyclingMap createSampleMap() {
        CyclingMap map = new CyclingMap();

        // 创建节点(模拟山地地形)
        map.addNode(new PathNode(0, 0, 0, 100, "起点-山脚"));
        map.addNode(new PathNode(1, 2, 1, 120, "缓坡路段A"));
        map.addNode(new PathNode(2, 4, 0, 150, "盘山路口"));
        map.addNode(new PathNode(3, 6, 2, 200, "半山腰"));
        map.addNode(new PathNode(4, 5, 4, 250, "陡坡路段"));
        map.addNode(new PathNode(5, 3, 5, 300, "山顶"));
        map.addNode(new PathNode(6, 7, 1, 180, "东侧山脊"));
        map.addNode(new PathNode(7, 8, 3, 220, "观景台"));
        map.addNode(new PathNode(8, 6, 5, 280, "北坡"));
        map.addNode(new PathNode(9, 4, 6, 150, "西侧河谷"));
        map.addNode(new PathNode(10, 2, 7, 120, "林间小道"));
        map.addNode(new PathNode(11, 0, 8, 100, "终点-返程"));

        // 添加边
        map.addEdge(new PathEdge(0, 1, RoadType.CYCLEWAY, 0.9));
        map.addEdge(new PathEdge(1, 2, RoadType.SECONDARY, 0.8));
        map.addEdge(new PathEdge(2, 3, RoadType.MAIN_ROAD, 0.85));
        map.addEdge(new PathEdge(3, 4, RoadType.SECONDARY, 0.7));
        map.addEdge(new PathEdge(4, 5, RoadType.PATH, 0.6));
        map.addEdge(new PathEdge(2, 6, RoadType.TRAIL, 0.5));
        map.addEdge(new PathEdge(6, 7, RoadType.PATH, 0.6));
        map.addEdge(new PathEdge(7, 8, RoadType.TRAIL, 0.5));
        map.addEdge(new PathEdge(8, 5, RoadType.PATH, 0.4));
        map.addEdge(new PathEdge(3, 9, RoadType.SECONDARY, 0.8));
        map.addEdge(new PathEdge(9, 10, RoadType.RESIDENTIAL, 0.7));
        map.addEdge(new PathEdge(10, 11, RoadType.CYCLEWAY, 0.9));
        map.addEdge(new PathEdge(5, 11, RoadType.HIGHWAY, 1.0)); // 下坡快速通道

        return map;
    }
}

2. 能量消耗计算器

/**
 * 骑行能量模型
 * 计算通过某段路径的能量消耗
 */
class EnergyModel {
    // 骑行者总质量 (kg):人体 + 自行车
    double totalMass;
    // 空气阻力系数 * 迎风面积
    double dragArea;
    // 空气密度
    static final double AIR_DENSITY = 1.225;
    // 重力加速度
    static final double GRAVITY = 9.81;
    // 传动效率
    static final double EFFICIENCY = 0.95;

    public EnergyModel(double riderMass, double bikeMass, double dragArea) {
        this.totalMass = riderMass + bikeMass;
        this.dragArea = dragArea;
    }

    /**
     * 计算通过一段路径的能量消耗 (Joules)
     * 
     * @param from 起点
     * @param to 终点
     * @param edge 路径边信息
     * @param targetSpeed 目标速度 (m/s)
     * @return 能量消耗 (J)
     */
    public double calculateEnergyCost(PathNode from, PathNode to, PathEdge edge, double targetSpeed) {
        double horizontalDist = from.horizontalDistanceTo(to);
        double elevationDiff = to.elevation - from.elevation;
        double slopeAngle = from.slopeAngleTo(to);

        RoadType road = edge.roadType;

        // 1. 滚动阻力做功
        double rollForce = road.rollingCoeff * totalMass * GRAVITY * Math.cos(slopeAngle);
        double rollWork = rollForce * horizontalDist;

        // 2. 空气阻力做功
        double airForce = 0.5 * AIR_DENSITY * dragArea * targetSpeed * targetSpeed;
        double airWork = airForce * horizontalDist;

        // 3. 坡度做功(上坡为正,下坡为负)
        double gradeWork = totalMass * GRAVITY * elevationDiff;

        // 4. 总机械功
        double totalMechanicalWork = rollWork + airWork + gradeWork;

        // 考虑传动效率(人体输出能量 = 机械功 / 效率)
        double humanEnergy = totalMechanicalWork / EFFICIENCY;

        // 下坡时重力做正功,但人体仍需输出最小维持能量
        if (humanEnergy < rollWork * 0.3) {
            humanEnergy = rollWork * 0.3; // 最小维持功率
        }

        return Math.max(0, humanEnergy);
    }

    /**
     * 计算通过一段路径的时间 (seconds)
     */
    public double calculateTime(PathNode from, PathNode to, PathEdge edge, double targetSpeed) {
        double horizontalDist = from.horizontalDistanceTo(to);
        double slopeAngle = from.slopeAngleTo(to);

        // 上坡时降低速度
        double adjustedSpeed = targetSpeed;
        if (slopeAngle > 0.02) { // 坡度 > 2%
            adjustedSpeed *= (1 - slopeAngle * 2); // 速度衰减
        }
        // 下坡时提高速度(考虑安全限制)
        if (slopeAngle < -0.02) {
            adjustedSpeed *= (1 + Math.min(-slopeAngle, 0.1));
        }

        // 路面质量影响
        adjustedSpeed *= edge.surfaceQuality;

        // 建议速度限制
        adjustedSpeed = Math.min(adjustedSpeed, edge.roadType.suggestedSpeed / 3.6);

        return horizontalDist / adjustedSpeed;
    }

    /**
     * 计算综合成本(能量与时间的加权组合)
     * 
     * @param energyWeight 能量权重
     * @param timeWeight 时间权重
     */
    public double calculateCompositeCost(PathNode from, PathNode to, PathEdge edge, 
                                          double targetSpeed, double energyWeight, double timeWeight) {
        double energy = calculateEnergyCost(from, to, edge, targetSpeed);
        double time = calculateTime(from, to, edge, targetSpeed);

        // 归一化处理
        double normalizedEnergy = energy / 10000.0; // 假设典型值
        double normalizedTime = time / 100.0;

        return energyWeight * normalizedEnergy + timeWeight * normalizedTime;
    }
}

3. 最优路径规划算法

/**
 * Dijkstra路径规划器
 * 支持多种优化目标:最短距离、最少能量、最短时间、综合最优
 */
class RoutePlanner {

    /**
     * 路径结果
     */
    static class RouteResult {
        List<Integer> path;
        double totalCost;
        double totalDistance;
        double totalEnergy;
        double totalTime;
        double totalElevationGain;

        public RouteResult() {
            this.path = new ArrayList<>();
        }
    }

    /**
     * 使用Dijkstra算法寻找最优路径
     * 
     * @param map 地图
     * @param start 起点ID
     * @param target 终点ID
     * @param energyModel 能量模型
     * @param targetSpeed 目标速度
     * @param optimizationType 优化类型
     * @return 最优路径
     */
    public static RouteResult findOptimalRoute(CyclingMap map, int start, int target,
                                                EnergyModel energyModel, double targetSpeed,
                                                OptimizationType optimizationType) {
        int n = map.nodes.size();

        // 距离数组
        double[] dist = new double[n];
        Arrays.fill(dist, Double.MAX_VALUE);
        dist[start] = 0;

        // 前驱节点
        int[] prev = new int[n];
        Arrays.fill(prev, -1);

        // 访问标记
        boolean[] visited = new boolean[n];

        // 优先队列
        PriorityQueue<NodeDist> pq = new PriorityQueue<>();
        pq.offer(new NodeDist(start, 0));

        while (!pq.isEmpty()) {
            NodeDist current = pq.poll();
            int u = current.nodeId;

            if (visited[u]) continue;
            visited[u] = true;

            if (u == target) break;

            // 遍历邻接边
            for (PathEdge edge : map.adjacency.getOrDefault(u, new ArrayList<>())) {
                int v = edge.to;
                if (visited[v]) continue;

                PathNode fromNode = map.nodes.get(u);
                PathNode toNode = map.nodes.get(v);

                // 计算边的成本
                double edgeCost = calculateEdgeCost(fromNode, toNode, edge, energyModel, 
                                                     targetSpeed, optimizationType);

                double newDist = dist[u] + edgeCost;

                if (newDist < dist[v]) {
                    dist[v] = newDist;
                    prev[v] = u;
                    pq.offer(new NodeDist(v, newDist));
                }
            }
        }

        // 构建路径
        RouteResult result = new RouteResult();
        result.totalCost = dist[target];

        if (dist[target] == Double.MAX_VALUE) {
            return result; // 不可达
        }

        // 回溯路径
        int at = target;
        while (at != -1) {
            result.path.add(0, at);
            at = prev[at];
        }

        // 计算路径统计信息
        computeRouteStats(result, map, energyModel, targetSpeed);

        return result;
    }

    /**
     * 根据优化类型计算边成本
     */
    private static double calculateEdgeCost(PathNode from, PathNode to, PathEdge edge,
                                             EnergyModel energyModel, double targetSpeed,
                                             OptimizationType type) {
        switch (type) {
            case SHORTEST_DISTANCE:
                return from.horizontalDistanceTo(to);

            case MIN_ENERGY:
                return energyModel.calculateEnergyCost(from, to, edge, targetSpeed);

            case FASTEST_TIME:
                return energyModel.calculateTime(from, to, edge, targetSpeed);

            case BALANCED:
                return energyModel.calculateCompositeCost(from, to, edge, targetSpeed, 0.6, 0.4);

            case MIN_ELEVATION:
                double elevDiff = to.elevation - from.elevation;
                return from.horizontalDistanceTo(to) + Math.max(0, elevDiff) * 10;

            case MOST_SCENIC:
                // 假设风景值与海拔成正比
                double scenicValue = (from.elevation + to.elevation) / 2;
                return from.horizontalDistanceTo(to) / (1 + scenicValue * 0.001);

            default:
                return from.horizontalDistanceTo(to);
        }
    }

    /**
     * 计算路径统计信息
     */
    private static void computeRouteStats(RouteResult result, CyclingMap map,
                                          EnergyModel energyModel, double targetSpeed) {
        for (int i = 0; i < result.path.size() - 1; i++) {
            int u = result.path.get(i);
            int v = result.path.get(i + 1);

            PathNode from = map.nodes.get(u);
            PathNode to = map.nodes.get(v);

            // 找到对应的边
            PathEdge edge = null;
            for (PathEdge e : map.adjacency.get(u)) {
                if (e.to == v) {
                    edge = e;
                    break;
                }
            }

            if (edge != null) {
                result.totalDistance += from.horizontalDistanceTo(to);
                result.totalEnergy += energyModel.calculateEnergyCost(from, to, edge, targetSpeed);
                result.totalTime += energyModel.calculateTime(from, to, edge, targetSpeed);
                double elevDiff = to.elevation - from.elevation;
                if (elevDiff > 0) {
                    result.totalElevationGain += elevDiff;
                }
            }
        }
    }
}

/**
 * 优化类型枚举
 */
enum OptimizationType {
    SHORTEST_DISTANCE,  // 最短距离
    MIN_ENERGY,         // 最少能量
    FASTEST_TIME,       // 最短时间
    BALANCED,           // 能量与时间平衡
    MIN_ELEVATION,      // 最少爬坡
    MOST_SCENIC         // 最美风景
}

/**
 * 节点距离类(用于优先队列)
 */
class NodeDist implements Comparable<NodeDist> {
    int nodeId;
    double distance;

    public NodeDist(int nodeId, double distance) {
        this.nodeId = nodeId;
        this.distance = distance;
    }

    @Override
    public int compareTo(NodeDist other) {
        return Double.compare(this.distance, other.distance);
    }
}

4. 能量最优策略分析

/**
 * 骑行策略分析器
 * 分析不同策略下的能量消耗
 */
class CyclingStrategyAnalyzer {

    /**
     * 分析不同速度下的能量效率
     * 
     * @param map 地图
     * @param path 路径节点列表
     * @param energyModel 能量模型
     * @return 最优速度建议
     */
    public static double analyzeOptimalSpeed(CyclingMap map, List<Integer> path, 
                                              EnergyModel energyModel) {
        System.out.println("\n=== 速度-能量效率分析 ===");
        System.out.println("速度(km/h) | 总能量(kJ) | 总时间(min) | 能量效率(kJ/km)");
        System.out.println("-----------|------------|-------------|----------------");

        double bestEfficiency = Double.MAX_VALUE;
        double bestSpeed = 0;

        for (int speedKmh = 10; speedKmh <= 40; speedKmh += 2) {
            double speed = speedKmh / 3.6; // m/s
            double totalEnergy = 0;
            double totalDist = 0;
            double totalTime = 0;

            for (int i = 0; i < path.size() - 1; i++) {
                int u = path.get(i);
                int v = path.get(i + 1);
                PathNode from = map.nodes.get(u);
                PathNode to = map.nodes.get(v);

                PathEdge edge = null;
                for (PathEdge e : map.adjacency.get(u)) {
                    if (e.to == v) {
                        edge = e;
                        break;
                    }
                }

                if (edge != null) {
                    totalEnergy += energyModel.calculateEnergyCost(from, to, edge, speed);
                    totalDist += from.horizontalDistanceTo(to);
                    totalTime += energyModel.calculateTime(from, to, edge, speed);
                }
            }

            double efficiency = totalEnergy / totalDist / 1000; // kJ/km
            System.out.printf("%10d | %10.1f | %11.1f | %15.2f%n",
                speedKmh, totalEnergy / 1000, totalTime / 60, efficiency);

            if (efficiency < bestEfficiency) {
                bestEfficiency = efficiency;
                bestSpeed = speed;
            }
        }

        System.out.printf("%n最优速度: %.1f km/h (能量效率: %.2f kJ/km)%n",
            bestSpeed * 3.6, bestEfficiency);

        return bestSpeed;
    }

    /**
     * 分析不同优化策略的对比
     */
    public static void compareStrategies(CyclingMap map, int start, int target,
                                          EnergyModel energyModel, double targetSpeed) {
        System.out.println("\n=== 不同优化策略对比 ===");
        System.out.println("策略 | 距离(km) | 能量(kJ) | 时间(min) | 爬升(m) | 路径节点数");
        System.out.println("-----|----------|----------|-----------|---------|----------");

        for (OptimizationType type : OptimizationType.values()) {
            RoutePlanner.RouteResult result = RoutePlanner.findOptimalRoute(
                map, start, target, energyModel, targetSpeed, type);

            if (result.path.isEmpty()) {
                System.out.printf("%-18s | 无路径%n", type);
                continue;
            }

            System.out.printf("%-18s | %8.2f | %8.1f | %9.1f | %7.1f | %10d%n",
                type,
                result.totalDistance / 1000,
                result.totalEnergy / 1000,
                result.totalTime / 60,
                result.totalElevationGain,
                result.path.size());
        }
    }
}

5. 完整模拟器

/**
 * 自行车路径规划模拟器
 */
public class BicycleGame {

    public static void main(String[] args) {
        // 创建地图
        CyclingMap map = CyclingMap.createSampleMap();

        // 创建能量模型(70kg骑手 + 10kg自行车)
        EnergyModel energyModel = new EnergyModel(70, 10, 0.4);

        // 起点和终点
        int start = 0;   // 山脚
        int target = 11; // 终点
        double targetSpeed = 20 / 3.6; // 20 km/h

        System.out.println("=== 自行车路径规划与能量最优策略 ===");
        System.out.println("地图节点数: " + map.nodes.size());
        System.out.printf("骑行者: 70kg + 自行车10kg = 总质量%.0fkg%n", energyModel.totalMass);
        System.out.printf("起点: %s -> 终点: %s%n",
            map.nodes.get(start).name, map.nodes.get(target).name);

        // 比较不同策略
        CyclingStrategyAnalyzer.compareStrategies(map, start, target, energyModel, targetSpeed);

        // 获取能量最优路径
        RoutePlanner.RouteResult energyOptimal = RoutePlanner.findOptimalRoute(
            map, start, target, energyModel, targetSpeed, OptimizationType.MIN_ENERGY);

        System.out.println("\n=== 能量最优路径详情 ===");
        System.out.println("路径节点: " + energyOptimal.path);
        System.out.printf("总距离: %.2f km%n", energyOptimal.totalDistance / 1000);
        System.out.printf("总能量消耗: %.1f kJ (约 %.1f kcal)%n",
            energyOptimal.totalEnergy / 1000, energyOptimal.totalEnergy / 4184);
        System.out.printf("预计时间: %.1f 分钟%n", energyOptimal.totalTime / 60);
        System.out.printf("总爬升: %.1f m%n", energyOptimal.totalElevationGain);

        System.out.println("\n路径详情:");
        for (int i = 0; i < energyOptimal.path.size(); i++) {
            PathNode node = map.nodes.get(energyOptimal.path.get(i));
            System.out.printf("  %d. %s (海拔: %.0fm)%n", i + 1, node.name, node.elevation);
        }

        // 速度分析
        double optimalSpeed = CyclingStrategyAnalyzer.analyzeOptimalSpeed(
            map, energyOptimal.path, energyModel);

        // 使用最优速度重新规划
        RoutePlanner.RouteResult refined = RoutePlanner.findOptimalRoute(
            map, start, target, energyModel, optimalSpeed, OptimizationType.BALANCED);

        System.out.println("\n=== 最优速度重新规划结果 ===");
        System.out.printf("最优速度: %.1f km/h%n", optimalSpeed * 3.6);
        System.out.printf("平衡优化路径距离: %.2f km%n", refined.totalDistance / 1000);
        System.out.printf("平衡优化能量消耗: %.1f kJ%n", refined.totalEnergy / 1000);
        System.out.printf("平衡优化预计时间: %.1f 分钟%n", refined.totalTime / 60);
    }
}

算法复杂度分析

组件 时间复杂度 空间复杂度
地图构建 O(N + E) O(N + E)
单条边能量计算 O(1) O(1)
Dijkstra路径规划 O(E log V) O(V)
速度分析 O(S·E) O(1)
策略对比 O(T·E log V) O(V)

其中 N 为节点数,E 为边数,V 为顶点数,S 为速度测试点数,T 为策略数。

策略分析

骑行能量优化的核心原则

  1. 匀速优于变速:频繁加速减速浪费能量,保持匀速最经济
  2. 下坡蓄能:利用下坡加速,减少平地和上坡的能量消耗
  3. 空气阻力控制:超过25km/h后空气阻力占主导,降低速度可显著节能
  4. 坡度利用:缓坡骑行效率高,陡坡应考虑推行或绕行

算法扩展方向

  1. 实时路径重规划:根据当前体力状态动态调整路线
  2. 天气预报集成:考虑风向风速对骑行能量的影响
  3. 历史数据学习:基于个人骑行数据校准能量模型参数
  4. 多人协同规划:组队骑行时的编队效应(跟车减阻)

总结

本文通过Java实现了自行车骑行的路径规划与能量最优策略。关键算法包括:
– 骑行力学模型:滚动阻力、空气阻力、坡度阻力的精确计算
– Dijkstra路径规划:支持多种优化目标的最短路径算法
– 能量效率分析:速度-能量关系的量化分析
– 多策略对比:距离、能量、时间、风景等多维度路径评估

自行车路径规划的算法思想在物流路径优化、电动汽车能耗预测、户外运动导航等领域有广泛应用。