ConvexHull(凸包)
凸包是什么
凸包是计算几何一个非常基础核心的概念。我理解的凸包就是给定一个点集合, 最外围的点的包围体就是凸包。如下所示:
极点(ExtremityPoint)
给定的点集合中, 如果一个点存在一条直线, 让其他所有点都在于该直线的同一侧, 则该点为极点。
非极点
和极点性质相反, 经过该点任一直线都无法做到让其他所有点位于同一侧
凸集合
给定一个点集合S = [P1, P2...Pn], 给每个点分配一个权重rx, 满足条件:
[1]rx >= 0 &&rx <= 1,
[2]r1 + r2 + ...... rn = 1.0
由 P = r1 * P1 + ..... + Pn * rn 计算公式, 得到新的点集合,成为S的凸组合.
我理解的是点S构成的凸包内部的点集合就是凸组合。
凸相关
给定一个点集合S, 加入一个点A后,凸组合没变化的,就称点A和点集合S是凸相关的。
换一个说法就是, 点A包含在集合S里的凸组合里。
比如给定点S = {1, 4}, 点2或者点3和集合S是凸相关。
凸无关
给定一个点集合S, 加入一个点A后,凸组合发生变化, 就称点A和点集合S是凸无关。
对于点集合S(1, 2, 3), 点4是凸无关.
给定点集合求极点
分解问题
极点满足: 不在点集合构成的所有三角形内部的点, 则为极点。反之为非极点。
伪代码实现
算法复杂度为O(n4)
算法实现
In-Trianle-Test
In-Trianle-Test: 判断一个点是否在一个三角形内.
如果点S在三角形三条边的同一侧, 则在三角形内. 分解为三次ToLeft测试,即点和三角形的三条边的测试.
ToLeft测试
ToLeft测试就是用于判断点是否在一条边的左侧.
叉积面积正负来判断左侧 or 右侧
(CCW 的时候ToLeft 世界左侧返回True, CW的时候ToLeft 世界左侧返回False)
代码实现
给定点集合
代码实现
#include <iostream>
#include <vector>using namespace std;struct Point
{float x;float y;
};float Area2(const Point& inPointA, const Point& inPointB, const Point& inPointC)
{float value =inPointA.x * inPointB.y - inPointA.y * inPointB.x+ inPointB.x * inPointC.y - inPointB.y * inPointC.x+ inPointC.x * inPointA.y - inPointC.y * inPointA.x;return value;
}bool IsLeftTest(const Point& inPointA, const Point& inPointB, const Point& inPointC)
{return Area2(inPointA, inPointB, inPointC) > 0;
}bool IsInTrianle(const Point& inPoint, const Point& triangleA, const Point& triangleB, const Point& triangleC)
{bool bLeftA = IsLeftTest(triangleA, triangleB, inPoint);bool bLeftB = IsLeftTest(triangleB, triangleC, inPoint);bool bLeftC = IsLeftTest(triangleC, triangleA, inPoint);return (bLeftA == bLeftB) && (bLeftB == bLeftC);
}void GetConvexPointSet(const vector<Point>& inPoints, vector<Point>& outPoints)
{int pointNum = inPoints.size();vector<bool> extrmeFlags;extrmeFlags.resize(pointNum);for (int index = 0; index < pointNum; index++){extrmeFlags[index] = true;}// O(n4)for (int idxA = 0; idxA < pointNum; idxA++){for (int idxB = idxA + 1; idxB < pointNum; idxB++){for (int idxC = idxB + 1; idxC < pointNum; idxC++){for (int s = 0; s < pointNum; s++){if (s == idxA || s == idxB || s == idxC || !extrmeFlags[s])continue;if (IsInTrianle(inPoints[s], inPoints[idxA], inPoints[idxB], inPoints[idxC])){extrmeFlags[s] = false;}}}}}for (int index = 0; index < pointNum; index++){if (extrmeFlags[index]){outPoints.push_back(inPoints[index]);}}}int main()
{std::cout << "Hello World!\n";// point set contructvector<Point> inPoints ={{0, 0},{-1, -1},{5, 2},{4, 5},{3, 3},{-1, 3},{2, 2},{-3, 2},};vector<Point> outPoints;GetConvexPointSet(inPoints, outPoints);for (int index = 0; index < outPoints.size(); index++){printf("(%f, %f)\n", outPoints[index].x, outPoints[index].y);}}
运行结果
很显然漏掉了 (-1, -1, -1, -1)
原因是(-1, -1), (0, 0) (2, 2), (3, 3)四点共线导致ToLeft测试失效,误认为也在三角形内部。所以得改进检测算法。
改进In-Trianle-Test
在进行In-Trianle-Test的时候先判断是否四点共线, 然后在进行ToLeftTest
bool IsInTrianle(const Point& inPoint, const Point& triangleA, const Point& triangleB, const Point& triangleC)
{float a_area2 = Area2(triangleA, triangleB, inPoint);float b_area2 = Area2(triangleB, triangleC, inPoint);float c_area2 = Area2(triangleC, triangleA, inPoint);if (a_area2 == 0 && b_area2 == 0 && c_area2 == 0)return false;bool bLeftA = a_area2 > 0;bool bLeftB = b_area2 > 0;bool bLeftC = c_area2 > 0;// 取决于CCW/CWreturn (bLeftA == bLeftB) && (bLeftB == bLeftC);
}
运行结果
参考资料
[1]清华计算几何 P1-P12