计算几何之凸包之graham scan

Graham scan算法:

主要步骤:

  1. 找出所有已知点的y值最小,如果相同,取x值最小的点,作为基准点s。
  2. 以s为基准,所有的点按照与X轴夹角从小到大排序。
  3. 使用两个栈,一个记录已访问的点,一个记录未访问的点,使用已访问点的最后两个入栈元素p,q判断未访问点元素c的位置,如果点c在pq向量的左边,则将c压入已访问栈,如果c在pq向量的右边,则已访问栈出pop()一个元素。再次判断。

1,实现:

public static Stack<Point> grahamScan(Point[] s, int n) {
    int start = LTL(s, n);
    Point sp = s[start];
    Point[] sorted = sort(s, n, sp);

    Stack<Point> visited = new Stack<>();
    Stack<Point> unvisited = new Stack<>();

    visited.push(sp);
    visited.push(sorted[0]);

    // attention: sort by degree base on start point
    // cut head and tail, the smallest and the biggest
    for (int i = 1; i <= s.length - 1; i++) {
        unvisited.push(sorted[i]);
    }

    while (!unvisited.empty()) {
        Point p1 = visited.get(visited.size() - 2);
        Point p2 = visited.get(visited.size() - 1);
        System.out.println(String.format("(%s, %s)->(%s, %s)", p1.x, p1.y, p2.x, p2.y));

        if (isLeft(visited.get(visited.size() - 2), visited.get(visited.size() - 1), unvisited.get(0))) {
            visited.push(unvisited.remove(0));
        } else {
            visited.pop();
        }
    }
    return visited;
}

2,注意点:

第一点,是要找到最下,最左的点s,这里只需要通过一次遍历就可以得到:

private static int LTL(Point[] s, int n) {
    int rank = 0;
    for (int i = 0; i < s.length; i++)
        if (s[rank].y > s[i].y || (s[rank].y == s[i].y && s[rank].x > s[i].x))
            rank = i;

    return rank;
}

第二点:以s点为基准,排序,排序算法的复杂度都是O(nlogn):

private static Point[] sort(Point[] s, int n, Point base) {
    // cos = a*
    return Arrays.stream(s).sorted((o1, o2) -> {
        double v1 = calculateDegree(new Point(o1.x - base.x, o1.y - base.y), Point.X);
        double v2 = calculateDegree(new Point(o2.x - base.x, o2.y - base.y), Point.X);
        return Double.compare(v1, v2);
    }).toArray(Point[]::new);
}

这里的:

/**
 * ->  ->
 * a   b
 * cosα=ab/|a||b|=
(x1y1+x2,y2)/(根号(x1^2+y1^2)根号(x2^2+y1^2))
 */

private static double calculateDegree(Point a, Point b) {
    double v = (a.x * b.x + a.y * b.y) / (Math.pow(Math.pow(a.x, 2) + Math.pow(a.y, 2), 0.5) * Math.pow(Math.pow(b.x, 2) + Math.pow(b.y, 2), 0.5));
    return Math.acos(v);
}

3,这里附上测试数据以及测试代码:

public static void test() throws URISyntaxException, IOException {
    URL resource = Thread.currentThread().getContextClassLoader().getResource("convex/convex.txt");
    Path of = Path.of(Objects.requireNonNull(resource).toURI());

    Point[] points = Files.lines(of).skip(1).map(e -> {
        String[] s = e.split(" ");
        return new Point(Integer.parseInt(s[0]), Integer.parseInt(s[1]));
    }).toArray(Point[]::new);
    List<Point> hull = PointPosition.grahamScan(points, points.length);

    for (Point point : hull) {
        System.out.println(String.format("(%s, %s)", point.x, point.y));
    }

}

convex.txt内容如下:

10
7 9
-8 -1
-3 -1
1 4
-3 9
6 -4
7 5
6 6
-6 10
0 8

利用python3.7画的图:很遗憾不知道怎么把凸包上的点链接起来:

Python源码为:

# coding=utf-8
import matplotlib.pyplot as plt

fig = plt.figure()
ax1 = fig.add_subplot()
# 设置标题
ax1.set_title('convex hull')
# 设置X轴标签
plt.xlabel('X')
# 设置Y轴标签
plt.ylabel('Y')
# 画散点图
for i, j in [(7, 9), (-8, -1), (-3, -1), (1, 4), (-3, 9), (6, -4), (7, 5), (6, 6), (-6, 10), (0, 8)]:
    ax1.scatter(i, j, c='r', marker='o')
    plt.text(i, j, (i, j), ha='center', va='bottom', fontsize=10)
# 设置图标
plt.legend('x1')
# 显示所画的图
plt.show()

 

Point的数据结构为:

public class Point {

    public double x;
    public double y;

    public Point() {
    }

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public static Point of(double x, double y){
        return new Point(x, y);
    }

    public static Point Y = new Point(0, 1);
    public static Point X = new Point(1, 0);

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

最后附上CAD2016画的图:当然了,这个图画的不大好,但是由于是矢量图,所以缩放不会失真哦!!

发布了29 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/u012803274/article/details/99432752
今日推荐