在二维平面几何中,计算点到直线的距离是一个基础但重要的问题。这个问题在计算机图形学、游戏开发、机器人路径规划等领域都有广泛应用。我们先来理解其数学原理。
第一种方法利用了三角形面积公式。给定直线L由点P1(x1,y1)和P2(x2,y2)确定,点P(a,b)到直线L的距离可以这样计算:
这个方法的几何意义很直观:三角形面积等于底乘以高除以2,因此高(即距离)等于2倍面积除以底边长度。
注意:使用叉积计算面积时要注意绝对值,因为叉积结果可能为负,而距离必须是正数。
第二种方法使用了直线的一般式方程Ax + By + C = 0。给定直线上的两点,我们可以先求出一般式的系数:
然后使用点到直线的距离公式:
distance = |Aa + Bb + C| / √(A² + B²)
这个方法的优势是计算步骤更简洁,且避免了开平方运算(在分母中只需要计算一次)。
首先我们需要合理的数据结构来表示点和直线:
cpp复制struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
};
struct Line {
Point p1, p2;
Line(Point p1 = {}, Point p2 = {}) : p1(p1), p2(p2) {}
};
这样设计的好处是:
基于面积计算的实现:
cpp复制double distanceByArea(Point p, Line l) {
// 计算向量PP1和PP2
double pp1x = l.p1.x - p.x;
double pp1y = l.p1.y - p.y;
double pp2x = l.p2.x - p.x;
double pp2y = l.p2.y - p.y;
// 叉积的绝对值等于平行四边形面积
double area = abs(pp1x * pp2y - pp1y * pp2x);
// 计算底边长度
double base = sqrt(pow(l.p1.x - l.p2.x, 2) + pow(l.p1.y - l.p2.y, 2));
// 距离 = 三角形高度 = 面积 / 底边
return area / base;
}
基于直线一般式的实现:
cpp复制double distanceByFormula(Point p, Line l) {
// 计算一般式系数
double A = l.p2.y - l.p1.y;
double B = l.p1.x - l.p2.x;
double C = l.p2.x * l.p1.y - l.p1.x * l.p2.y;
// 计算分子 |A*x + B*y + C|
double numerator = abs(A * p.x + B * p.y + C);
// 计算分母 sqrt(A^2 + B^2)
double denominator = sqrt(A * A + B * B);
return numerator / denominator;
}
| 特性 | 几何法 | 代数法 |
|---|---|---|
| 计算步骤 | 较多 | 较少 |
| 开方运算 | 2次 | 1次 |
| 直观性 | 更直观 | 稍抽象 |
| 数值稳定性 | 较好 | 更好 |
| 代码复杂度 | 略高 | 简洁 |
在实际应用中,代数法通常是更好的选择,特别是当需要频繁计算时。
当给定的两个点重合时,直线实际上退化为一个点。这时应该返回两点之间的距离:
cpp复制if(l.p1.x == l.p2.x && l.p1.y == l.p2.y) {
return sqrt(pow(p.x - l.p1.x, 2) + pow(p.y - l.p1.y, 2));
}
题目备注指出坐标值可能很大(<2^31),因此:
浮点数比较应该使用epsilon方法:
cpp复制const double EPS = 1e-10;
bool equal(double a, double b) {
return fabs(a - b) < EPS;
}
在距离计算中,当分母接近0时(直线两点非常接近),应该特殊处理。
在代数法中,如果需要对同一直线计算多个点的距离,可以预先计算A、B、C和分母:
cpp复制struct Line {
Point p1, p2;
double A, B, C, denom;
Line(Point p1, Point p2) : p1(p1), p2(p2) {
A = p2.y - p1.y;
B = p1.x - p2.x;
C = p2.x * p1.y - p1.x * p2.y;
denom = sqrt(A * A + B * B);
}
double distanceTo(Point p) {
return abs(A * p.x + B * p.y + C) / denom;
}
};
sqrtl代替sqrt提高精度hypot函数计算两点距离更安全-O2或-O3优化级别inline关键字constexpr在编译期计算常量有时我们需要计算点到线段的最短距离,这需要考虑点的投影是否在线段上:
cpp复制double distanceToSegment(Point p, Line l) {
Point p1 = l.p1, p2 = l.p2;
// 计算线段长度平方
double l2 = pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2);
if (l2 == 0.0) return sqrt(pow(p.x - p1.x, 2) + pow(p.y - p1.y, 2));
// 考虑参数t在[0,1]区间外的投影
double t = ((p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y) * (p2.y - p1.y)) / l2;
t = max(0.0, min(1.0, t));
Point projection(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
return sqrt(pow(p.x - projection.x, 2) + pow(p.y - projection.y, 2));
}
通过计算点到多边形各边的距离,可以判断点是否在多边形内部或附近。
在游戏开发中,计算物体到障碍物的距离是碰撞检测的基础。
编写全面的测试用例非常重要:
cpp复制void testDistance() {
Point p(0, 0);
Line l1(Point(-1, 1), Point(1, 1)); // y=1
assert(abs(distanceByFormula(p, l1) - 1.0) < 1e-6);
Line l2(Point(1, -1), Point(1, 1)); // x=1
assert(abs(distanceByFormula(p, l2) - 1.0) < 1e-6);
Line l3(Point(-1, -1), Point(1, 1)); // y=x
assert(abs(distanceByFormula(p, l3) - 0.0) < 1e-6);
Line l4(Point(1, 0), Point(2, 0)); // y=0, x>=1
assert(abs(distanceByFormula(p, l4) - 1.0) < 1e-6);
}
测试极端情况下的精度:
cpp复制void testPrecision() {
Point p(1e100, 1e100);
Line l(Point(1e100, 1e100+1), Point(1e100+1, 1e100));
double d = distanceByFormula(p, l);
double expected = sqrt(2)/2;
assert(abs(d - expected) < 1e-6);
}
比较两种方法的性能:
cpp复制void benchmark() {
Point p(1.5, 2.5);
Line l(Point(0, 0), Point(100, 200));
auto start = chrono::high_resolution_clock::now();
for(int i = 0; i < 1e6; ++i) {
distanceByArea(p, l);
}
auto end = chrono::high_resolution_clock::now();
cout << "Area method: " << chrono::duration_cast<chrono::milliseconds>(end-start).count() << "ms\n";
start = chrono::high_resolution_clock::now();
for(int i = 0; i < 1e6; ++i) {
distanceByFormula(p, l);
}
end = chrono::high_resolution_clock::now();
cout << "Formula method: " << chrono::duration_cast<chrono::milliseconds>(end-start).count() << "ms\n";
}
在实际项目中实现点到直线距离计算时,代数法通常是首选,因为它更简洁、高效。但理解几何法的原理也很重要,这有助于解决更复杂的几何问题。根据具体应用场景选择合适的方法,并注意处理边界条件和精度问题。