给定两条线段(表示为起点start = {X1, Y1}和终点end = {X2, Y2}),如果它们有交点,请计算其交点,没有交点则返回空值。
要求浮点型误差不超过10^-6。若有多个交点(线段重叠)则返回 X 值最小的点,X 坐标相同则返回 Y 值最小的点。
示例:输入:line1 = {0, 0}, {1, 0} line2 = {1, 1}, {0, -1}
输出: {0.5, 0}
首先,确定思路:
学过初中数学的人都会认为:啊,简单!但很快你就会发现并不是那么回事,需要考虑的情况很多。
从数学角度,我们可以比较轻松的得到上述的流程图,但是从代码实现的角度我们必须要考虑到几个重要的问题:
数学理论知识告诉我们,一条直线可以表示为:
y = k x + b 或 a x + b y + c = 0 y = kx + b \quad 或 \quad ax + by + c = 0 y=kx+b或ax+by+c=0
但是,明显我们不知道方程中的参数值,我们已知的信息只有直线上两个点的坐标。幸运的是,数学老师教过 两点确定一条直线,并且考虑到直线有垂直于x轴和垂直于y轴两种特殊情况,用 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1), (x_2,y_2) (x1,y1),(x2,y2) 表示一条直线上的两点,则直线可以表示为: x = x 1 + t ( x 2 − x 1 ) , y = y 1 + t ( y 2 − y 1 ) x = x_1 + t(x_2-x_1), y=y_1+t(y2-y1) x=x1+t(x2−x1),y=y1+t(y2−y1)
仅考虑两直线相交的情况,用 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1), (x_2,y_2) (x1,y1),(x2,y2) 表示第一条直线上的两点, ( x 3 , y 3 ) , ( x 4 , y 4 ) (x_3,y_3) ,(x_4,y_4) (x3,y3),(x4,y4) 表示第二条直线上的两点。则有:
x = x 1 + t 1 ( x 2 − x 1 ) , y = y 1 + t 1 ( y 2 − y 1 ) x = x_1 + t_1(x_2-x_1), y=y_1+t_1(y2-y1) x=x1+t1(x2−x1),y=y1+t1(y2−y1)
x = x 3 + t 2 ( x 4 − x 3 ) , y = y 3 + t 2 ( y 4 − y 3 ) x = x_3 + t_2(x_4-x_3) ,y=y_3+t_2(y4-y3) x=x3+t2(x4−x3),y=y3+t2(y4−y3)
考虑到不平行直线有且只有唯一交点,因此联立方程组必能得到 t 1 , t 2 t_1,t_2 t1,t2 的唯一解
t 1 = ( x 3 − x 1 ) ( y 4 − y 3 ) − ( y 3 − y 1 ) ( x 4 − x 3 ) ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) t_1=\frac{(x_3-x_1)(y_4-y_3)-(y_3-y_1)(x_4-x_3)}{(x_2-x_1)(y_4-y_3)-(x_4-x_3)(y_2-y_1)} t1=(x2−x1)(y4−y3)−(x4−x3)(y2−y1)(x3−x1)(y4−y3)−(y3−y1)(x4−x3)
t 2 = ( x 1 − x 3 ) ( y 2 − y 1 ) − ( y 1 − y 3 ) ( x 2 − x 1 ) ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) t_2=\frac{(x_1-x_3)(y_2-y_1)-(y_1-y_3)(x_2-x_1)}{(x_2-x_1)(y_4-y_3)-(x_4-x_3)(y_2-y_1)} t2=(x2−x1)(y4−y3)−(x4−x3)(y2−y1)(x1−x3)(y2−y1)−(y1−y3)(x2−x1)
如图所示为点在线段上的三种情况,可以明确,当 t ∈ [ 0 , 1 ] t\in[0,1] t∈[0,1] ,点 ( x , y ) (x,y) (x,y) 在线段 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) —— ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 上
AC代码如下:
class Solution {
public:
vector<double> intersection(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2) {
x1 = start1[0], y1 = start1[1], x2 = end1[0], y2 = end1[1];
x3 = start2[0], y3 = start2[1], x4 = end2[0], y4 = end2[1];
if(isparallal()){
if(ispointonline(x1, y1, x3, y3, x4, y4)) update(x1, y1);
if(ispointonline(x2, y2, x3, y3, x4, y4)) update(x2, y2);
if(ispointonline(x3, y3, x1, y1, x2, y2)) update(x3, y3);
if(ispointonline(x4, y4, x1, y1, x2, y2)) update(x4, y4);
}
else {
double t1 = (double)((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3))
/ ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1));
double t2 = (double)((x1 - x3) * (y2 - y1) - (y1 - y3) * (x2 - x1))
/ ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3));
if (t1 >= 0.0 && t1 <= 1.0 && t2 >= 0.0 && t2 <= 1.0) {
res = {x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)};
}
}
return res;
}
private:
int x1,y1,x2,y2,x3,y3,x4,y4;
vector<double> res;
/* 判断是否平行 */
bool isparallal(){
return (x2 - x1) * (y4 - y3) == (y2 - y1) * (x4 - x3);
}
/* 在平行基础上,判断点(x,y)是否在线段(sx,sy)——(ex,ey)上 */
bool ispointonline(int x, int y, int sx, int sy, int ex, int ey){
if(sx == ex && x == ex) return isbetween(y, sy, ey); // 垂直于x轴
if(sy == ey && y == ey) return isbetween(x, sx, ex); // 垂直于y轴
double t1 = (double)(x - sx) / (ex - sx);
double t2 = (double)(y - sy) / (ey - sy);
return t1 == t2 && t1 >= 0.0 && t1 <= 1.0;
}
/* 判断x是否在范围内 */
bool isbetween(int x, int x1, int x2){
if(x1 > x2) swap(x1, x2);
return x >= x1 && x <= x2;
}
/* 重合时,更新最小(x,y) */
void update(int x, int y){
if (res.empty()){
res.push_back((double)x);
res.push_back((double)y);
}
else if ((double)x < res[0] || ((double)x == res[0] && (double)y < res[1])){
res[0] = (double)x;
res[1] = (double)y;
}
}
};
转换一下思路,用向量的思路进行思考
向量内积(点乘)为0,表示向量垂直;向量外积(叉乘)为0,表示向量平行。用 a ⃗ = ( x 1 , y 1 ) \vec{a}=(x_1, y_1) a=(x1,y1), b ⃗ = ( x 2 , y 2 ) \vec{b}=(x_2, y_2) b=(x2,y2) 表示两个向量,则有:
a ⃗ ⊥ b ⃗ ⇔ a ⃗ ⋅ b ⃗ = x 1 x 2 + y 1 y 2 = 0 \vec{a}\perp \vec{b} \Leftrightarrow \vec{a} \cdot \vec{b}=x_1x_2+y_1y_2=0 a⊥b⇔a⋅b=x1x2+y1y2=0
a ⃗ ∥ b ⃗ ⇔ a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 = 0 \vec{a}\parallel \vec{b} \Leftrightarrow \vec{a} \times \vec{b}=x_1y_2-x_2y_1=0 a∥b⇔a×b=x1y2−x2y1=0
如图所示,当且仅当P、Q在直线MN两侧,且M、N在直线PQ两侧时,线段PQ与MN相交。
那么我们的问题就转变为如何借助向量,判断两点是否在直线两侧。考虑下图:
向量的外积具有一定的几何意义,当 a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 > 0 \vec{a} \times \vec{b}=x_1y_2-x_2y_1>0 a×b=x1y2−x2y1>0 则 b ⃗ \vec{b} b 在 a ⃗ \vec{a} a 逆时针方向;当 a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 < 0 \vec{a} \times \vec{b}=x_1y_2-x_2y_1<0 a×b=x1y2−x2y1<0 则 b ⃗ \vec{b} b 在 a ⃗ \vec{a} a 顺时针方向。因此可得,
M 、 N 在 P Q 两 侧 ⇔ ( P M ⃗ × P Q ⃗ ) ( P N ⃗ × P Q ⃗ ) ≤ 0 M、N在PQ两侧 \Leftrightarrow (\vec{PM} \times \vec{PQ}) (\vec{PN} \times \vec{PQ})\leq0 M、N在PQ两侧⇔(PM×PQ)(PN×PQ)≤0
则有
M N 与 P Q 相 交 ⇔ ( P M ⃗ × P Q ⃗ ) ( P N ⃗ × P Q ⃗ ) ≤ 0 且 ( M P ⃗ × M N ⃗ ) ( M Q ⃗ × M N ⃗ ) ≤ 0 MN与PQ相交 \Leftrightarrow (\vec{PM} \times \vec{PQ}) (\vec{PN} \times \vec{PQ})\leq 0 且 (\vec{MP} \times \vec{MN}) (\vec{MQ} \times \vec{MN})\leq 0 MN与PQ相交⇔(PM×PQ)(PN×PQ)≤0且(MP×MN)(MQ×MN)≤0
向量的外积,除了可表示向量的方向之外,还可用于计算三角形的面积。
∵ ∣ A B ⃗ × A C ⃗ ∣ = ∣ A B ⃗ ∣ ⋅ ∣ A C ⃗ ∣ ⋅ s i n ( ∠ B A C ) \because |\vec{AB} \times \vec{AC}| = |\vec{AB}| \cdot |\vec{AC}| \cdot sin(\angle{BAC}) ∵∣AB×AC∣=∣AB∣⋅∣AC∣⋅sin(∠BAC)
∴ S ( △ A B C ) = 1 2 ⋅ ∣ A B ⃗ ∣ ⋅ ∣ A C ⃗ ∣ ⋅ s i n ( ∠ B A C ) = 1 2 ⋅ ∣ A B ⃗ × A C ⃗ ∣ \therefore S(△ABC)=\frac{1}{2} \cdot |\vec{AB}| \cdot |\vec{AC}| \cdot sin(\angle{BAC})=\frac{1}{2} \cdot |\vec{AB} \times \vec{AC}| ∴S(△ABC)=21⋅∣AB∣⋅∣AC∣⋅sin(∠BAC)=21⋅∣AB×AC∣
交点在直线中的位置,也可转换为面积的位置,最终通过计算叉积得到,若 M ( x 1 , y 1 ) M(x_1, y_1) M(x1,y1), N ( x 2 , y 2 ) N(x_2,y_2) N(x2,y2) ,则可计算出点 O ( x , y ) O(x,y) O(x,y) 的坐标
AC代码如下:
class Solution {
public:
vector<double> intersection(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2) {
pmx = start2[0] - start1[0], pmy = start2[1] - start1[1];
pnx = end2[0] - start1[0], pny = end2[1] - start1[1];
pqx = end1[0] - start1[0], pqy = end1[1] - start1[1];
mpx = -pmx, mpy = -pmy;
mqx = end1[0] - start2[0], mqy = end1[1] - start2[1];
mnx = end2[0] - start2[0], mny = end2[1] - start2[1];
npx = -pnx, npy = -pny;
nqx = end1[0] - end2[0], nqy = end1[1] - end2[1];
if(cross(pqx, pqy, mnx, mny) == 0){
if(ispointonline(start1, start2, end2)) update(start1);
if(ispointonline(end1, start2, end2)) update(end1);
if(ispointonline(start2, start1, end1)) update(start2);
if(ispointonline(end2, start1, end1)) update(end2);
}
else if(isintersect()){
double lambda = fabs((double)cross(mpx, mpy, mqx, mqy) / cross(npx, npy, nqx, nqy));
double k = lambda / (lambda + 1);
res.push_back(start2[0] + k * (end2[0] - start2[0]));
res.push_back(start2[1] + k * (end2[1] - start2[1]));
}
return res;
}
private:
int pmx, pmy, pnx, pny, pqx, pqy, mpx, mpy, mqx, mqy, mnx, mny, npx, npy, nqx, nqy;
vector<double> res;
/* 计算向量乘积 */
int cross(int x1, int y1, int x2, int y2){
return x1 * y2 - x2 * y1;
}
/* 判断两线段是否相交 */
bool isintersect(){
return cross(pmx, pmy, pqx, pqy) * cross(pnx, pny, pqx, pqy) <= 0
&& cross(mpx, mpy, mnx, mny) * cross(mqx, mqy, mnx, mny) <= 0;
}
/* 在平行基础上,判断点(x,y)是否在线段(sx,sy)——(ex,ey)上 */
bool ispointonline(vector<int>& point, vector<int>& start, vector<int>& end){
int x = point[0], y = point[1];
int sx = start[0], sy = start[1], ex = end[0], ey = end[1];
if(sx == ex && x == ex) return isbetween(y, sy, ey); // 垂直于x轴
if(sy == ey && y == ey) return isbetween(x, sx, ex); // 垂直于y轴
double t1 = (double)(x - sx) / (ex - sx);
double t2 = (double)(y - sy) / (ey - sy);
return t1 == t2 && t1 >= 0.0 && t1 <= 1.0;
}
/* 判断x是否在范围内 */
bool isbetween(int x, int x1, int x2){
if(x1 > x2) swap(x1, x2);
return x >= x1 && x <= x2;
}
/* 重合时,更新最小(x,y) */
void update(vector<int>& point){
int x = point[0], y = point[1];
if (res.empty()){
res.push_back((double)x);
res.push_back((double)y);
}
else if ((double)x < res[0] || ((double)x == res[0] && (double)y < res[1])){
res[0] = (double)x;
res[1] = (double)y;
}
}
};