有一天,我的一个朋友给我发了这样的一道题:
题目描述:
每个线段是用平面上的两点来描述,用结构体实现对于任意输入的2个线段,判断是否相交。
提示:两点
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)和
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2)的直线斜率为
k
=
(
y
2
−
y
1
)
/
(
x
2
−
x
1
)
k=(y_2-y_1)/(x_2-x_1)
k=(y2−y1)/(x2−x1)
输入:
3
1 5 2 9
1 3 2 4
5 6 7 8
5 7 7 7
2 5 1 0
9 4 2 9
输出:
disjoint
intersect
disjoint
和好基友讨论了一番,一共找到了三种解决这个问题的方法:
①朋友的方法:构造四边形,判断点是否在四边形内
②我的方法:假定这两条线段是直线,求出两直线交点,再判断交点是否在这两条直线内,在即相交.
③向量叉乘:利用向量叉乘的正负判断线段是否相交(但有前提条件).
我们来分别讨论这三种方法:
①朋友的方法
代码:
#include <iostream>
#include <cstdio>
using namespace std;
//结构体定义
struct point {
double x;
double y;
}p1,p2,p3,p4,pt,pc;
//p1和p2构成直线1;p3和p4构成直线2
int main()
{
int n;
cin >> n;
while (n--) {
cin >> p1.x >> p1.y;
cin >> p2.x >> p2.y;
cin >> p3.x >> p3.y;
cin >> p4.x >> p4.y;
double k1 = (p1.y - p2.y) / (p1.x - p2.x); //p1p2斜率
double k2 = (p3.y - p4.y) / (p3.x - p4.x); //p3p4斜率
pt.x = p4.x - p3.x;
pt.y = p4.y - p3.y;
bool temp1 = false;
bool temp2 = false;
bool temp3 = false;
bool temp4 = false;
bool ans1 = false;
bool ans2 = false;
//第一次判断
if (p3.y <= k1 * (p3.x - p1.x) + p1.y) temp1 = true;
if (p3.y >= k1 * (p3.x - p1.x + pt.x) + p1.y - pt.y) temp2 = true;
if (p3.y <= k2 * (p3.x - p1.x) + p1.y) temp3 = true;
if (p3.y >= k2 * (p3.x - p2.x) + p2.y) temp4 = true;
if (temp1 && temp2 && temp3 && temp4) ans1=true;
//直线点的“互换”
pc = p1;
p1 = p3;
p3 = pc;
pc = p2;
p2 = p4;
p4 = pc;
k1 = (p1.y - p2.y) / (p1.x - p2.x);
k2 = (p3.y - p4.y) / (p3.x - p4.x);
pt.x = p4.x - p3.x;
pt.y = p4.y - p3.y;
temp1 = false;
temp2 = false;
temp3 = false;
temp4 = false;
//第二次判断
if (p3.y <= k1 * (p3.x - p1.x) + p1.y) temp1 = true;
if (p3.y >= k1 * (p3.x - p1.x + pt.x) + p1.y - pt.y) temp2 = true;
if (p3.y <= k2 * (p3.x - p1.x) + p1.y) temp3 = true;
if (p3.y >= k2 * (p3.x - p2.x) + p2.y) temp4 = true;
if (temp1 && temp2 && temp3 && temp4) ans2 = true;
//其中一次若符合就相交
if (ans1 || ans2) cout << "intersect\n";
else cout << "disjoint\n";
}
return 0;
}
//这个代码是我朋友写的
原理:
假设两条线段相交,那么这两条线段可以构造出四边形,
如果两条线段不相交,那么这两条线段只能构造出三角形:
根据画图分析可以得出:假设某点(指 p 3 p_3 p3和 p 4 p_4 p4)存在,若 p 3 p_3 p3在直线 p 1 p 2 p_1p_2 p1p2的上方且 p 3 p_3 p3在虚线 p 1 p 3 p_1p_3 p1p3和 p 2 p 3 p_2p_3 p2p3的下方(第一次判断),或 p 4 p_4 p4在直线 p 1 p 2 p_1p_2 p1p2的下方且 p 4 p_4 p4在虚线 p 1 p 4 p_1p_4 p1p4和 p 2 p 4 p_2p_4 p2p4的上方(第二次判断),那么这两条线段就相交。(因为p3和p4的位置本身就不确定,所以要有两次判断)
我们如何判断点是否在直线上方或下方呢?直线不等式:
由上述关系可以得出:如果点满足下面的方程组
p
3
.
y
−
p
1
.
y
<
=
k
1
(
p
3
.
x
−
p
1
.
x
)
p_3.y-p_1.y<=k_1(p_3.x-p_1.x)
p3.y−p1.y<=k1(p3.x−p1.x)
p
3
.
y
−
(
p
1
.
y
−
p
t
.
y
)
>
=
k
1
[
p
3
.
x
−
(
p
1
.
x
−
p
t
.
x
)
]
p_3.y-(p_1.y-p_t.y)>=k_1[p_3.x-(p_1.x-p_t.x)]
p3.y−(p1.y−pt.y)>=k1[p3.x−(p1.x−pt.x)]
p
3
.
y
−
p
1
.
y
<
=
k
2
(
p
3
.
x
−
p
1
.
x
)
p_3.y-p_1.y<=k_2(p_3.x-p_1.x)
p3.y−p1.y<=k2(p3.x−p1.x)
p
3
.
y
−
p
2
.
y
>
=
k
2
(
p
3
.
x
−
p
2
.
x
)
p_3.y-p_2.y>=k_2(p_3.x-p_2.x)
p3.y−p2.y>=k2(p3.x−p2.x)
对于点
p
4
p_4
p4上述关系若成立也符合
②我的方法:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct point {
double x;
double y;
}p1, p2, p3, p4, pt;
int main()
{
int n;
cin >> n;
while (n--) {
cin >> p1.x >> p1.y;
cin >> p2.x >> p2.y;
cin >> p3.x >> p3.y;
cin >> p4.x >> p4.y;
double k1 = (p1.y - p2.y) / (p1.x - p2.x);
double k2 = (p3.y - p4.y) / (p3.x - p4.x);
if (k1 == k2 && k1 * p1.x - p1.y == k2 * p3.x - p3.y) {
//特殊情况的判断(即使重合但无交点的情况)
if ((max(p1.x, p2.x) < min(p3.x, p4.x))||(max(p3.x,p4.x)<min(p1.x,p2.x))) {
cout << "disjoint\n";
continue;
}
else {
cout << "intersect\n";
continue;
}
}
else if (k1 == k2 && k1 * p1.x - p1.y != k2 * p3.x - p3.y) {
cout << "disjoint\n";
continue;
}
else {
pt.x = (k1 * p1.x - k2 * p3.x - p1.y + p3.y) / (k1 - k2);
pt.y = k1 * (pt.x - p1.x) + p1.y;
bool temp1 = false;
bool temp2 = false;
if (min(p1.x, p2.x) <= pt.x && pt.x <= max(p1.x, p2.x)) {
temp1 = true;
}
if (min(p3.x, p4.x) <= pt.x && pt.x <= max(p3.x, p4.x)) {
temp2 = true;
}
if (temp1 && temp2) {
cout << "intersect\n";
}
else cout << "disjoint\n";
}
}
return 0;
}
//我的思路,它写的代码
原理:
假设两条线段不相交,那么分别延长两条线段,直到相交,如图:
我们要判断的就是:这个交点是否在这两条线段内
所以第一步就是要求出交点坐标?
不,我们先判断两条线段的斜率是否相等?如果相等,计算两条线段的距离,如果等于0就相交,否则不相交。
如果斜率不相等,那么计算交点坐标,并判断。
具体如下:
①
k
1
=
=
k
2
k_1==k_2
k1==k2的情况
构造两线段方程:
y
−
p
1
.
y
=
k
1
(
x
−
p
1
.
x
)
(
∗
)
y-p_1.y=k_1(x-p_1.x)(*)
y−p1.y=k1(x−p1.x)(∗)
y
−
p
3
.
y
=
k
2
(
x
−
p
3
.
x
)
(
∗
∗
)
y-p_3.y=k_2(x-p_3.x)(**)
y−p3.y=k2(x−p3.x)(∗∗)
联立(*)(**),且
k
1
=
k
2
k_1=k_2
k1=k2,可以得到直线距离
d
=
(
k
2
p
3
.
x
−
k
1
p
1
.
x
+
p
1
.
y
−
p
3
.
y
)
/
(
关
于
k
1
和
k
2
的
方
程
)
d=(k_2p_3.x-k_1p_1.x+p_1.y-p_3.y)/(关于k_1和k_2的方程)
d=(k2p3.x−k1p1.x+p1.y−p3.y)/(关于k1和k2的方程)
令
d
=
0
d=0
d=0,就得到
k
2
p
3
.
x
−
p
3
.
y
=
k
1
p
1
.
x
−
p
1
.
y
k_2p_3.x-p_3.y=k_1p_1.x-p_1.y
k2p3.x−p3.y=k1p1.x−p1.y
如果这个等式成立,那么这两条线段就重合(也就是相交了)
否则就不相交
②
k
1
!
=
k
2
k_1!=k_2
k1!=k2的情况
联立(*)(**),求出交点坐标:
p
t
.
x
=
[
k
1
p
1
.
x
−
k
2
p
3
.
x
−
(
p
1
.
y
−
p
3
.
y
)
]
/
(
k
1
−
k
2
)
p_t.x=[k_1p_1.x-k_2p_3.x-(p_1.y-p_3.y)]/(k_1-k_2)
pt.x=[k1p1.x−k2p3.x−(p1.y−p3.y)]/(k1−k2)
p
t
.
y
=
k
1
(
p
t
.
x
−
p
1
.
x
)
+
p
1
.
y
p_t.y=k_1(p_t.x-p_1.x)+p_1.y
pt.y=k1(pt.x−p1.x)+p1.y
由图可以看出,若使两线段相交,只要交点x坐标大于等于min(p1.x,p2.x)且小于等于max(p1.x,p2.x)
同样,对于线段p3p4同样符合
这两种都符合线段就相交
③向量叉乘法:
#include <iostream>
#include <algorithm>
using namespace std;
//点类
class point
{
public:
int x;
int y;
};
//根据向量叉乘判断是否相交(跨立实验)
bool judgement(point p1, point p2, point p3, point p4)
{
point vec_ca;
point vec_cb;
point vec_cd;
vec_ca.x = p1.x - p3.x;
vec_ca.y = p1.y - p3.y;
vec_cb.x = p1.x - p4.x;
vec_cb.y = p1.y - p4.y;
vec_cd.x = p1.x - p2.x;
vec_cd.y = p1.y - p2.y;
int q = (vec_ca.x * vec_cd.y - vec_ca.y * vec_cd.x) * (vec_cb.x * vec_cd.y - vec_cd.x * vec_cb.y);
if (q >= 0)
return false;
else
{
return true;
}
}
//向量叉乘的前提条件判断(快速排斥实验)
bool projJudge(point p1, point p2, point p3, point p4)
{
if (max(p1.x, p2.x) <= min(p3.x, p4.x)||max(p3.x,p4.x)<=min(p1.x,p2.x)||
max(p3.y,p4.y)<=min(p1.y,p2.y)||max(p1.y,p2.y)<=min(p3.y,p4.y))
return true;
else
{
return false;
}
}
int main()
{
int n;
point p1, p2, p3, p4;
cin >> n;
while (n--)
{
cin >> p1.x >> p1.y >> p2.x >> p2.y;
cin >> p3.x >> p3.y >> p4.x >> p4.y;
if (!projJudge(p1, p2, p3, p4))
{
if (judgement(p1, p2, p3, p4))
cout << "intersect";
}
else
{
cout << "disjoint";
}
cout << endl;
}
return 0;
}
//我写的代码
原理:
①先进行快速排斥实验
如果这两条线段构成的矩形不能相交,明显就不会相交,用方程表示:
m
a
x
(
p
1.
x
,
p
2.
x
)
<
=
m
i
n
(
p
3.
x
,
p
4.
x
)
∣
∣
m
a
x
(
p
3.
x
,
p
4.
x
)
<
=
m
i
n
(
p
1.
x
,
p
2.
x
)
∣
∣
m
a
x
(
p
3.
y
,
p
4.
y
)
<
=
m
i
n
(
p
1.
y
,
p
2.
y
)
∣
∣
m
a
x
(
p
1.
y
,
p
2.
y
)
<
=
m
i
n
(
p
3.
y
,
p
4.
y
)
max(p1.x, p2.x) <= min(p3.x, p4.x) || max(p3.x,p4.x)<=min(p1.x,p2.x) || max(p3.y,p4.y)<=min(p1.y,p2.y) || max(p1.y,p2.y)<=min(p3.y,p4.y)
max(p1.x,p2.x)<=min(p3.x,p4.x)∣∣max(p3.x,p4.x)<=min(p1.x,p2.x)∣∣max(p3.y,p4.y)<=min(p1.y,p2.y)∣∣max(p1.y,p2.y)<=min(p3.y,p4.y)
②如果通过快速排斥实验,那么进行跨立实验
具体可看:https://segmentfault.com/a/1190000004070478