这是个挺有意思的问题,没想到能有这么实际的应用。这个答案是用 Java 写的,因为我最熟悉它,但是算法很简单,代码中的 cmets 应该可以帮助你用任何语言重新实现。
我已将您的数据网格扩展为多行,并添加了E(结束位置)。所以完整的网格看起来像:
S 1 2 3
4 5 6 7
8 x 9 10
11 x 13 14
12 x E 15
只说几句:
- 每个单元格的维度是 1 乘 1。这意味着每个单元格的对角线等于 sqrt(2),因为 d^2 = 1^2 + 1^2
- 每个单元格称为
Position(value, row, column)。 Row 和 Column 值就像坐标,Value 只是单元格中的一个值。例如,我们的开始是:new Position("S", 0, 0),结束是new Position("E", 4, 2)
- 提出的算法是在一些“最短路径算法”之后提出的,例如提到的 A* 或 Dijkstra
- 所提出的算法只看最短路径的角度,如果在计算中应该考虑最短路径上的值,那么我可以用回溯机制替换它。
算法设置:
// shortest path is stored in "positions" list
Position _S = new Position("S", 0, 0);
Position _5 = new Position("5", 1, 1);
...
Position _E = new Position("E", 4, 2);
List<Position> positions = new ArrayList<>();
Collections.addAll(positions, _S, _5, _9, _13, _E);
// obstruction points are stored in "xxx" list
Position x1 = new Position("x", 2, 1);
Position x2 = new Position("x", 3, 1);
Position x3 = new Position("x", 4, 1);
List<Position> xxx = new ArrayList<>();
Collections.addAll(xxx, x1, x2, x3);
现在算法:
// iterate from the end
int index = positions.size()-1;
while (index > 1) {
// get three current positions
Position c = positions.get(index);
Position b = positions.get(index-1);
Position a = positions.get(index-2);
// calculate angle on point "b". Angle is constructed with a-b-c
int angle = angle(a,b,c);
if (angle == 0) { // angle = 0 means that line is straight
positions.remove(index-1); // then just remove the middle one (b)
} else { // in ELSE part check if line goes through any "x" cell
Line line = new Line(a,c);
boolean middleCanBeRejected = true;
for (Position x : xxx) {
if (line.distanceTo(x) < HALF_DIAGONAL) { // if distance from "x" to "line" is less than sqrt(2),
// it means that straight line will pass over "x",
// so we are not able to discard the middle point
middleCanBeRejected = false;
break;
}
}
if (middleCanBeRejected) { // still in ELSE
positions.remove(index-1); // check result of FOR above
}
}
index--;
}
最后是测试用例:
S 1 2 3
4 5 6 7
8 x 9 10
11 x 13 14
12 x E 15
Path:
S 0.0, 0.0
9 2.0, 2.0
E 4.0, 2.0
S 1 2 3
4 5 6 7
8 22 9 10
11 23 13 14
12 x E 15
Path:
S 0.0, 0.0
E 4.0, 2.0
附录,实用方法和类:
// -1 -> turn right
// 0 -> straight
// +1 -> turn left
// Point2D.ccw() algorithm from Sedgewick's book
public static int angle(Position a, Position b, Position c) {
double area = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
if (area < 0.0D) { return -1; }
else { return area > 0.0D ? 1 : 0; }
}
public class Line {
// A*x + B*y + C = 0
double A;
double B;
double C;
public Line(Position p1, Position p2) {
// https://math.stackexchange.com/questions/422602/convert-two-points-to-line-eq-ax-by-c-0
this.A = p1.y - p2.y;
this.B = p2.x - p1.x;
this.C = p1.x*p2.y - p2.x*p1.y;
}
public double distanceTo(Position p) {
double nom = Math.abs(A*p.x + B*p.y + C);
double den = Math.sqrt(A*A + B*B);
return nom/den;
}
}
public class Position {
String value;
double x; // row
double y; // column
public Position(String value, double x, double y) {
this.value = value;
this.x = x;
this.y = y;
}
}