Graham scan算法:
主要步骤:
- 找出所有已知点的y值最小,如果相同,取x值最小的点,作为基准点s。
- 以s为基准,所有的点按照与X轴夹角从小到大排序。
- 使用两个栈,一个记录已访问的点,一个记录未访问的点,使用已访问点的最后两个入栈元素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画的图:当然了,这个图画的不大好,但是由于是矢量图,所以缩放不会失真哦!!