計算幾何--凸包之graham scan算法

Graham scan算法主要步驟:

  1. 找出所有已知點的y值最小,如果相同,取x值最小的點,作爲基準點s。

  2. 以s爲基準,所有的點按照與X軸夾角從小到大排序。

  3. 使用兩個棧,一個記錄已訪問的點,一個記錄未訪問的點,使用已訪問點的最後兩個入棧元素p,q判斷未訪問點元素c的位置,如果點c在pq向量的左邊,則將c壓入已訪問棧,如果c在pq向量的右邊,則已訪問棧出pop()一個元素。再次判斷。

1,實現:

(1.1)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 +
                '}';
    }
}

 

(1.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;
}

(1.3)以s點爲基準,按照與x軸形成的夾角從小到大排序。排序算法的複雜度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);
}

 (1.4)主方法體

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,測試數據以及測試代碼

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

程序運行結果爲,也就是構成凸包的點集

"C:\Program Files\Java\jdk-11.0.7\bin\java.exe" "-javaagent: computationalgeometry.ConvexHull
(6.0, -4.0)
(7.0, 5.0)
(7.0, 9.0)
(-6.0, 10.0)
(-8.0, -1.0)
(6.0, -4.0)

3,結果驗證

(3.1)利用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()

 

手動將程序輸出點集鏈接起來。

目測結果是正確的。

最後附上CAD2016畫的圖,這個圖畫的不大好,但是由於是矢量圖,所以縮放不會失真哦!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章