凸包—Graham扫描法 && Jarvis March(Gift Wrapping)
Graham扫描法
提要
1.点线位置:利用向量叉积判断点在直线的某一侧
2.极角排序:选定边界上的点(一般为最左下点),按相对该点构成极角的大小排序
3.退化问题:出现多点在同一条直线上时,将距离加入排序
步骤
1.按最左下点进行极角排序
2.选择序列前两个依次入栈A,剩余则从序列末尾开始往前依次入栈B
3.如果栈B非空,每次从栈A中取出两个点 a , b 构成直线ab,从栈B中取出一个点c,判断c与ab的位置关系。如果c在ab左侧,a、b回到栈A,c再入栈A,若果c在ab右侧,a回到栈A中。
求凸包并输出凸包边上的点,这里是顺时针顺序
///Graham Scan
///O(n*log(n))
#include <stack>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 10000 + 5;
const double PI = acos(-1.0);
struct Point{
double x, y;
bool extreme;
int pos;
};
Point P[maxn];
int n;
double dis(Point A, Point B)
{
return sqrt((A.x - B.x)*(A.x - B.x) + (A.y - B.y)*(A.y - B.y));
}
double cross(Point O, Point A, Point B)
{
return (A.x - O.x)*(B.y - O.y) - (A.y - O.y)*(B.x - O.x);
}
///B to OA
bool cmp(Point A, Point B)
{
///sort base 0
int cur = 0;
double temp = cross(P[cur], A, B);
///control direction
if(temp > 0) return true;
else if(temp == 0 && (dis(P[cur], A) < dis(P[cur], B))) return true;
else return false;
}
bool ToLeft(Point O, Point A, Point B)
{
double temp = cross(O, A, B);
if(temp > 0) return true;
else if(temp == 0 && (dis(O, A) < dis(O, B))) return true;
else return false;
}
void Graham()
{
sort(P+1, P+n, cmp); ///extreme angle sort
stack<Point> s1, s2;
while(!s1.empty()) s1.pop();
while(!s2.empty()) s2.pop();
if(n == 1){
s1.push(P[0]);
P[0].extreme = true;
}
else if(n == 2){
s1.push(P[0]);
s1.push(P[1]);
P[0].extreme = true;
P[1].extreme = true;
}
else{
s1.push(P[0]), s1.push(P[1]);
for(int i = n-1; i >=2; i--) s2.push(P[i]);
while(!s2.empty()){
Point B = s1.top();s1.pop();
Point A = s1.top();
Point C = s2.top();
B.extreme = false;
if(ToLeft(A, B, C)){
s2.pop();
s1.push(B), s1.push(C);
B.extreme = true; C.extreme = true;
}
}
}
Point a[1005];
int cnta = 0;
while(!s1.empty()){
a[cnta++] = s1.top();
s1.pop();
}
for(int i = 0; i < cnta; i++)
printf("%d ",a[i].pos);
printf("\n");
}
int main()
{
while(~scanf("%d", &n)){
int tmp = 0;
for(int i = 0; i < n; i++){
scanf("%lf%lf", &P[i].x, &P[i].y);
P[i].pos = i;///the number of P[i]
P[i].extreme = false;
if(P[i].y < P[tmp].y || (P[i].y == P[tmp].y && P[i].x < P[tmp].x))
tmp = i;
}
swap(P[tmp], P[0]);
Graham();
}
return 0;
}
Jarvis March(Gift Wrapping)
步骤:
1.选出最左下方的点,顺时针扩展成一条极边 K->S
2.遍历其他所有点,寻找一个点 T(T!=K) 使得边 S->T 在所有点的右侧,当下一次选的点是初始点时结束
(由于一次是遍历所有的点,所以选出的凸包上的点是凸包角上的点)
(时间复杂度为O(N*H),最坏的情况是所有点都是凸包的角上的点)
///Jarvis March(Gift Wrapping)
///O(N*H)
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 10000+5;
struct Point{
double x, y;
int succ;
bool extreme;
};
double dis(Point A, Point B)
{
return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
}
//t to k-s
bool ToLeft(Point k, Point s, Point t)
{
double temp = (s.x - k.x)*(t.y - k.y) - (s.y - k.y)*(t.x - k.x);
if(temp > 0) return true;
else if(temp == 0 && dis(k, s) < dis(k, t)) return true;
else return false;
}
int LTL(Point P[], int n)
{
int ltl = 0;
for(int k = 1; k < n; k++)
if(P[k].y < P[ltl].y ||
(P[k].y == P[ltl].y &&
P[k].x < P[ltl].x))
ltl = k;
return ltl;
}
void Jarvis(Point P[], int n){
for(int k = 0; k < n; k++) P[k].extreme = false;
int ltl = LTL(P, n); int k = ltl;
do{
P[k].extreme = true;
int s = -1;
for(int t = 0; t < n; t++)
if(t != k &&
(s == -1 || ToLeft(P[k], P[s], P[t])))
s = t;
P[k].succ = s; k = s;
}while(ltl != k);
}
int main()
{
int n;
Point a[maxn];
while(~scanf("%d", &n)){
memset(a, 0, sizeof(a));
for(int i = 0; i < n; i++){
scanf("%lf%lf", &a[i].x, &a[i].y);
}
Jarvis(a, n);
for(int i = 0; i < n; i++){
if(a[i].extreme) printf("%d\n", i);
}
}
return 0;
}