本文主要根据第二次模拟赛的泰森多边形试题进行算法的编写,仅用于记录比赛结果。
一、数据及格式
根据文档提供的数据格式,以及后续算法所需要的数据结构,定义DataCenter类。
static class Data
{
static public List<Point> RowPoints = new List<Point>();
static public List<Triangle> Delaunay_Triangles = new List<Triangle>();
static public List<double> Delaunay_Areas =new List<double>();
static public List<Point> Hull_pts=new List<Point>();
static public double Hull_area;
static public List<Polygon> Vorinoi_Polygon=new List<Polygon>();
static public List<double> Vorinoi_Areas = new List<double>();
}
public class Point
{
public double x;
public double y;
public Point() { }
public Point(double x, double y)
{
this.x = x;
this.y = y;
}
}
public class Edge
{
public Point a;
public Point b;
public double d;
public Edge() { }
public Edge(Point a, Point b)
{
this.a = a;
this.b = b;
d=Algorithm.get_d(a,b);
}
}
public class Triangle
{
//三角形的三个顶点
public Point a=new Point();
public Point b=new Point();
public Point c= new Point();
//三角形的边
public List<Edge> edges = new List<Edge>();
public Point o=new Point();//三角形的外心
public double r;//三角形外接圆的半径
public double Area;//三角形的面积
public Triangle() { }
public Triangle(Point a, Point b, Point c)
{
this.a = a;
this.b = b;
this.c = c;
Edge ab=new Edge(a,b);
Edge ac=new Edge(a,c);
Edge bc=new Edge(b,c);
edges.Add(ab);
edges.Add(ac);
edges.Add(bc);
this.Area=get_Area(edges);
this.o = get_O();
this.r=Algorithm.get_d(a,o);
}
//利用垂直平分线交点求取三角形外心
public Point get_O()
{
double d = 2 * (a.x * (b.y - c.y) +b.x * (c.y - a.y) +c.x * (a.y - b.y));
if (Math.Abs(d) < 1e-10)
{
// 如果 d≈0,说明三点几乎共线
return null;
}
double ux = ((a.x * a.x + a.y * a.y) * (b.y - c.y) +
(b.x * b.x + b.y * b.y) * (c.y - a.y) +
(c.x * c.x + c.y * c.y) * (a.y - b.y)) / d;
double uy = ((a.x * a.x + a.y * a.y) * (c.x - b.x) +
(b.x * b.x + b.y * b.y) * (a.x - c.x) +
(c.x * c.x + c.y * c.y) * (b.x - a.x)) / d;
Point o=new Point( ux, uy);
return o;
}
//海伦公式求取三角形面积
public double get_Area(List<Edge> edge)
{
double ab = edge[0].d;
double ac = edge[1].d;
double bc = edge[2].d;
double s = (ab + ac + bc) / 2;
double area = Math.Sqrt(s*(s-ab)*(s-ac)*(s-bc));
return area;
}
}
public class Polygon
{
public List<Point> points;
public double Area;
public Polygon(List<Point> points)
{
this.points = points;
this.Area = Algorithm.Polygon_Area(points);
}
}
二、主要算法
主要展示核心算法部分,文件管理及绘图部分省略。
(一)Delaunay 三角剖分
试题中提供Bowyer-Watson 算法流程进行Delaunay 三角剖分,并使用海伦公式求取每个Delaunay三角形面积,进行升序排序。
static public void Delaunay_BW()
{
//超级三角形的构建
double minX = points.Min(p => p.x);
double maxX = points.Max(p => p.x);
double minY = points.Min(p => p.y);
double maxY = points.Max(p => p.y);
double dx = maxX - minX;
double dy = maxY - minY;
double deltaMax = Math.Max(dx, dy) * 100; // 放大 100 倍,确保足够大
Point p1 = new Point(minX - deltaMax, minY - deltaMax);
Point p2 = new Point(minX - deltaMax, maxY + deltaMax * 3);
Point p3 = new Point(maxX + deltaMax * 3, minY - deltaMax);
Triangle supertriangle =new Triangle(p1,p2,p3);
triangles.Add(supertriangle);
foreach (Point p in points)
{
//标记坏三角形
List<Triangle> bad_triangles=new List<Triangle>();
foreach (Triangle triangle in triangles)
{
Point center = triangle.o;
double r=triangle.r;
double d=get_d(p,center);
if (d <=r) bad_triangles.Add(triangle);
}
//构造边界多边形polygon
Dictionary<string,Edge> edgeCount=new Dictionary<string,Edge>();
foreach(Triangle tr in bad_triangles)
{
foreach(Edge edge in tr.edges)
{
string key=get_key(edge);
if (edgeCount.ContainsKey(key)) edgeCount.Remove(key);
else edgeCount[key] = edge;
}
}
List<Edge> polygon=edgeCount.Values.ToList();
//移除坏三角形
foreach(Triangle tr in bad_triangles) triangles.Remove(tr);
//构造新的三角形
foreach(Edge edge in polygon)
{
if (IsCollinear(edge.a, edge.b, p)) continue;
else
{
triangles.Add(new Triangle(edge.a, edge.b, p));
}
}
}
//移除所有和超级三角形相关的三角形
triangles.RemoveAll(t => t.a == p1 || t.a == p2 || t.a == p3 ||
t.b == p1 || t.b == p2 || t.b == p3 ||
t.c == p1 || t.c == p2 || t.c == p3);
//存储Delaunay三角剖分结果
Data.Delaunay_Triangles=triangles;
//将三角形面积按照升序排序并保存
Data.Delaunay_Areas= triangles.Select(t => t.Area).OrderBy(a => a).ToList();
}
(二)凸包计算
试题根据Andrew's Monotone Chain 算法进行凸包的计算,并使用鞋带公式计算凸包面积
static public void ConvexHull_AWC()
{
//对点集合points进行排序,并初始化凸包序列
points = points.OrderBy(p => p.x).ThenBy(p => p.y).ToList();
List<Point> lower = new List<Point>(), upper = new List<Point>();
//构建下凸包
foreach(Point p in points)
{
while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
lower.Remove(lower[lower.Count - 1]);
lower.Add(p);
}
//构建上凸包
for (int i = points.Count-1; i >= 0 ; i--)
{
Point p = points[i];
while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
upper.Remove(upper[upper.Count - 1]);
upper.Add(p);
}
//合并上下凸包,得到逆时针顺序的凸包点集
lower.RemoveAt(lower.Count-1);
upper.RemoveAt(upper.Count-1);
Data.Hull_pts=lower.Concat(upper).ToList();
//计算凸包面积
Data.Hull_area=Polygon_Area(Data.Hull_pts);
}
(三)Vorinoi图计算
试题主要构建封闭的泰森多边形,并计算其面积进行升序排序。
static public void Voronoi()
{
HashSet<Point> hull_pts=new HashSet<Point>(Data.Hull_pts);
List<Polygon> polygons=new List<Polygon>();
List<Polygon> open_polygons=new List<Polygon>();
List<double> areas=new List<double>();
foreach(Point p in points)
{
List<Triangle> tri_indices = new List<Triangle>();
List<Point> circumcenters=new List<Point>();
//排除凸包上的点
if (hull_pts.Contains(p)) continue;
//获取该点关联的三角形索引列表
tri_indices = Data.Delaunay_Triangles.Where(t=>t.a==p||t.b==p||t.c==p).ToList();
//获取该点关联的所有三角形的外心
circumcenters = tri_indices.Select(t=>t.o).ToList();
//如果外心数量小于3则无法构成多边形,跳过
if(circumcenters.Count<3) continue;
//以 circumcenters 列表中所有点的平均坐标为参考点
Point bar =new Point(circumcenters.Average(o=>o.x), circumcenters.Average(o => o.y));
//对circumcenters 列表进行极角排序
circumcenters=circumcenters.OrderBy(o => Math.Atan2(o.y - bar.y, o.x - bar.x)).ToList();
//对多边形进行检查
if (IsPointInsidePolygon(p, circumcenters))
{
polygons.Add(new Polygon(circumcenters));
areas.Add(Polygon_Area(circumcenters));
}
}
Data.Vorinoi_Polygon=polygons;
Data.Vorinoi_Areas=areas.OrderBy(area=>area).ToList();
}
(四)辅助函数
//使用射线法判断点是否位于封闭多边形内部
static public bool IsPointInsidePolygon(Point p, List<Point> polygon)
{
int crossings = 0;
for (int i = 0; i < polygon.Count; i++)
{
Point a = polygon[i];
Point b = polygon[(i + 1) % polygon.Count];
if (((a.y > p.y) != (b.y > p.y)) &&
(p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x))
{
crossings++;
}
}
return (crossings % 2) == 1;
}
//鞋带公式求取多边形面积
static public double Polygon_Area(List<Point> points)
{
double area=0;
for (int i = 0; i < points.Count; i++)
{
int j = (i + 1) % points.Count;
area +=Math.Abs(points[i].x * points[j].y - points[j].x * points[i].y);
}
return area;
}
//定义向量的叉积
static public double Cross(Point a, Point b, Point c)
{
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
//判断三个点是否共线
static public bool IsCollinear(Point p1, Point p2, Point p3)
{
double area = p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y);
return Math.Abs(area) < 1e-10;
}
//获取边的唯一索引值
static public string get_key(Edge edge)
{
Point p1 = edge.a;
Point p2 = edge.b;
if (p1.x < p2.x || (Math.Abs(p1.x - p2.x) < 1e-10 && p1.y < p2.y))
return $"{p1.x}-{p1.y}-{p2.x}-{p2.y}";
else
return $"{p2.x}-{p2.y}-{p1.x}-{p1.y}";
}
//判断两条边是否相等
static public bool issame(Edge edge1,Edge edge2)
{
if (edge1.a == edge2.a && edge1.b == edge2.b)
{
return true;
}
else if (edge1.a == edge2.b && edge1.b == edge2.a)
{
return true;
}
return false;
}
//计算两点间的距离
static public double get_d(Point a, Point b)
{
double left = Math.Pow(a.x - b.x, 2);
double right = Math.Pow(a.y - b.y, 2);
double d = Math.Sqrt(left + right);
return d;
}
三、结果展示
(一)数据导入
(二)计算结果
(三)Delanuay三角剖分结果
(四)封闭泰森多边形绘图
泰森多边形绘图部分,因泰森多边形算法中只计算并记录了封闭的泰森多边形,故仅绘制出封闭多边形部分。位于凸包上的点的泰森多边形是开放的,暂未研究明白如何计算并绘制。