【计算几何】浅谈凸包Andrew算法

前置知识

计算几何基础知识

引文

这样一个问题,

有许多个杆子,需要用绳子围住所有的杆子,然鹅没有很多的绳子,求最短需要多少绳子。

整个图大概是这样的,

正文

我们要如何解决这题呢?不难想出,最优解法应该是这样的,

而这个图中隐藏着解决这道题的奥秘。

特别的这个图被我们称为凸包。

首先我们可以把它分割成两个图,分为上壳与下壳

分析一下,我们可以发现,在上壳中,编号从大到小,都是向右转,下壳相同。

可以推测个结论,在上壳中,如果一个节点需要左转,这次时必定不是最优的,下壳相同,

怎么样才能改成最优的呢?很简单,让他变成向反方向转:将之前的节点一个个删除,直到达到目的为止。

凸包步骤

首先,将节点按 \(x\) 第一关键字, \(y\) 第二关键字排序,

然后枚举上壳:现将节点 \(A\) 与节点 \(B\) 加入凸包,并加入 \(C\)

尝试连接 \(E\) ,然而由于 \(C\)\(AB\) 的左方向,并不是最短的,所以并不可行,

\(D\) 删去凸包中,依次连接 \(E\)\(F\)\(I\)

不可行,将 \(F\) 删去;

这样上壳就枚举完了,便开始枚举下壳。

下壳是从编号大向编号小的枚举,

依次加入 \(I\)\(G\)\(F\)\(D\)

然而 \(D\)\(GF\) 的左边,自然并非最优,

删掉 \(F\) ,连入 \(C\)

发现 \(C\)\(GH\) 的左边,于是删掉 \(G\) ,连入 \(A\) 。( \(B\) 显然连不了)

然后凸包便成功生成了出来11!

补充

还有最后一个问题,我们该如何判断一个点在直线的方向?

将该节点与直线首尾相接,当这两个直线的叉积大于 \(0\) 时,点在左方;

小于 \(0\) 时,点在右方;

当其等于 \(0\) 时,点与直线共线。

代码

inline int Andrew(Point *p, int n, Point *Poly) { // 求凸包
  int used[N] = {0};                              // 标记每个数字是否使用过
  sort(p + 1, p + n + 1, [](Point a, Point b) {   // 排序节点
    return a.x < b.x || (a.x == b.x && a.y < b.y);
  });
  int cnt = 3;
  Poly[1] = p[1], Poly[2] = p[2];                 // 初始时加入1号和2号节点
  for (int i = 3; i <= n; ++i) {
    if (cnt > 1 && Cross(Poly[cnt] - Poly[cnt - 1], Poly[i] - Poly[cnt] <= 0)) { // 判断是否在左边
      -- cnt;                                                                    // 删除此节点
      used[Poly[cnt + 1]] = 0;
    }
    used[i] = 1;                                 // 标记节点
    Poly[++ cnt] = p[i];                         // 加入凸包
  }
  int k = cnt;                                   // 上壳的起点
  for (int i = n; i >= 1; --i) {
    if (used[i]) {                               // 已经加入凸包
      continue;
    }
    while (cnt > k && Cross(Poly[cnt] - Poly[cnt - 1], Poly[i] - Poly[cnt]) <= 0)) {
      -- cnt;
      used[Poly[cnt + 1]] = 0;
    }
    used[i] = 1;
    Poly[++ cnt] = p[i];
  }
  Poly[++ cnt] = 1;                              // 便于计算周长
  return cnt;
}
posted @ 2023-01-12 14:06  SenGYi  阅读(193)  评论(1)    收藏  举报