了解凸包及Graham扫描法

       问题描述:二位平面内,给定n个散乱的点,求一个最小凸多边形(凸包),使得n个点都不在凸多边形外。

       问题的解决用到Graham算法:

算法步骤:

  1.取y坐标最小的一点,作为p0,显然p0一定在凸包上。

 计算几何--凸包总结_i++

  2.将p0作为坐标系原点,其他点按极角从小到大排序,从p1开始编号。

 计算几何--凸包总结_i++_02

 

  3.从小到大遍历所有点:显然[p0, p1] 在凸包中

 计算几何--凸包总结_叉积_03

 

  4.连接p1, p2的时候需要判断:p0->p1 叉乘 p1->p2 是否大于0:

    > 0 p1->p2 在 p0->p1 夹角小于π,物理意义:p1->p2 在 p0->p1的左边,满足凸多边形定义。

    = 0 p1->p2 与 p0->p1 共线,同向满足,相反不满足。

    < 0 p1->p2 在 p0->p1 夹角大于π,不满足。

 计算几何--凸包总结_i++_04

 

  5.连接p2, p3 向量p2->p3在p1->p2左边,满足定义,当连接p3, p4的时候,发现不满足定义了,此时要放弃p3, 从p2开始回溯,找到第一个满足要求的点。

 计算几何--凸包总结_叉积_05

 

  6.以此类推,知道回到p0点。

 计算几何--凸包总结_凸多边形_06

 

Graham scan 正确性:

       令散点的数量为k,散点(p0 ~ pk – 1)已经按照极角排序。

       当k=3时,显然,p0->p1时凸包的一条边,且p2 极角小于p1, 那么p1->p2在p0->p1的左侧,所以p1->p2保留。

       当k=n时,假设此时(p0 ~ pn – 1) 都按照Graham scan找出“最完美的凸包”

       当k=n+1时,如果pn – 1 -> pn 在 pn – 2 > pn – 1左边,如下图,如果舍弃pn,直接连接pn – 1 -> p0, 那么pn在多边形外,不满足要求。

      计算几何--凸包总结_极角排序_07

 

              证毕。

代码实现:

/****************************凸包模板*******************************/

const double eps = 1e-8;

int sgn(double x) {
  if (fabs(x) < eps)
    return 0;

  if (x < 0)
    return -1;
  else
    return 1;
}

struct Point {
  double x, y;
  Point() {}
  Point(double _x, double _y) {
    x = _x;
    y = _y;
  }

  Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
  //叉积
  double operator^(const Point &b) const { return x * b.y - y * b.x; }
  //点积
  double operator*(const Point &b) const { return x * b.x + y * b.y; }
  void input() {
    scanf("%lf%lf", &x, &y);
  }
};

struct Line {
  Point s, e;
  Line() {}
  Line(Point _s, Point _e) {
    s = _s;
    e = _e;
  }
};

//*两点间距离
double dist(Point a, Point b) { return sqrt((a - b) * (a - b)); }

/*
 * 求凸包,Graham算法
 * 点的编号0~n-1
 * 返回凸包结果Stack[0~top-1]为凸包的编号
 */

const int MAXN = 1010;
Point List[MAXN];
int Stack[MAXN];  //用来存放凸包的点
int top;  //表示凸包中点的个数

//相对于List[0]的极角排序

bool _cmp(Point p1, Point p2) {
  double tmp = (p1 - List[0]) ^(p2 - List[0]);
  if (sgn(tmp) > 0)
    return true;
  else if (sgn(tmp) == 0 && sgn(dist(p1, List[0]) - dist(p2, List[0])) <= 0)
    return true;
  else
    return false;
}

void Graham(int n) {
  Point p0;
  int k = 0;
  p0 = List[0];
  //找最下边的一个点
  for (int i = 1; i < n; i++) {
    if ((p0.y > List[i].y) || (p0.y == List[i].y && p0.x > List[i].x)) {
      p0 = List[i];
      k = i;
    }
  }

  swap(List[k], List[0]);
  sort(List + 1, List + n, _cmp);
  if (n == 1) {
    top = 1;
    Stack[0] = 0;
    return;
  }

  if (n == 2) {
    top = 2;
    Stack[0] = 0;
    Stack[1] = 1;
    return;
  }

  Stack[0] = 0;
  Stack[1] = 1;
  top = 2;
  for (int i = 2; i < n; i++) {
    while (top > 1 &&
        sgn((List[Stack[top - 1]] - List[Stack[top - 2]]) ^ (List[i] - List[Stack[top - 2]])) <= 0)
      top--;
    Stack[top++] = i;
  }
}

/****************************凸包模板*******************************/

 

我每天都在努力,只是想证明我是认真的活着.