一、问题背景
登山者从山脚出发前往山顶,需要选择一条最优路径。路径的选择不仅要考虑距离最短,还要考虑海拔变化带来的能量消耗。上坡消耗能量,下坡可以部分恢复能量(但膝盖损耗限制恢复效率)。本文用Java实现登山路径的图论建模,并用动态规划求解能量最优路径。
二、核心算法思路
2.1 图论建模
将登山地形建模为带权无向图:
– 节点:地形采样点(具有海拔属性)
– 边:相邻采样点之间的可通行路径
– 边权:通过该路段所需的净能量消耗
2.2 能量计算模型
能量消耗由以下因素决定:
– 上坡:E_up = m * g * Δh / η,其中 η 为人体能量转换效率(约25%)
– 下坡:E_down = m * g * Δh * (1 - recoveryRate),回收部分势能
– 平路:基础代谢能量 E_flat = c * distance
2.3 动态规划求解
使用Dijkstra算法的变种求解能量最优路径。由于能量消耗满足最优子结构,状态定义为:dp[node][energyLevel] = 到达该节点的最小总能量消耗。
为简化实现,本文采用标准Dijkstra算法,以能量消耗作为边权。
三、Java实现
import java.util.*;
/**
* 登山路径规划:图论建模与动态规划最优能量路径
*/
public class MountainClimbingDP {
// 物理常量
private static final double GRAVITY = 9.8;
private static final double HUMAN_EFFICIENCY = 0.25;
private static final double RECOVERY_RATE = 0.15;
private static final double BASE_METABOLIC_COST = 3.5; // J/(kg·m)
/**
* 地形节点
*/
static class Node {
int id;
double x, y; // 平面坐标 (m)
double elevation; // 海拔 (m)
Node(int id, double x, double y, double elevation) {
this.id = id;
this.x = x;
this.y = y;
this.elevation = elevation;
}
/**
* 计算到另一节点的平面距离
*/
double distanceTo(Node other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算到另一节点的坡度角
*/
double slopeAngleTo(Node other) {
double dist = distanceTo(other);
if (dist == 0) return 0;
double dh = other.elevation - this.elevation;
return Math.atan2(dh, dist);
}
}
/**
* 边:连接两个地形节点
*/
static class Edge {
Node from, to;
double energyCost; // 通过该边的净能量消耗 (J/kg)
Edge(Node from, Node to) {
this.from = from;
this.to = to;
this.energyCost = computeEnergyCost();
}
/**
* 计算单位质量通过该边的能量消耗
*/
private double computeEnergyCost() {
double dist = from.distanceTo(to);
double dh = to.elevation - from.elevation;
double baseCost = BASE_METABOLIC_COST * dist;
if (dh > 0) {
// 上坡:需要额外克服重力做功,考虑人体效率
double climbCost = GRAVITY * dh / HUMAN_EFFICIENCY;
return baseCost + climbCost;
} else {
// 下坡:势能部分回收,但仍有基础代谢和膝盖损耗
double descentRecovery = GRAVITY * Math.abs(dh) * RECOVERY_RATE;
return baseCost - descentRecovery;
}
}
}
/**
* 地形图
*/
static class TerrainGraph {
List<Node> nodes = new ArrayList<>();
Map<Integer, List<Edge>> adjacency = new HashMap<>();
void addNode(Node node) {
nodes.add(node);
adjacency.put(node.id, new ArrayList<>());
}
void addEdge(Node a, Node b) {
Edge e1 = new Edge(a, b);
Edge e2 = new Edge(b, a);
adjacency.get(a.id).add(e1);
adjacency.get(b.id).add(e2);
}
/**
* 获取邻居边
*/
List<Edge> getNeighbors(int nodeId) {
return adjacency.getOrDefault(nodeId, new ArrayList<>());
}
}
/**
* Dijkstra算法求解能量最优路径
*/
static class PathFinder {
/**
* 返回从起点到终点的最优路径及总能量消耗
*/
Result findOptimalPath(TerrainGraph graph, int startId, int goalId) {
int n = graph.nodes.size();
double[] dist = new double[n];
int[] prev = new int[n];
boolean[] visited = new boolean[n];
Arrays.fill(dist, Double.MAX_VALUE);
Arrays.fill(prev, -1);
dist[startId] = 0;
// 优先队列:按能量消耗排序
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingDouble(a -> dist[a[0]]));
pq.offer(new int[]{startId});
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int u = curr[0];
if (visited[u]) continue;
visited[u] = true;
if (u == goalId) break;
for (Edge edge : graph.getNeighbors(u)) {
int v = edge.to.id;
if (visited[v]) continue;
double newDist = dist[u] + edge.energyCost;
if (newDist < dist[v]) {
dist[v] = newDist;
prev[v] = u;
pq.offer(new int[]{v});
}
}
}
// 重建路径
List<Integer> path = new ArrayList<>();
for (int at = goalId; at != -1; at = prev[at]) {
path.add(at);
}
Collections.reverse(path);
return new Result(path, dist[goalId]);
}
}
static class Result {
List<Integer> path;
double totalEnergy;
Result(List<Integer> path, double totalEnergy) {
this.path = path;
this.totalEnergy = totalEnergy;
}
}
/**
* 生成示例地形:螺旋山形
*/
static TerrainGraph generateSpiralMountain(int layers) {
TerrainGraph graph = new TerrainGraph();
Random rand = new Random(42);
// 创建节点
int id = 0;
for (int layer = 0; layer <= layers; layer++) {
double radius = (layers - layer) * 50.0;
double elevation = layer * 100.0 + rand.nextDouble() * 20;
int pointsInLayer = Math.max(6, layer * 4);
for (int i = 0; i < pointsInLayer; i++) {
double angle = 2 * Math.PI * i / pointsInLayer;
double x = radius * Math.cos(angle);
double y = radius * Math.sin(angle);
graph.addNode(new Node(id++, x, y, elevation));
}
}
// 连接相邻层的节点
int idx = 0;
for (int layer = 0; layer < layers; layer++) {
int currPoints = Math.max(6, layer * 4);
int nextPoints = Math.max(6, (layer + 1) * 4);
int currStart = idx;
int nextStart = idx + currPoints;
for (int i = 0; i < currPoints; i++) {
Node currNode = graph.nodes.get(currStart + i);
// 连接到下一层的最近节点
for (int j = 0; j < nextPoints; j++) {
Node nextNode = graph.nodes.get(nextStart + j);
double dist = currNode.distanceTo(nextNode);
if (dist < 80) {
graph.addEdge(currNode, nextNode);
}
}
// 连接同层的相邻节点
Node nextInLayer = graph.nodes.get(currStart + (i + 1) % currPoints);
graph.addEdge(currNode, nextInLayer);
}
idx += currPoints;
}
return graph;
}
public static void main(String[] args) {
// 生成地形
TerrainGraph mountain = generateSpiralMountain(8);
System.out.println("=== 登山路径规划 ===");
System.out.printf("地形节点数: %d%n", mountain.nodes.size());
// 寻找山脚(海拔最低)和山顶(海拔最高)
int startId = 0, goalId = 0;
double minElev = Double.MAX_VALUE, maxElev = -Double.MAX_VALUE;
for (Node node : mountain.nodes) {
if (node.elevation < minElev) {
minElev = node.elevation;
startId = node.id;
}
if (node.elevation > maxElev) {
maxElev = node.elevation;
goalId = node.id;
}
}
System.out.printf("山脚节点: #%d (海拔%.1fm)%n", startId, minElev);
System.out.printf("山顶节点: #%d (海拔%.1fm)%n%n", goalId, maxElev);
// 求解最优路径
PathFinder finder = new PathFinder();
Result result = finder.findOptimalPath(mountain, startId, goalId);
System.out.printf("最优路径节点数: %d%n", result.path.size());
System.out.printf("单位质量能量消耗: %.2f J/kg%n", result.totalEnergy);
System.out.printf("70kg登山者总能量: %.2f kJ%n", result.totalEnergy * 70 / 1000);
System.out.println("\n路径详情:");
double totalDist = 0;
for (int i = 0; i < result.path.size(); i++) {
Node node = mountain.nodes.get(result.path.get(i));
System.out.printf(" [%d] 节点#%d (%.1f, %.1f, 海拔%.1fm)%n",
i, node.id, node.x, node.y, node.elevation);
if (i > 0) {
Node prev = mountain.nodes.get(result.path.get(i - 1));
totalDist += prev.distanceTo(node);
}
}
System.out.printf("\n总路程: %.2f米%n", totalDist);
}
}
四、算法复杂度分析
- 建图:
O(V + E),V为节点数,E为边数 - Dijkstra算法:
O(E log V),使用优先队列实现 - 路径重建:
O(V)
五、扩展思考
- 多目标优化:同时优化能量消耗、时间和风景评分,使用帕累托前沿求解
- 动态地形:考虑天气导致的地面湿滑系数变化,动态调整边权
- 团队登山:引入多人协作约束,如等待时间、绳索长度限制