Graham算法笔记
一切参照题解
笔记部分:
怎么判断旋转方向:
如图,第一次入栈,P2直接进入
此处为什么p3是左转:
我们判断左转还是右转的方法:
看要进入的点pi与栈顶的点pa和栈第二个点pb:
此处就是看向量p1p2到向量p1p3是顺时针还是逆时针,如果是逆时针,为左转,顺时针为右转
此处是逆时针
然后我们看接下来:
p4将要入栈时候,栈顶是p3,下面一个是p2,那么我们就以p2为起点,看向量p2p3到向量p2p4是顺时针还是逆时针,此处我们发现是顺时针,就是右转。
如何判断两个向量是顺时针还是逆时针?
做向量a叉乘b = xayb - xbya > 0 :逆时针,<0顺时针。
// AC代码:题目传送门
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <stack>
#include <algorithm>
#include <cmath>
using namespace std;
// 方法:Graham算法
int n;
const int MAXN = 1e5 + 5;
struct Point
{
double x, y;
}p[MAXN],s[MAXN];
//// 判断左转右转
//bool check(Point p1, Point p2, Point p)
//{
// // p 为起始点,p1 p2分别为终点,判断向量pp1到pp2是顺时针还是逆时针
// int x1 = p1.x - p.x,
// y1 = p1.y - p.y,
// x2 = p2.x - p.x,
// y2 = p2.y - p.y;
// if (x1 * y2 - x2 * y1 > 0) // 逆时针
// return true; // 左转
// return false;
//}
double check(Point a1, Point a2, Point b1, Point b2)//检查叉积是否大于0,如果是a就逆时针转到b
{
return (a2.x - a1.x) * (b2.y - b1.y) - (b2.x - b1.x) * (a2.y - a1.y);
}
double d(Point a, Point b) // 距离公式
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
bool cmp(Point p1, Point p2)//排序函数, sort时候,是调用 a < b 判断是否成立调用的是cmp,即cmp(a,b) 如果true, a在b前,否则,b在a前
{
double tmp = check(p[1], p1, p[1], p2);
if (tmp > 0)
return 1;
if (tmp == 0 && d(p[0], p1) < d(p[0], p2)) // 二者平行
return 1;
return 0;
}
int main()
{
double mid;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
cin >> p[i].x >> p[i].y;
if (i != 1 && p[i].y < p[1].y)// 找出最低点p[1]
{
/*mid = p[1].y; p[1].y = p[i].y; p[i].y = mid;
mid = p[1].x; p[1].x = p[i].x; p[i].x = mid;*/
swap(p[i], p[1]);
}
if (i != 1 && p[i].y == p[1].y && p[i].x < p[1].x)
{
swap(p[i], p[1]);
}
}
sort(p+2, p+ 1 + n,cmp);
s[1] = p[1];
int cnt = 1;
for (int i = 2; i <= n; i++)
{
while (cnt > 1 && check(s[cnt - 1], s[cnt], s[cnt], p[i]) <= 0)
--cnt;
s[++cnt] = p[i];
}
s[++cnt] = p[1]; // 第一个点同时也是末尾
double sum_d = 0;
for (int i = 1; i < cnt; i++)
sum_d += d(s[i], s[i + 1]);
printf("%.2lf\n", sum_d);
return 0;
}