凸包(Convex Hull)是一个计算几何(图形学)中的概念。
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造.
在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
那么,怎么才能从平面上的点中构造出凸包呢
一: Graham-Scan(复杂度nlogn)
1: 找到所有点中
x
x
x坐标最小的点,若有相同
x
x
x坐标的多个点,取
y
y
y坐标最小的.以这个点为基准对其余所有的点进行极角排序,极角相等的情况下距离极点
(
p
[
1
]
)
(p[1])
(p[1])最近的优先
上图排序后:
2: 用一个栈(数组)
s
t
k
[
N
]
stk[N]
stk[N], 存储凸包上的点,先把
p
[
1
]
p[1]
p[1],
p
[
2
]
p[2]
p[2]压入栈,用k记录当前栈里的元素个数.
3: 扫描所有的点,不断地更新最外围的点,是否在最外围可由当前点与
s
t
k
[
k
]
stk[k]
stk[k]和
s
t
k
[
k
−
1
]
stk[k-1]
stk[k−1]之间的叉积判断。即
(
s
t
k
[
k
]
−
s
t
k
[
k
−
1
]
)
×
(
p
[
i
]
−
s
t
k
[
k
−
1
]
)
,
(stk[k]-stk[k-1])\times (p[i]-stk[k-1]),
(stk[k]−stk[k−1])×(p[i]−stk[k−1]),
叉积大于零即
p
[
i
]
p[i]
p[i]在向量
(
s
t
k
[
k
]
−
s
t
k
[
k
−
1
]
)
(stk[k]-stk[k-1])
(stk[k]−stk[k−1])左边.
点在右边就弹出栈顶元素,继续判断, 否则压入新点
p
[
i
]
p[i]
p[i]; 如图
p
1
p
2
⃗
×
p
1
p
3
⃗
>
0
说
明
p
3
在
p
1
p
2
⃗
的
左
边
,
压
入
p
3
,
继
续
扫
描
\vec{p_1p_2} × \vec{p_1p_3} > 0 说明p_3在\vec{p_1p_2}的左边,压入p_3,继续扫描
p1p2×p1p3>0说明p3在p1p2的左边,压入p3,继续扫描
p
2
p
3
⃗
×
p
2
p
4
⃗
>
0
说
明
p
4
在
p
2
p
3
⃗
的
左
边
,
压
入
p
4
,
继
续
扫
描
\vec{p_2p_3} × \vec{p_2p_4} > 0 说明p_4在\vec{p_2p_3}的左边,压入p_4,继续扫描
p2p3×p2p4>0说明p4在p2p3的左边,压入p4,继续扫描
p
3
p
4
⃗
×
p
3
p
5
⃗
>
0
说
明
p
5
在
p
3
p
4
⃗
的
左
边
,
压
入
p
5
,
继
续
扫
描
\vec{p_3p_4} × \vec{p_3p_5} > 0 说明p_5在\vec{p_3p_4}的左边,压入p_5,继续扫描
p3p4×p3p5>0说明p5在p3p4的左边,压入p5,继续扫描
p
4
p
5
⃗
×
p
4
p
6
⃗
<
0
说
明
p
6
在
p
4
p
5
⃗
的
右
边
,
弹
出
栈
顶
元
素
,
即
弹
出
p
5
,
继
续
判
断
\vec{p_4p_5} × \vec{p_4p_6} < 0 说明p_6在\vec{p_4p_5}的右边,弹出栈顶元素,即弹出p_5,继续判断
p4p5×p4p6<0说明p6在p4p5的右边,弹出栈顶元素,即弹出p5,继续判断
p
3
p
4
⃗
×
p
3
p
6
⃗
>
0
说
明
p
6
在
p
3
p
4
⃗
的
左
边
,
压
入
p
6
,
继
续
扫
描
\vec{p_3p_4} × \vec{p_3p_6} > 0 说明p_6在\vec{p_3p_4}的左边,压入p_6,继续扫描
p3p4×p3p6>0说明p6在p3p4的左边,压入p6,继续扫描
p
4
p
6
⃗
×
p
4
p
7
⃗
<
0
说
明
p
7
在
p
4
p
6
⃗
的
右
边
,
弹
出
栈
顶
元
素
,
即
弹
出
p
6
,
继
续
判
断
\vec{p_4p_6} × \vec{p_4p_7} < 0 说明p_7在\vec{p_4p_6}的右边,弹出栈顶元素,即弹出p_6,继续判断
p4p6×p4p7<0说明p7在p4p6的右边,弹出栈顶元素,即弹出p6,继续判断
p
3
p
4
⃗
×
p
3
p
7
⃗
>
0
说
明
p
7
在
p
3
p
4
⃗
的
左
边
,
压
入
p
7
,
继
续
扫
描
\vec{p_3p_4} × \vec{p_3p_7} > 0 说明p_7在\vec{p_3p_4}的左边,压入p_7,继续扫描
p3p4×p3p7>0说明p7在p3p4的左边,压入p7,继续扫描
p
4
p
7
⃗
×
p
4
p
8
⃗
>
0
说
明
p
8
在
p
4
p
7
⃗
的
左
边
,
压
入
p
8
,
扫
描
完
毕
\vec{p_4p_7} × \vec{p_4p_8} > 0 说明p_8在\vec{p_4p_7}的左边,压入p_8,扫描完毕
p4p7×p4p8>0说明p8在p4p7的左边,压入p8,扫描完毕
栈中剩下的元素就是凸包点了.ok
有人:“你这不是在逗我嘛,这图都不连通,怎么就凸包了…”
代码:
struct Point;
typedef Point Vector;
struct Point{
double x,y;
Point(){}
Point(double a, double b):x(a),y(b){}
double len(){return sqrt(x*x+y*y);}
double disToPoint(Point p){return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));}
void read(){scanf("%lf%lf",&x,&y);}
Point operator+(Vector v){return {x+v.x,y+v.y};}
Vector operator-(Point p){return {x-p.x,y-p.y};}
double operator^(Vector v){return x*v.y-y*v.x;}//叉乘
double operator*(Vector v){return x*v.x+y*v.y;}//点乘
Vector operator*(double d){return {x*d,y*d};}
Vector operator/(double d){return {x/d,y/d};}
bool operator==(Point p){return cmp(x,p.x)==0&&cmp(y,p.y)==0;}
bool operator<(Point p){if(cmp(x,p.x)==0) return y<p.y;return x<p.x;}
};
Point base;
bool grahamCmp(Point& p1,Point& p2){
if(sgn((p1-base)^(p2-base))==0)
return (p1-base).len()<(p2-base).len();
return ((p1-base)^(p2-base))>0;
}
Point p[MAXN];
Point stk[MAXN];
int graham(){//点的存储位置[1,n]
for(int i=2;i<=n;i++){
if(p[i]<p[1])
swap(p[i],p[1]);
}
base=p[1];
sort(p+2,p+1+n,grahamCmp);//将其余的点排序
int k=0;//凸包点的个数
stk[++k]=p[1];stk[++k]=p[2];
for(int i=3;i<=n;i++){
while(k>1&&sgn((stk[k]-stk[k-1])^(p[i]-stk[k-1]))<0)
k--;
stk[++k]=p[i];
}
return k;
}
例题:
P2742 【模板】二维凸包
HDU - 1392 Surround the Trees
HDU - 2297 Run