假设集合
S
S
S是平面上
n
>
1
n>1
n>1个点
p
1
(
x
1
,
y
1
)
,
.
.
.
,
p
n
(
x
n
,
y
n
)
p_1(x_1,y_1),...,p_n(x_n,y_n)
p1(x1,y1),...,pn(xn,yn)构成的。我们还假设这些点是按照它们的
x
x
x轴坐标升序排列的,如果
x
x
x轴坐标相同,则按照
y
y
y轴坐标升序排列。
算法步骤:
-
排序后找到最左边的点 p 1 p_1 p1和最右边的点 p 2 p_2 p2(因为这两个点经过证明是凸包的顶点)。
-
利用向量 p 1 p n → \overrightarrow{p_1p_n} p1pn将点分成上包 S 1 S_1 S1和下包 S 2 S_2 S2,对于点 p r p_r pr( p r p_r pr不能是 p 1 p n → \overrightarrow{p_1p_n} p1pn上的点,因为 p 1 p n → \overrightarrow{p_1p_n} p1pn上的点不可能再为凸包的顶点):
- 如果 p 1 p n → × p 1 p r → > 0 \overrightarrow{p_1p_n}\times\overrightarrow{p_1p_r}>0 p1pn×p1pr>0,则 p 1 p r → \overrightarrow{p_1p_r} p1pr在 p 1 p n → \overrightarrow{p_1p_n} p1pn的左侧,即 p r p_r pr是上包 S 1 S_1 S1内的点。
- 如果
p
1
p
n
→
×
p
1
p
r
→
<
0
\overrightarrow{p_1p_n}\times\overrightarrow{p_1p_r}<0
p1pn×p1pr<0,则
p
1
p
r
→
\overrightarrow{p_1p_r}
p1pr在
p
1
p
n
→
\overrightarrow{p_1p_n}
p1pn的右侧,即
p
r
p_r
pr是下包
S
2
S_2
S2内的点。
注:这里进行的是向量的叉积运算,设二维向量 a ⃗ = ( a 1 , a 2 ) \vec{a}=(a_1,a_2) a=(a1,a2), b ⃗ = ( b 1 , b 2 ) \vec{b}=(b_1,b_2) b=(b1,b2),那么 a × b = a 1 b 2 − a 2 b 1 a\times b=a_1b_2-a_2b_1 a×b=a1b2−a2b1(具体内容见高数的解析几何部分)
-
将点分类到上包和下包后,分别在其中找出顶点 p m a x p_{max} pmax,它是使三角形 △ p 1 p m a x p n \triangle p_1p_{max}p_n △p1pmaxpn面积最大的点。如果 q 1 ( x 1 , y 1 ) , q 2 ( x 2 , y 2 ) , q 3 ( x 3 , y 3 ) q_1(x_1,y_1),q_2(x_2,y_2),q_3(x_3,y_3) q1(x1,y1),q2(x2,y2),q3(x3,y3)是平面上的任意三个点,那么三角形 △ q 1 q 2 q 3 \triangle q_1q_2q_3 △q1q2q3的面积等于下面这个行列式绝对值的二分之一
∣ x 1 y 1 1 x 2 y 2 1 x 3 y 3 1 ∣ = x 1 y 2 + x 3 y 1 + x 2 y 3 − x 3 y 2 − x 2 y 1 − x 1 y 3 \left| \begin{array}{cccc} x_1 & y_1 & 1 \\ x_2 & y_2 & 1 \\ x_3 & y_3 & 1 \end{array} \right|=x_1y_2+x_3y_1+x_2y_3-x_3y_2-x_2y_1-x_1y_3 ∣∣∣∣∣∣x1x2x3y1y2y3111∣∣∣∣∣∣=x1y2+x3y1+x2y3−x3y2−x2y1−x1y3 -
找到 p m a x p_{max} pmax之后,这里以上包举例,可以证明
- p m a x p_{max} pmax是上包的顶点
- 包含在 △ p 1 p m a x p n \triangle p_1p_{max}p_n △p1pmaxpn之中的点不可能是上包的顶点(因此在后面不必考虑)。
- 同时位于 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax和 p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn的左边的点是不存在的
-
然后利用向量 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax找到位于 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax左侧的点,然后返回第3步的做法找到顶点;利用向量 p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn找到位于 p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn右侧的点,然后返回第3步的做法找到顶点,依次类推,最后找出上包的所有顶点,下包的做法也类似。
实现代码(C++)
#include<vector>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define Up true
#define Down false
// 输出包围pts的凸包
void helper(vector<pair<int,int>> pts,pair<int,int>A,pair<int,int>B,bool up,vector<pair<int,int>>& res) {
if (pts.size()==0) return;
vector<pair<int,int>> LeftPts;
vector<pair<int,int>> RightPts;
pair<int,int> AB = make_pair(B.first-A.first, B.second-A.second);
int maxArea = 0;
pair<int, int> Pmax;
bool flag = false;
if(up){
// 找到使得APmaxB面积最大的点
for(auto p:pts) {
int Area = abs(A.first*p.second + B.first*A.second + p.first*B.second - B.first*p.second - p.first*A.second - A.first*B.second)/2;
if(Area > maxArea) {
Pmax = p;
maxArea = Area;
flag = true;
}
}
pair<int,int> APmax = make_pair(Pmax.first-A.first, Pmax.second-A.second);
pair<int,int> PmaxB = make_pair(B.first-Pmax.first, B.second-Pmax.second);
for(auto p:pts) {
pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
pair<int,int> PmaxP = make_pair(p.first-Pmax.first, p.second-Pmax.second);
// 找出APmax左边的点
if((APmax.first * AP.second - AP.first * APmax.second) > 0) {
LeftPts.push_back(p);
}
// 找出PmaxB右边的点
else if(PmaxB.first * PmaxP.second - PmaxP.first * PmaxB.second > 0) {
RightPts.push_back(p);
}
}
}
else {
// 找到使得APmaxB面积最大的点
for(auto p:pts) {
int Area = abs(A.first*p.second + B.first*A.second + p.first*B.second - B.first*p.second - p.first*A.second - A.first*B.second)/2;
if(Area > maxArea) {
Pmax = p;
maxArea = Area;
flag = true;
}
}
pair<int,int> APmax = make_pair(Pmax.first-A.first, Pmax.second-A.second);
pair<int,int> PmaxB = make_pair(B.first-Pmax.first, B.second-Pmax.second);
for(auto p:pts) {
pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
pair<int,int> PmaxP = make_pair(p.first-Pmax.first, p.second-Pmax.second);
// 找出APmax左边的点
if((APmax.first * AP.second - AP.first * APmax.second) < 0) {
LeftPts.push_back(p);
}
// 找出PmaxB右边的点
else if(PmaxB.first * PmaxP.second - PmaxP.first * PmaxB.second < 0) {
RightPts.push_back(p);
}
}
}
if(flag) {
res.push_back(Pmax);
helper(LeftPts, A, Pmax, up, res);
helper(RightPts, Pmax, B, up, res);
}
}
void quickHull(vector<pair<int,int>> pts, vector<pair<int,int>>&res) {
// 少于三点直接返回点集合
if(pts.size()<=3) {
res = pts;
}
// 点按x坐标排序
sort(pts.begin(),pts.end());
// 最左边的点和最右边的点就是凸包的顶点
pair<int,int> A = pts.front();
pair<int,int> B = pts.back();
res.push_back(A);
vector<pair<int, int>> UpPts;
vector<pair<int, int>> DownPts;
pair<int,int> AB = make_pair(B.first-A.first, B.second-A.second);
for(auto p:pts) {
pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
// 找出上凸包的里面的点
if((AB.first * AP.second - AP.first * AB.second) > 0) {
UpPts.push_back(p);
}
// 找出下凸包的里面的点
else {
DownPts.push_back(p);
}
}
helper(UpPts,A,B,Up,res);
// 找出下凸包的顶点
helper(DownPts,A,B,Down,res);
res.push_back(B);
}
int main() {
vector<int> xpts = {0, 1, 5, 12, 17, 21, 24, 6, 19, 3, 9, 15, 7, 11, 6, 16};
vector<int> ypts = {4, 9, 11, 12, 12, 9, 7, 0, 1, 8, 9, 8, 6, 6, 2, 2};
vector<pair<int, int>> pts;
for (int i = 0; i < xpts.size(); ++i) {
pts.push_back(make_pair(xpts[i], ypts[i]));
}
vector<pair<int, int>> res;
quickHull(pts, res);
for(auto r:res) {
cout << "(" << r.first << "," << r.second << ")" << endl;
}
system("pause");
return 0;
}
绘图程序(python):
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
ptsdata=[[0, 4],[1, 9],[5, 11],[12, 12],
[17, 12],
[21, 9],
[24, 7],
[6, 0],
[19, 1],
[3, 8],
[9, 9],
[15, 8],
[7, 6],
[11, 6],
[6, 2],
[16, 2]]
data=pd.DataFrame(ptsdata).rename(columns={0:'x',1:'y'})
res=[(0,4),(24,7),(12,12),(1,9),(5,11),(17,12),(19,1),(6,0)]
res.sort()
def frange(start, stop, step):
x = start
while x < stop:
yield x
x += step
fig, ax = plt.subplots()
sns.scatterplot(x='x',y='y',data=data)
uppts=[]
downpts=[]
pts=[]
A=res[0]
B=res[-1]
uppts.append(A)
downpts.append(A)
AB=(B[0]-A[0],B[1]-A[1])
for i in range(len(res)):
AP=(res[i][0]-A[0],res[i][1]-A[1])
if((AB[0] * AP[1] - AP[0] * AB[1]) > 0):
uppts.append(res[i])
elif((AB[0] * AP[1] - AP[0] * AB[1]) < 0):
downpts.append(res[i])
uppts.append(B)
downpts.append(B)
pts=uppts+downpts
for i in range(len(pts)-1):
X=np.array(list(frange(pts[i][0],pts[i+1][0],0.01)))
Y=pts[i][1]+((pts[i][1]-pts[i+1][1])/(pts[i][0]-pts[i+1][0]))*(X-pts[i][0])
ax.plot(X,Y,'b-')
plt.show()
绘图结果:
相关题目:
POJ1113题解