일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Recommender System
- 논문리뷰
- SGNS
- ALS
- Collaborative Filtering
- Item2Vec
- Neural Collaborative Filtering
- CF
- Tacotron2
- 부스트캠프 AI Tech
- BOJ
- wavenet
- FastSpeech
- 백준
- Skip-gram
- RecSys
- matrix factorization
- TTS
- ANNOY
- Tacotron
- FastSpeech2
- Implicit feedback
- Dilated convolution
- NEG
- word2vec
- CV
- 추천시스템
- Ai
- Negative Sampling
- Noise Contrastive Estimation
- Today
- Total
devmoon
[C++] CCW / 두 선분의 교차 판단 본문
선분 교차 판단 알고리즘은 2차원 평면 위에서 두 선분이 주어졌을 때, 두 선분이 서로 교차하는지 교차하지 않는지 판단하는 알고리즘이다. 이 알고리즘을 학습하기 위해서 선행되어야 하는 기하 알고리즘이 있는데, 그 개념은 CCW이다. CCW (Counter Clock Wise)는 2차원 평면 위에 놓인 3개의 점에 대해서 어떤 방향성이 있는지 알려주며 이 방향성 정보를 사용해 선분이 서로 교차하는지 판단하게 된다.
CCW
먼저 3개의 점으로 나타낼 수 있는 방향성의 종류에는 무엇이 있는지 알아봐야 한다. 3개의 점을 임의의 순서대로 이어서 나온 방향성은 아래와 같이 1) 시계 방향 2) 반시계 방향 3) 일직선 3종류가 있을 수 있다.
그러면 이제 3개의 각 점에 대한 좌표정보가 주어지면 그 방향성을 어떻게 구하는지 그 계산 과정에 대해서 알아봐야 하는데, 외적에 대한 이해가 필요하다. 방향성을 판단하는데 외적을 사용하는 이유는 아래의 블로그에 나와있으며 중고등학교 과정에서 한 번쯤 사용해보았을 사선 공식을 사용하게 된다.
https://wogud6792.tistory.com/11
이제부터 2차원 평면상에 3개의 점을 $p_1, p_2, p_3$ 라고 가정하고, 각각의 좌표는 $x_i, y_i$ 라고 해보면, 외적의 결과를 다음과 같이 계산할 수 있다.
$$ S = (x_{1}y_{2} + x_{2}y_{3} + x_{3}y_{1}) - (x_{2}y_{1} + x_{3}y_{2} + x_{1}y_{3}) $$
$S$ 의 값에 대해 나올 수 있는 부호에 따라 다시 3종류로 분류해볼 수 있다. 그리고 외적의 개념에 의해 그 방향성을 아래와 같이 판단할 수 있다.
- $S < 0$ : 시계방향
- $S > 0$ : 반시계 방향
- $S = 0$ : 일직선(평행)
int ccw(pair<int, int>p1, pair<int, int>p2, pair<int, int>p3) {
int s = p1.first * p2.second + p2.first * p3.second + p3.first * p1.second;
s -= (p1.second * p2.first + p2.second * p3.first + p3.second * p1.first);
if (s > 0) return 1;
else if (s == 0) return 0;
else return -1;
}
두 선분의 교차 판단 알고리즘
이제 3점이 주어졌을 때, 그 방향성을 파악할 수 있게 되었으니 두 선분의 교차 판단을 할 준비가 되었다. 먼저 두 선분이 교차하는 상황부터 예를 들어 확인해보자.
$p_1, p_2, p_3$와 $p_1, p_2, p_4$ 에 대해서 각각 CCW알고리즘으로 계산한다면 한쪽은 1(반시계방향), 다른 한쪽은 -1(시계방향)의 결과 값을 얻을 수 있다. 비슷한 예시로, $p_1, p_2, p_3$가 일직선상에 존재한다면 한쪽은 0, 다른 한쪽은 1 또는 -1의 값을 얻을 수 있다. 따라서, 위의 그래프를 가지고 판단할 수 있는 정보는 아래 수식과 동일하다.
$$ \text{CCW}(p_1, p_2, p_3) \times \text{CCW}(p_1, p_2, p_4) \leq 0 $$
하지만, 위의 허술한 예시와 수식에는 반례가 존재한다. 그 반례는 아래와 같이 두 선분 $l_{1}$과 $l_{2}$가 교차하지 않음에도 위의 조건에서는 교차한다고 판단을 하게 되는 것이다.
물론, 위의 $l_1$이 기준이 되는 수식$\text{CCW}(p_1, p_2, p_3) \times \text{CCW}(p_1, p_2, p_4) \leq 0 $)에 대해서는 만족을 하지만, $l_2$가 기준이 되는 수식에서는 둘 다 반시계 방향 또는 둘 다 시계방향으로 볼 수 있다. 이렇기 때문에 $l_2$ 에 대해서도 CCW를 진행해야 한다. 결과적으로 $l_1$을 기준으로 하는 CCW의 결과 값과 $l_2$를 기준으로 하는 CCW의 결과 값 모두가 0 이하일 때, 교차한다고 판단할 수 있다.
$$(\text{CCW}(p_1, p_2, p_3) \times \text{CCW}(p_1, p_2, p_4) \leq 0) \;\&\&\; (\text{CCW}(p_3, p_4, p_1) \times
\text{CCW}(p_3, p_4, p_2) \leq 0)$$
하지만 안타깝게도, 이렇게 새롭게 조건이 추가되었음에도 불구하고 반례는 여전히 1개 존재한다. 반례는 두 선분이 교차하지 않으면서 일직선 관계에 놓여있는 경우로 모든 CCW의 값이 0이 되는 예시이다.
모든 선분이 일직선 상에 존재할 때 즉, 서로의 CCW의 값이 모두 0인 경우에는 따로 분리를 해야 한다. 그렇다면 일직선 상에 있음에도 두 선분이 교차하는 경우는 서로 포개져 있는 경우 뿐이다. 해당 경우를 제외하고 두 선분은 모두 분리되어 있다.
지금까지의 모든 조건들을 합쳐서 교차 여부를 판단하는 함수를 C++로 작성해보면 아래와 같다.
#define pii pair<int, int>
bool isIntersect(pair<pii, pii> l1, pair<pii, pii> l2) {
pii p1 = l1.first;
pii p2 = l1.second;
pii p3 = l2.first;
pii p4 = l2.second;
int p1p2 = ccw(p1, p2, p3) * ccw(p1, p2, p4); // l1 기준
int p3p4 = ccw(p3, p4, p1) * ccw(p3, p4, p2); // l2 기준
// 두 직선이 일직선 상에 존재
if (p1p2 == 0 && p3p4 == 0) {
// 비교를 일반화하기 위한 점 위치 변경
if (p1 > p2) swap(p2, p1);
if (p3 > p4) swap(p3, p4);
return p3 <= p2 && p1 <= p4; // 두 선분이 포개어져 있는지 확인
}
return p1p2 <= 0 && p3p4 <= 0;
}
'알고리즘 > Concept' 카테고리의 다른 글
Heavy Light Decomposition (0) | 2022.12.14 |
---|---|
KMP(Knuth-Morris-Pratt) / 문자열 매칭 (2) | 2021.10.14 |
Meet in the middle / 중간에서 만나기 (0) | 2021.10.11 |