点到线段最短距离的运算与点到直线的最短距离的运算二者之间存在一定的差别,即求点到线段最短距离时需要考虑参考点在沿线段方向的投影点是否在线段上,若在线段上才可采用点到直线距离公式,如图1所示。
图1 (a)最短距离为点P与其在线段AB上投影C之间的线段PC
具体算法主要有以下三种:
1、方法——经典算法
该算法直接用高中时所学习到的解析几何知识对点到线段的距离进行求解。其基本思想是先判断点在线段端点、点在线上等等的特殊情况,逐步的由特殊到一般,当忽略点在线段上的特殊情况时,判断点到线段方向的垂线是否落在线段上的方法是通过比较横纵坐标的方式来判断,最后把不同的判断情况用不同的几何方式来进行处理计算得出结果。
由上面叙述的基本思路可以知道这种算法虽然很容易理解和接受,但从算法的实用性的角度分析还是有很大的缺点的,首先是算法复杂,计算量巨大,大量的比较判断、距离计算、角度计算等等,实际应用中往往是需要求由大量线段组成的折线到某点的最短距离,如此用这样的算法计算量是不能想象的。其次经典算法中使用的一些简化运算的函数不利于语言的重新包装,如果想换编程语言的话,就比较麻烦了。
2、方法二——面积算法
该方法主要是先判断投影点是否在线段上,投影点在线段延长线上时,最短距离长度为点到端点的线段长度;当投影点在线段上时,先使用海伦公式计算三角形面积,再计算出三角形的高,即为最短距离。
运用面积算法求解点到线段最短距离思路很清晰,也很容易理解。从效率方面考虑,比如需要多次计算平方、根号,这对于大量数据进行运算是负担很重的。求面积就必须把三条边长全部求出,并且用到的海伦公式也需要进行开方运算,计算过程显得繁琐。
3、方法三——矢量算法
矢量算法过程清晰,如果具有一定的空间几何基础,则是解决此类问题时应优先考虑的方法。当需要计算的数据量很大时,这种方式优势明显。
由于矢量具有方向性,故一些方向的判断直接根据其正负号就可以得知,使得其中的一些问题得以很简单的解决。
用此方法考虑,我们只需要找到向量 在方向上的投影,具体如下:
上面的 是方向上的单位向量,其意义是给所求向量确定方向。是的两个向量的内积,且
那么即为上图中线段AC的长度值,不带有方向性。此数值与上述表征方向的 整体构成有大小、有方向的新向量,即为 在方向上的投影向量,C为投影点。
根据得到的,由向量的方向性可知:如果情况是上图(a)所示,那么0<r<1;如果是如图(b)所示的情况,那么r
特殊情况如点在线段上、点在端点、点在线段延长线上等等的情况全部适用于此公式,只是作为特殊情况出现,无需另作讨论。这也是矢量算法思想的优势所在。
故根据r值的不同,最短距离
附上代码
distance(double x,double y,double x1,double y1,double x2,double y2)
{
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0)
return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2)
return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return sqrt((x - px) * (x - px) + (y - py) * (y - py));
}
2127: tmk射气球
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 715 Solved: 130
Submit Status Web Board
Description
有一天TMK在做一个飞艇环游世界,突然他发现有一个气球匀速沿直线飘过,tmk想起了他飞艇上有一把弓,他打算拿弓去射气球,为了提高射击的准确性,他首先在飞艇上找到一个离气球最近的一个点,然后射击(即使气球在飞船的正上方),现在求某些时刻飞艇上的点和气球的距离最小是多少(这个最小距离我们简称为飞艇到气球的距离)。
Input
第一行一个整数T(T<=20),表示有T组测试数据
每组测试数据,有两行。
第一行有5个整数,h,x1,y1,x2,y2,其中h表示飞船的高度,飞船可抽象为一个线段,(x1,y1)(x2,y2)分别是这个线段的端点(有可能会有(x1,y1)(x2,y2)重合的情况)
第二行有6个整数,x,y,z,X,Y,Z分别表示气球的在第0秒的时候的横坐标,纵坐标,高度,一秒时间气球横坐标的变化量,一秒时间气球纵坐标的变化量,一秒时间气球高度的变化量(如果现在气球在(x0,y0,z0)下一秒坐标就为(x0+X,y0+Y,z0+Z))
第三行1个整数n,表示询问组数
接下来的n行,每行一个整数,表示询问的秒数t
题目涉及的整数除了T以外,范围均为[0,1000]
Output
每组询问输出n行,每行输出一个数,表示在t秒的时候飞艇与气球的距离最小是多少,保留两位小数
Sample Input
Sample Output
#include<stdio.h>
#include<math.h>
double distance(double x,double y,double x1,double y1,double x2,double y2)
{
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0)
return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2)
return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return sqrt((x - px) * (x - px) + (y - py) * (y - py));
}
int main()
{
int t,n;
double h1,x1,x2,y1,y2;
double x,y,z,x0,y0,z0,t0;
double a1,b1,c1,d,h;
scanf("%d",&t);
while(t--)
{
scanf("%lf%lf%lf%lf%lf",&h1,&x1,&y1,&x2,&y2);
scanf("%lf%lf%lf%lf%lf%lf",&x,&y,&z,&x0,&y0,&z0);
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%lf",&t0);
a1=x+x0*t0; b1=y+y0*t0; c1=z+z0*t0;
if((x1==x2)&&(y1==y2))
{
d=sqrt((pow(x1-a1,2)+pow(y1-b1,2)+pow(h1-c1,2)));
printf("%.2lf\n",d);continue;
}
h=fabs(h1-c1);
// d1=sqrt((y2-y1)*(y2-y1)+(x1-x2)*(x1-x2));
// d2=fabs((y2-y1)*a1 + (x1-x2)*b1 - x1*y2 + x2*y1);
// d=d2/d1; 点到直线的距离 非线段 (测试样例就会出错(三点在一直线上))
d=distance(a1,b1,x1,y1,x2,y2);//点到线段的距离(非直线)
/*(0,0)到(1,1)(2,2)组成的线段距离为 根号2。注:三点在一条直线上 */
printf("%.2lf\n",sqrt(d*d+h*h));
}
}
return 0;
}