From e3d9200acd31742e725280a3e8b53e20ccd7c906 Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 14:25:19 -0300 Subject: [PATCH 1/7] Add original Graham's scan, rename to convex-hull --- ...ams-scan-convex-hull.md => convex-hull.md} | 85 ++++++++++++++++--- src/geometry/halfplane-intersection.md | 2 +- src/index.md | 2 +- 3 files changed, 76 insertions(+), 13 deletions(-) rename src/geometry/{grahams-scan-convex-hull.md => convex-hull.md} (63%) diff --git a/src/geometry/grahams-scan-convex-hull.md b/src/geometry/convex-hull.md similarity index 63% rename from src/geometry/grahams-scan-convex-hull.md rename to src/geometry/convex-hull.md index 0e7b2ed3e..d2122e14c 100644 --- a/src/geometry/grahams-scan-convex-hull.md +++ b/src/geometry/convex-hull.md @@ -1,18 +1,81 @@ - -# Convex Hull construction using Graham's Scan + +# Convex Hull construction In this article we will discuss the problem of constructing a convex hull from a set of points. Consider $N$ points given on a plane, and the objective is to generate a convex hull, i.e. the smallest convex polygon that contains all the given points. -The algorithm used here is **Graham's scan** (proposed in 1972 by Graham) with improvements by Andrew (1979). -The algorithm allows for the construction of a convex hull in $O(N \log N)$ using only comparison, -addition and multiplication operations. The algorithm is asymptotically optimal (as it is proven that there -is no algorithm asymptotically better), with the exception of a few problems where parallel or online processing -is involved. +We will see the **Graham's scan** algorithm published in 1972 by Graham, and +also the **Monotone chain** algorithm published in 1979 by Andrew. Both +are $\mathcal{O}(N \log N)$, and are asymptotically optimal (as it is proven that there +is no algorithm asymptotically better), with the exception of a few problems where +parallel or online processing is involved. -## Description +## Graham Scan Algorithm +The algorithm first finds the bottom-most point $P_0$. If there are multiple points +with the same Y coordinate, the one with the smaller X coordinate is considered. This +step takes $\mathcal{O}(N)$ time. + +Next, all the other points are sorted by polar angle in counterclockwise order. +If the polar angle between two points is the same, the nearest point is chosen instead. + +Then we iterate through each point one by one, and make sure that the current +point and the two before it make a counterclockwise turn, otherwise the previous +point is discarded, since it would make a non-convex shape. If the three points are collinear, +you have a choice of whether to consider it part of the convex hull or not. +In this implementation we are choosing not to. Checking for clockwise or anticlockwise +nature can be done by checking the [orientation](./geometry/oriented-triangle-area.html). + +We use a stack to store the points, and once we reach the original point $P_0$, +the algorithm is done and we return the stack containing all the points of the +convex hull in clockwise order. + +### Implementation + +```cpp graham_scan +struct pt { + double x, y; +}; + +int orientation(pt a, pt b, pt c) { + double v = a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y); + if (v < 0) return -1; // clockwise + if (v > 0) return +1; // counter-clockwise + return 0; +} + +bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } + +pt p0; +bool cmp(pt a, pt b) { + int o = orientation(p0, a, b); + if (o == 0) + return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) + < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); + return o < 0; +} + +bool cmp_p0(pt a, pt b) { + return a.y < b.y || (a.y == b.y && a.x < b.x); +} + +void convex_hull(vector& a) { + p0 = *min_element(a.begin(), a.end(), cmp_p0); + sort(a.begin(), a.end(), &cmp); + + vector st; + for (int i = 0; i < (int)a.size(); i++) { + while (st.size() > 1 && !cw(st[st.size()-2], st.back(), a[i])) + st.pop_back(); + st.push_back(a[i]); + } + + a = st; +} +``` + +## Monotone chain Algorithm The algorithm first finds the leftmost and rightmost points A and B. In the event multiple such points exist, the lowest among the left (lowest Y-coordinate) is taken as A, and the highest among the right (highest Y-coordinate) is taken as B. Clearly, A and B must both belong to the convex hull as they are the farthest away and they cannot be contained @@ -40,11 +103,11 @@ orientation instead of a clockwise orientation. Thus, if the angle made by the l with the line connecting the last point in the lower convex hull and the current point is not counterclockwise, we remove the most recent point added to the lower convex hull as the current point will be able to contain the previous point once added to the hull. -The final convex hull is obtained from the union of the upper and lower convex hull, and the implementation is as follows. +The final convex hull is obtained from the union of the upper and lower convex hull, forming a clockwise hull, and the implementation is as follows. -## Implementation +### Implementation -```cpp grahams_scan +```cpp monotone_chain struct pt { double x, y; }; diff --git a/src/geometry/halfplane-intersection.md b/src/geometry/halfplane-intersection.md index d9c01315d..fd91eb083 100644 --- a/src/geometry/halfplane-intersection.md +++ b/src/geometry/halfplane-intersection.md @@ -4,7 +4,7 @@ In this article we will discuss the problem of computing the intersection of a set of half-planes. Such an intersection can be conveniently represented as a convex region/polygon, where every point inside of it is also inside all of the half-planes, and it is this polygon that we're trying to find or construct. We give some initial intuition for the problem, describe a $O(N \log N)$ approach known as the Sort-and-Incremental algorithm and give some sample applications of this technique. -It is strongly recommended for the reader to be familiar with basic geometrical primitives and operations (points, vectors, intersection of lines). Additionally, knowledge about [Convex Hulls](./geometry/grahams-scan-convex-hull.html) or the [Convex Hull Trick](./geometry/convex_hull_trick.html) may help to better understand the concepts in this article, but they are not a prerequisite by any means. +It is strongly recommended for the reader to be familiar with basic geometrical primitives and operations (points, vectors, intersection of lines). Additionally, knowledge about [Convex Hulls](./geometry/convex-hull.html) or the [Convex Hull Trick](./geometry/convex_hull_trick.html) may help to better understand the concepts in this article, but they are not a prerequisite by any means. ## Initial clarifications and definitions diff --git a/src/index.md b/src/index.md index 697e9f4f5..299e4be87 100644 --- a/src/index.md +++ b/src/index.md @@ -139,7 +139,7 @@ and adding new articles to the collection.* - [Pick's Theorem - area of lattice polygons](./geometry/picks-theorem.html) - [Lattice points of non-lattice polygon](./geometry/lattice-points.html) - **Convex hull** - - [Convex hull construction using Graham's Scan](./geometry/grahams-scan-convex-hull.html) + - [Convex hull construction](./geometry/convex-hull.html) - [Convex hull trick and Li Chao tree](./geometry/convex_hull_trick.html) - **Sweep-line** - [Search for a pair of intersecting segments](./geometry/intersecting_segments.html) From b6655bea39b9bba428a40b92a4ddcf7b98cddd3e Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 16:18:59 -0300 Subject: [PATCH 2/7] Add collinearity and Onion Layers to Convex Hull --- src/geometry/convex-hull.md | 65 ++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index d2122e14c..623834171 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -22,16 +22,22 @@ If the polar angle between two points is the same, the nearest point is chosen i Then we iterate through each point one by one, and make sure that the current point and the two before it make a counterclockwise turn, otherwise the previous -point is discarded, since it would make a non-convex shape. If the three points are collinear, -you have a choice of whether to consider it part of the convex hull or not. -In this implementation we are choosing not to. Checking for clockwise or anticlockwise +point is discarded, since it would make a non-convex shape. Checking for clockwise or anticlockwise nature can be done by checking the [orientation](./geometry/oriented-triangle-area.html). We use a stack to store the points, and once we reach the original point $P_0$, the algorithm is done and we return the stack containing all the points of the convex hull in clockwise order. -### Implementation +If you need to include the collinear points while doing a Graham scan, you need +another step after sorting. You need to get the points that have the biggest +polar distance from $P_0$ (these should be at the end of the sorted vector) and are collinear. +The points in this line should be reversed so that we can output all the +collinear points, otherwise the algorithm would get the nearest point in this +line and bail. This step shouldn't be included in the non-collinear version +of the algorithm, otherwise you wouldn't get the smallest convex hull. + +### Implementation (excluding collinear points) ```cpp graham_scan struct pt { @@ -61,7 +67,7 @@ bool cmp_p0(pt a, pt b) { } void convex_hull(vector& a) { - p0 = *min_element(a.begin(), a.end(), cmp_p0); + p0 = *min_element(a.begin(), a.end(), &cmp_p0); sort(a.begin(), a.end(), &cmp); vector st; @@ -75,6 +81,54 @@ void convex_hull(vector& a) { } ``` +### Implementation (including collinear points) + +```cpp graham_scan +struct pt { + double x, y; +}; + +int orientation(pt a, pt b, pt c) { + double v = a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y); + if (v < 0) return -1; // clockwise + if (v > 0) return +1; // counter-clockwise + return 0; +} + +bool cw_or_collinear(pt a, pt b, pt c) { return orientation(a, b, c) <= 0; } +bool collinear(pt a, pt b, pt c) { return orientation(a, b, c) == 0; } + +pt p0; +bool cmp(pt a, pt b) { + int o = orientation(p0, a, b); + if (o == 0) + return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) + < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); + return o < 0; +} + +bool cmp_p0(pt a, pt b) { + return a.y < b.y || (a.y == b.y && a.x < b.x); +} + +void convex_hull(vector& a) { + p0 = *min_element(a.begin(), a.end(), &cmp_p0); + sort(a.begin(), a.end(), &cmp); + int i = (int)a.size()-1; + while (i >= 0 && collinear(p0, a[i], a.back())) i--; + reverse(a.begin()+i+1, a.end()); + + vector st; + for (int i = 0; i < (int)a.size(); i++) { + while (st.size() > 1 && !cw_or_collinear(st[st.size()-2], st.back(), a[i])) + st.pop_back(); + st.push_back(a[i]); + } + + a = st; +} +``` + ## Monotone chain Algorithm The algorithm first finds the leftmost and rightmost points A and B. In the event multiple such points exist, the lowest among the left (lowest Y-coordinate) is taken as A, and the highest among the right (highest Y-coordinate) @@ -158,5 +212,6 @@ void convex_hull(vector& a) { * [Kattis - Convex Hull](https://open.kattis.com/problems/convexhull) * [Kattis - Keep the Parade Safe](https://open.kattis.com/problems/parade) +* [URI 1464 - Onion Layers](https://www.urionlinejudge.com.br/judge/en/problems/view/1464) * [Timus 1185: Wall](http://acm.timus.ru/problem.aspx?space=1&num=1185) * [Usaco 2014 January Contest, Gold - Cow Curling](http://usaco.org/index.php?page=viewproblem2&cpid=382) From 1819fca105676c13f65c7ec4db8ac6a72c8a1611 Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 16:22:41 -0300 Subject: [PATCH 3/7] Add note about collinearity to Monotone Chain --- src/geometry/convex-hull.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index 623834171..5b5d0b9d0 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -158,6 +158,7 @@ with the line connecting the last point in the lower convex hull and the current the previous point once added to the hull. The final convex hull is obtained from the union of the upper and lower convex hull, forming a clockwise hull, and the implementation is as follows. +If you need collinear points, you just need to include them in the clockwise/counterclockwise routines, by changing `<` to `<=` and `>` to `>=`. ### Implementation From f2d03f1138289049a55764d3ca0a423a5e5088b4 Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 16:31:45 -0300 Subject: [PATCH 4/7] Fix Graham's scan title --- src/geometry/convex-hull.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index 5b5d0b9d0..b8ff82f5a 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -12,7 +12,7 @@ are $\mathcal{O}(N \log N)$, and are asymptotically optimal (as it is proven tha is no algorithm asymptotically better), with the exception of a few problems where parallel or online processing is involved. -## Graham Scan Algorithm +## Graham's scan Algorithm The algorithm first finds the bottom-most point $P_0$. If there are multiple points with the same Y coordinate, the one with the smaller X coordinate is considered. This step takes $\mathcal{O}(N)$ time. From 575a4ef2f0062b7a258bd643afef580977302689 Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 18:02:03 -0300 Subject: [PATCH 5/7] Make code more terse by using more lambdas --- src/geometry/convex-hull.md | 78 ++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index b8ff82f5a..838959ea0 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -53,22 +53,17 @@ int orientation(pt a, pt b, pt c) { bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } -pt p0; -bool cmp(pt a, pt b) { - int o = orientation(p0, a, b); - if (o == 0) - return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) - < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); - return o < 0; -} - -bool cmp_p0(pt a, pt b) { - return a.y < b.y || (a.y == b.y && a.x < b.x); -} - void convex_hull(vector& a) { - p0 = *min_element(a.begin(), a.end(), &cmp_p0); - sort(a.begin(), a.end(), &cmp); + pt p0 = *min_element(a.begin(), a.end(), [&](pt a, pt b) { + return make_pair(a.y, a.x) < make_pair(b.y, b.x); + }); + sort(a.begin(), a.end(), [&](pt a, pt b) { + int o = orientation(p0, a, b); + if (o == 0) + return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) + < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); + return o < 0; + }); vector st; for (int i = 0; i < (int)a.size(); i++) { @@ -83,7 +78,7 @@ void convex_hull(vector& a) { ### Implementation (including collinear points) -```cpp graham_scan +```cpp graham_scan_with_collinear struct pt { double x, y; }; @@ -95,32 +90,27 @@ int orientation(pt a, pt b, pt c) { return 0; } -bool cw_or_collinear(pt a, pt b, pt c) { return orientation(a, b, c) <= 0; } +bool ccw(pt a, pt b, pt c) { return orientation(a, b, c) > 0; } bool collinear(pt a, pt b, pt c) { return orientation(a, b, c) == 0; } -pt p0; -bool cmp(pt a, pt b) { - int o = orientation(p0, a, b); - if (o == 0) - return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) - < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); - return o < 0; -} - -bool cmp_p0(pt a, pt b) { - return a.y < b.y || (a.y == b.y && a.x < b.x); -} - void convex_hull(vector& a) { - p0 = *min_element(a.begin(), a.end(), &cmp_p0); - sort(a.begin(), a.end(), &cmp); + pt p0 = *min_element(a.begin(), a.end(), [&](pt a, pt b) { + return make_pair(a.y, a.x) < make_pair(b.y, b.x); + }); + sort(a.begin(), a.end(), [&](pt a, pt b) { + int o = orientation(p0, a, b); + if (o == 0) + return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) + < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); + return o < 0; + }); int i = (int)a.size()-1; while (i >= 0 && collinear(p0, a[i], a.back())) i--; reverse(a.begin()+i+1, a.end()); vector st; for (int i = 0; i < (int)a.size(); i++) { - while (st.size() > 1 && !cw_or_collinear(st[st.size()-2], st.back(), a[i])) + while (st.size() > 1 && ccw(st[st.size()-2], st.back(), a[i])) st.pop_back(); st.push_back(a[i]); } @@ -167,23 +157,23 @@ struct pt { double x, y; }; -bool cmp(pt a, pt b) { - return a.x < b.x || (a.x == b.x && a.y < b.y); -} - -bool cw(pt a, pt b, pt c) { - return a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y) < 0; +int orientation(pt a, pt b, pt c) { + double v = a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y); + if (v < 0) return -1; // clockwise + if (v > 0) return +1; // counter-clockwise + return 0; } -bool ccw(pt a, pt b, pt c) { - return a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y) > 0; -} +bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } +bool ccw(pt a, pt b, pt c) { return orientation(a, b, c) > 0; } void convex_hull(vector& a) { if (a.size() == 1) return; - sort(a.begin(), a.end(), &cmp); + sort(a.begin(), a.end(), [&](pt a, pt b) { + return make_pair(a.x, a.y) < make_pair(b.x, b.y); + }); pt p1 = a[0], p2 = a.back(); vector up, down; up.push_back(p1); @@ -195,7 +185,7 @@ void convex_hull(vector& a) { up.push_back(a[i]); } if (i == a.size() - 1 || ccw(p1, a[i], p2)) { - while(down.size() >= 2 && !ccw(down[down.size()-2], down[down.size()-1], a[i])) + while (down.size() >= 2 && !ccw(down[down.size()-2], down[down.size()-1], a[i])) down.pop_back(); down.push_back(a[i]); } From 38823829bc0da63dd2e77106178d06fce081bd54 Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 18:15:58 -0300 Subject: [PATCH 6/7] Improve lambdas in Convex Hull --- src/geometry/convex-hull.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index 838959ea0..9a5a6ff97 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -54,10 +54,10 @@ int orientation(pt a, pt b, pt c) { bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } void convex_hull(vector& a) { - pt p0 = *min_element(a.begin(), a.end(), [&](pt a, pt b) { + pt p0 = *min_element(a.begin(), a.end(), [](pt a, pt b) { return make_pair(a.y, a.x) < make_pair(b.y, b.x); }); - sort(a.begin(), a.end(), [&](pt a, pt b) { + sort(a.begin(), a.end(), [&p0](const pt& a, const pt& b) { int o = orientation(p0, a, b); if (o == 0) return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) @@ -94,10 +94,10 @@ bool ccw(pt a, pt b, pt c) { return orientation(a, b, c) > 0; } bool collinear(pt a, pt b, pt c) { return orientation(a, b, c) == 0; } void convex_hull(vector& a) { - pt p0 = *min_element(a.begin(), a.end(), [&](pt a, pt b) { + pt p0 = *min_element(a.begin(), a.end(), [](pt a, pt b) { return make_pair(a.y, a.x) < make_pair(b.y, b.x); }); - sort(a.begin(), a.end(), [&](pt a, pt b) { + sort(a.begin(), a.end(), [&p0](const pt& a, const pt& b) { int o = orientation(p0, a, b); if (o == 0) return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) @@ -171,7 +171,7 @@ void convex_hull(vector& a) { if (a.size() == 1) return; - sort(a.begin(), a.end(), [&](pt a, pt b) { + sort(a.begin(), a.end(), [](pt a, pt b) { return make_pair(a.x, a.y) < make_pair(b.x, b.y); }); pt p1 = a[0], p2 = a.back(); From cbad8182184bc28050a0c4bc355bb4ce5b58602a Mon Sep 17 00:00:00 2001 From: fer22f Date: Mon, 11 Oct 2021 21:41:44 -0300 Subject: [PATCH 7/7] Use single implementation, add tests --- src/geometry/convex-hull.md | 76 ++++++++++++------------------------- test/data/convex_hull.h | 50 ++++++++++++++++++++++++ test/test_convex_hull.cpp | 63 ++++++++++++++++++++++++++++++ test/text_graham_scan.cpp | 34 ----------------- 4 files changed, 137 insertions(+), 86 deletions(-) create mode 100644 test/data/convex_hull.h create mode 100644 test/test_convex_hull.cpp delete mode 100644 test/text_graham_scan.cpp diff --git a/src/geometry/convex-hull.md b/src/geometry/convex-hull.md index 9a5a6ff97..ad40e3f16 100644 --- a/src/geometry/convex-hull.md +++ b/src/geometry/convex-hull.md @@ -37,7 +37,7 @@ collinear points, otherwise the algorithm would get the nearest point in this line and bail. This step shouldn't be included in the non-collinear version of the algorithm, otherwise you wouldn't get the smallest convex hull. -### Implementation (excluding collinear points) +### Implementation ```cpp graham_scan struct pt { @@ -51,49 +51,13 @@ int orientation(pt a, pt b, pt c) { return 0; } -bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } - -void convex_hull(vector& a) { - pt p0 = *min_element(a.begin(), a.end(), [](pt a, pt b) { - return make_pair(a.y, a.x) < make_pair(b.y, b.x); - }); - sort(a.begin(), a.end(), [&p0](const pt& a, const pt& b) { - int o = orientation(p0, a, b); - if (o == 0) - return (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y) - < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); - return o < 0; - }); - - vector st; - for (int i = 0; i < (int)a.size(); i++) { - while (st.size() > 1 && !cw(st[st.size()-2], st.back(), a[i])) - st.pop_back(); - st.push_back(a[i]); - } - - a = st; -} -``` - -### Implementation (including collinear points) - -```cpp graham_scan_with_collinear -struct pt { - double x, y; -}; - -int orientation(pt a, pt b, pt c) { - double v = a.x*(b.y-c.y)+b.x*(c.y-a.y)+c.x*(a.y-b.y); - if (v < 0) return -1; // clockwise - if (v > 0) return +1; // counter-clockwise - return 0; +bool cw(pt a, pt b, pt c, bool include_collinear) { + int o = orientation(a, b, c); + return o < 0 || (include_collinear && o == 0); } - -bool ccw(pt a, pt b, pt c) { return orientation(a, b, c) > 0; } bool collinear(pt a, pt b, pt c) { return orientation(a, b, c) == 0; } -void convex_hull(vector& a) { +void convex_hull(vector& a, bool include_collinear = false) { pt p0 = *min_element(a.begin(), a.end(), [](pt a, pt b) { return make_pair(a.y, a.x) < make_pair(b.y, b.x); }); @@ -104,13 +68,15 @@ void convex_hull(vector& a) { < (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); return o < 0; }); - int i = (int)a.size()-1; - while (i >= 0 && collinear(p0, a[i], a.back())) i--; - reverse(a.begin()+i+1, a.end()); + if (include_collinear) { + int i = (int)a.size()-1; + while (i >= 0 && collinear(p0, a[i], a.back())) i--; + reverse(a.begin()+i+1, a.end()); + } vector st; for (int i = 0; i < (int)a.size(); i++) { - while (st.size() > 1 && ccw(st[st.size()-2], st.back(), a[i])) + while (st.size() > 1 && !cw(st[st.size()-2], st.back(), a[i], include_collinear)) st.pop_back(); st.push_back(a[i]); } @@ -164,10 +130,16 @@ int orientation(pt a, pt b, pt c) { return 0; } -bool cw(pt a, pt b, pt c) { return orientation(a, b, c) < 0; } -bool ccw(pt a, pt b, pt c) { return orientation(a, b, c) > 0; } +bool cw(pt a, pt b, pt c, bool include_collinear) { + int o = orientation(a, b, c); + return o < 0 || (include_collinear && o == 0); +} +bool ccw(pt a, pt b, pt c, bool include_collinear) { + int o = orientation(a, b, c); + return o > 0 || (include_collinear && o == 0); +} -void convex_hull(vector& a) { +void convex_hull(vector& a, bool include_collinear = false) { if (a.size() == 1) return; @@ -179,13 +151,13 @@ void convex_hull(vector& a) { up.push_back(p1); down.push_back(p1); for (int i = 1; i < (int)a.size(); i++) { - if (i == a.size() - 1 || cw(p1, a[i], p2)) { - while (up.size() >= 2 && !cw(up[up.size()-2], up[up.size()-1], a[i])) + if (i == a.size() - 1 || cw(p1, a[i], p2, include_collinear)) { + while (up.size() >= 2 && !cw(up[up.size()-2], up[up.size()-1], a[i], include_collinear)) up.pop_back(); up.push_back(a[i]); } - if (i == a.size() - 1 || ccw(p1, a[i], p2)) { - while (down.size() >= 2 && !ccw(down[down.size()-2], down[down.size()-1], a[i])) + if (i == a.size() - 1 || ccw(p1, a[i], p2, include_collinear)) { + while (down.size() >= 2 && !ccw(down[down.size()-2], down[down.size()-1], a[i], include_collinear)) down.pop_back(); down.push_back(a[i]); } diff --git a/test/data/convex_hull.h b/test/data/convex_hull.h new file mode 100644 index 000000000..ccb772013 --- /dev/null +++ b/test/data/convex_hull.h @@ -0,0 +1,50 @@ +struct pt { double x, y; }; + +struct ConvexHull { + vector points; + vector convex_hull; + vector convex_hull_collinear; +}; + +vector convex_hulls = { + // 1 |1 2 1 |1 2 + // 0 |0 * 3 0 |0 4 3 + // y +----- y +----- + // x 0 1 2 x 0 1 2 + {{{0,0},{1,0},{2,0},{0,1},{2,1}}, + {{0,0},{0,1},{2,1},{2,0}}, + {{0,0},{0,1},{2,1},{2,0},{1,0}}}, + {{{0,3},{1,1},{2,2},{4,4},{0,0},{1,2},{3,1},{3,3}}, + {{0,0},{0,3},{4,4},{3,1}}, + {{0,0},{0,3},{4,4},{3,1}}}, + {{{0,0},{0,8},{1,6},{3,1},{6,6},{8,0},{8,8}}, + {{0,0},{0,8},{8,8},{8,0}}, + {{0,0},{0,8},{8,8},{8,0}}}, + {{{2,6},{3,2},{6,6},{0,0},{0,11},{1,1},{1,9},{7,1},{7,9},{8,10},{8,0}}, + {{0,0},{0,11},{8,10},{8,0}}, + {{0,0},{0,11},{8,10},{8,0}}}, + {{{0,0},{0,1},{1,0},{1,1}}, + {{0,0},{0,1},{1,1},{1,0}}, + {{0,0},{0,1},{1,1},{1,0}}}, + {{{0,0},{1,0},{2,0},{2,1},{0,1}}, + {{0,0},{0,1},{2,1},{2,0}}, + {{0,0},{0,1},{2,1},{2,0},{1,0}}}, + {{{101,100},{102,100},{103,100},{104,100},{105,100},{106,100},{107,100},{108,100},{109,100},{110,100},{110,101},{110,102},{110,103},{110,104},{110,105},{110,106},{110,107},{110,108},{110,109},{110,110},{109,110},{108,110},{107,110},{106,110},{105,110},{104,110},{103,110},{102,110},{101,110},{100,110},{100,109},{100,108},{100,107},{100,106},{100,105},{100,104},{100,103},{100,102},{100,101},{100,100}}, + {{100,100},{100,110},{110,110},{110,100}}, + {{100,100},{100,101},{100,102},{100,103},{100,104},{100,105},{100,106},{100,107},{100,108},{100,109},{100,110},{101,110},{102,110},{103,110},{104,110},{105,110},{106,110},{107,110},{108,110},{109,110},{110,110},{110,109},{110,108},{110,107},{110,106},{110,105},{110,104},{110,103},{110,102},{110,101},{110,100},{109,100},{108,100},{107,100},{106,100},{105,100},{104,100},{103,100},{102,100},{101,100}}}, + {{{56,57},{43,58},{53,50},{59,60},{55,44},{39,38},{37,50},{53,46},{61,50},{38,63},{46,55},{46,50},{56,43},{45,50},{45,56},{52,47},{40,61},{55,56},{57,42},{60,61},{54,55},{57,58},{58,50},{52,53},{56,50},{44,43},{62,50},{58,41},{42,59},{44,57},{60,39},{41,40},{55,50},{47,54},{43,42},{58,59},{44,50},{40,50},{38,37},{60,50},{46,45},{54,50},{62,63},{40,39},{43,50},{63,50},{61,62},{41,60},{62,37},{47,46},{41,50},{38,50},{47,50},{42,41},{45,44},{61,38},{48,53},{48,47},{59,40},{59,50},{57,50},{42,50},{54,45},{39,50},{39,62},{53,54}}, + {{38,37},{37,50},{38,63},{62,63},{63,50},{62,37}}, + {{38,37},{37,50},{38,63},{62,63},{63,50},{62,37}}}, + {{{52,49},{88,100},{142,55},{64,61},{82,100},{130,133},{151,100},{121,100},{151,154},{121,124},{91,112},{142,145},{124,100},{67,64},{124,73},{112,85},{118,121},{112,115},{145,148},{55,100},{79,100},{55,52},{46,100},{130,100},{67,136},{109,88},{76,73},{82,79},{43,100},{88,115},{115,82},{109,112},{133,136},{88,85},{94,91},{49,46},{61,58},{85,82},{151,46},{130,67},{148,49},{157,100},{145,100},{154,100},{154,157},{139,142},{91,88},{67,100},{70,133},{127,100},{73,130},{64,139},{112,100},{139,100},{94,109},{82,121},{121,76},{142,100},{136,139},{115,118},{148,100},{76,100},{79,124},{85,100},{76,127},{139,58},{109,100},{118,79},{46,157},{55,148},{136,100},{70,67},{136,61},{61,100},{133,64},{91,100},{49,100},{145,52},{118,100},{73,100},{52,151},{52,100},{70,100},{85,118},{58,145},{127,70},{61,142},{148,151},{46,43},{115,100},{79,76},{64,100},{49,154},{58,100},{106,91},{106,109},{73,70},{124,127},{154,43},{58,55},{133,100},{127,130}}, + {{46,43},{43,100},{46,157},{154,157},{157,100},{154,43}}, + {{46,43},{43,100},{46,157},{154,157},{157,100},{154,43}}}, + {{{100,92},{95,100},{101,100},{86,100},{100,93},{114,100},{100,95},{108,100},{99,100},{100,111},{115,100},{100,108},{100,102},{100,91},{103,100},{100,99},{100,86},{100,107},{91,100},{100,103},{113,100},{100,97},{100,112},{100,104},{92,100},{100,94},{100,109},{98,100},{90,100},{88,100},{100,110},{100,106},{100,105},{100,85},{100,87},{111,100},{97,100},{85,100},{105,100},{100,101},{100,89},{100,96},{100,115},{104,100},{107,100},{94,100},{100,114},{100,88},{110,100},{100,113},{93,100},{100,90},{100,98},{89,100},{102,100},{96,100},{106,100},{109,100},{112,100},{87,100}}, + {{100,85},{85,100},{100,115},{115,100}}, + {{100,85},{85,100},{100,115},{115,100}}}, + {{{100,88},{110,100},{96,100},{134,100},{90,100},{84,100},{100,80},{104,100},{80,100},{100,72},{100,122},{100,110},{140,100},{100,134},{100,98},{54,100},{100,82},{100,94},{108,100},{116,100},{100,140},{112,100},{100,64},{92,100},{128,100},{138,100},{100,60},{100,128},{100,68},{100,74},{120,100},{100,102},{100,136},{100,132},{58,100},{76,100},{130,100},{124,100},{100,76},{100,96},{142,100},{106,100},{100,86},{146,100},{100,126},{100,58},{100,84},{100,106},{100,146},{114,100},{100,90},{60,100},{100,124},{100,104},{100,92},{144,100},{100,62},{100,108},{136,100},{62,100},{100,114},{100,78},{100,54},{100,118},{102,100},{78,100},{88,100},{100,116},{74,100},{100,138},{98,100},{118,100},{82,100},{132,100},{94,100},{122,100},{66,100},{100,56},{100,112},{86,100},{126,100},{100,144},{68,100},{64,100},{100,66},{100,142},{56,100},{72,100},{100,120},{100,130},{70,100},{100,70}}, + {{100,54},{54,100},{100,146},{146,100}}, + {{100,54},{54,100},{100,146},{146,100}}}, + {{{4,7},{4,6}, {4,5}, {4,4}, {4,3}, {4,1}, {4,0}, {4,9}, {4,8}, {5,10}, {2,0}, {2,3}, {2,2}, {2,5}, {2,4}, {2,7}, {2,6}, {2,9}, {2,8}, {0,3}, {0,2}, {0,1}, {0,0}, {0,7}, {0,5}, {0,4}, {0,9}, {10,10}, {5,1}, {9,2}, {9,0}, {9,1}, {9,6}, {9,7}, {9,4}, {0,10}, {9,8}, {9,9}, {10,9}, {10,8}, {10,7}, {10,5}, {10,3}, {10,2}, {10,1}, {7,9}, {7,4}, {7,5}, {7,6}, {7,7}, {7,0}, {7,1}, {7,2}, {7,3}, {2,10}, {6,10}, {5,8}, {5,9}, {5,6}, {5,4}, {5,2}, {5,3}, {5,0}, {8,10}, {3,9}, {3,1}, {3,2}, {3,3}, {3,4}, {3,5}, {3,6}, {3,7}, {1,9}, {1,2}, {1,3}, {1,0}, {1,1}, {1,6}, {1,7}, {1,4}, {1,10}, {4,10}, {9,10}, {9,5}, {7,10}, {8,9}, {8,3}, {8,7}, {8,6}, {8,5}, {8,4}, {6,5}, {6,4}, {6,0}, {6,3}, {6,2}, {6,9}, {6,8}, {3,10}}, + {{0,0},{0,10},{10,10},{10,1},{9,0}}, + {{0,0},{0,1},{0,2},{0,3},{0,4},{0,5},{0,7},{0,9},{0,10},{1,10},{2,10},{3,10},{4,10},{5,10},{6,10},{7,10},{8,10},{9,10},{10,10},{10,9},{10,8},{10,7},{10,5},{10,3},{10,2},{10,1},{9,0},{7,0},{6,0},{5,0},{4,0},{2,0},{1,0}}} +}; diff --git a/test/test_convex_hull.cpp b/test/test_convex_hull.cpp new file mode 100644 index 000000000..33eaef526 --- /dev/null +++ b/test/test_convex_hull.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +using namespace std; + +namespace GrahamScan { +#include "graham_scan.h" +} +namespace MonotoneChain { +#include "monotone_chain.h" +} +#include "data/convex_hull.h" + +int main() { + for (auto& [points, ans, ans_col] : convex_hulls) { + // for searching valid rotations + ans.insert(ans.end(), ans.begin(), ans.end()); + ans_col.insert(ans_col.end(), ans_col.begin(), ans_col.end()); + + { + using namespace GrahamScan; + vector hull, hull_col; + for (auto p : points) { + hull.push_back({ .x = p.x, .y = p.y }); + hull_col.push_back({ .x = p.x, .y = p.y }); + } + + convex_hull(hull, false); + assert(hull.size() == ans.size()/2); + assert(search(ans.begin(), ans.end(), hull.begin(), hull.end(), [](::pt a, GrahamScan::pt b){ + return a.x == b.x && a.y == b.y; + }) != ans.end()); + + convex_hull(hull_col, true); + assert(hull_col.size() == ans_col.size()/2); + assert(search(ans_col.begin(), ans_col.end(), hull_col.begin(), hull_col.end(), [](::pt a, GrahamScan::pt b){ + return a.x == b.x && a.y == b.y; + }) != ans_col.end()); + } + + { + using namespace MonotoneChain; + vector hull, hull_col; + for (auto p : points) { + hull.push_back({ .x = p.x, .y = p.y }); + hull_col.push_back({ .x = p.x, .y = p.y }); + } + + convex_hull(hull, false); + assert(hull.size() == ans.size()/2); + assert(search(ans.begin(), ans.end(), hull.begin(), hull.end(), [](::pt a, MonotoneChain::pt b){ + return a.x == b.x && a.y == b.y; + }) != ans.end()); + + convex_hull(hull_col, true); + assert(hull_col.size() == ans_col.size()/2); + assert(search(ans_col.begin(), ans_col.end(), hull_col.begin(), hull_col.end(), [](::pt a, MonotoneChain::pt b){ + return a.x == b.x && a.y == b.y; + }) != ans_col.end()); + } + } +} diff --git a/test/text_graham_scan.cpp b/test/text_graham_scan.cpp deleted file mode 100644 index 8c9899f70..000000000 --- a/test/text_graham_scan.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -using namespace std; - -#include "grahams_scan.h" - -bool operator==(pt a, pt b) { - return a.x == b.x && a.y == b.y; -} - -int main() { - vector points = { - {0, 3}, - {1, 1}, - {2, 2}, - {4, 4}, - {0, 0}, - {1, 2}, - {3, 1}, - {3, 3} - }; - - convex_hull(points); - vector expected = { - {0, 0}, - {0, 3}, - {4, 4}, - {3, 1} - }; - - assert(points == expected); -}