秒懂算法 | 计算几何:圆

简介: 计算几何的基础是点积和叉积,它们定义了向量的大小和方向的关系,是其他计算几何概念和算法的出发点。在点积和叉积的基础上,本篇重点介绍圆覆盖。计算几何题目的代码大多繁琐冗长,因此掌握模板代码是学习计算几何的关键。本篇精心组织了经典的几何模板

640.jpg


先说明计算几何中的数值计算问题。计算几何题目的计算量大多是实数,需要处理小数,注意以下两点。

(1)实数的输入和输出。几何坐标值一般是实数,编程时用double类型,不用精度较低的float。double类型读入时用%lf格式,输出时用%f格式。

(2)实数的精度判断。实数做浮点数运算会产生精度误差,为了控制精度,可以设置一个偏差值eps(epsilon)。判断一个浮点数是否等于0,不能直接用“==0”来判断,而是用sgn()函数,判断是否小于eps。比较2个浮点数时,也不能直接用“==”判断是否相等,而是用dcmp()函数判断是否相等。eps要大于浮点运算结果的不确定量,一般取10-8。如果eps取10-10,可能会有问题。下面是代码。


const double pi = acos(-1.0);          //圆周率,精确到15位小数:3.141592653589793
const double eps = 1e-8;               //偏差值,有时用1e-10,但是要注意精度
int sgn(double x){                     //判断x的大小
if(fabs(x) < eps)  return 0;       //x==0,返回0
else return x<0?-1:1;              //x<0返回-1,x>0返回1
}
int dcmp(double x, double y){          //比较两个浮点数 
if(fabs(x - y) < eps)   return 0;  //x==y,返回0
else return x<y ?-1:1;             //x<y返回-1,x>y返回1
}

圆也是二维几何的内容,本文首先给出圆的基本定义和计算,然后介绍重要应用——最小圆覆盖。

01、圆基本的定义和计算

1. 圆的定义

用圆心和半径表示圆。


struct Circle{
    Point c;      //圆心
    double r;     //半径
    Circle(){}
    Circle(Point c,double r):c(c),r(r){}
    Circle(double x,double y,double _r){c=Point(x,y);r = _r;}
};

2. 点和圆的关系

根据点到圆心的距离判断点和圆的关系。

int Point_circle_relation(Point p, Circle C){
    double dst = Distance(p,C.c);
    if(sgn(dst – C.r) < 0) return 0;       //0 点在圆内
    if(sgn(dst – C.r) ==0) return 1;       //1 圆上
    return 2;                               //2 圆外
}

3. 直线和圆的关系

根据圆心到直线的距离判断直线和圆的关系。

int Line_circle_relation(Line v,Circle C){
    double dst = Dis_point_line(C.c,v);
    if(sgn(dst-C.r) < 0) return 0;     //0 直线和圆相交
    if(sgn(dst-C.r) ==0) return 1;     //1 直线和圆相切
    return 2;                               //2 直线在圆外
}

4. 线段和圆的关系

根据圆心到线段的距离判断线段和圆的关系。

int Seg_circle_relation(Segment v,Circle C){
    double dst = Dis_point_seg(C.c,v);
    if(sgn(dst-C.r) < 0) return 0;      //0线段在圆内
    if(sgn(dst-C.r) ==0) return 1;      //1线段和圆相切
    return 2;                           //2线段在圆外
}

5. 直线和圆的交点

求直线和圆的交点,如图1所示,先求圆心c在直线上的投影q,再求得距离d,然后根据r和d求出长度k,最后求出两个交点图片,其中n为直线的单位向量。

640.png


■ 图1 直线和圆的交点

//pa, pb是交点。返回值是交点个数
int Line_cross_circle(Line v,Circle C,Point &pa,Point &pb){
    if(Line_circle_relation(v, C)==2)  return 0;//无交点
  Point q = Point_line_proj(C.c,v);          //圆心在直线上的投影点
     double d = Dis_point_line(C.c,v);          //圆心到直线的距离
  double k = sqrt(C.r*C.r-d*d);    
    if(sgn(k) == 0){                            //1个交点,直线和圆相切
     pa = q;  pb = q;  return 1;
}
Point n=(v.p2-v.p1)/ Len(v.p2-v.p1);       //单位向量
pa = q + n*k;  pb = q - n*k;
return 2;                                  //2个交点
}

02、最小圆覆盖

最小圆覆盖问题:给定n个点的坐标,求一个半径最小的圆,把n个点全部包围,部分点在圆上。

常见的算法有两种:几何算法、模拟退火算法。本文只介绍几何算法,如果数据规模不大,还可以用模拟退火算法求解。

这个最小圆可以由n个点中的两个或3个点确定。两点定圆时,圆心是线段AB的中点,半径是AB长度的一半,其他点都在这个圆内。如果两点不足以包围所有点,就需要三点定圆,此时圆心是A、B、C这3个点组成的三角形的外心,如图2所示。

640.png


■ 图2 两点定圆和三点定圆


获得最小覆盖圆,就是寻找能两点定圆或三点定圆的那几个点。

一般用增量法求最小圆覆盖。算法从一个点开始,每次加入一个新的点,更新最小圆,直到扩展到全部n个点。设前i个点的最小覆盖圆为Ci,过程如下。

(1) 加入第1个点p1。C1的圆心就是p1,半径为0。

(2) 加入第2个点p2。新的C2的圆心是线段p1 p2的中心,半径为两点距离的一半。这一步操作是两点定圆。

(3) 加入第3个点p3。有两种情况:p3在C2的内部或圆周上,不影响原来的最小圆,忽略p3;p3在C2的外部,此时C2已不能覆盖所有3个点,需要更新。下面讨论p3在C2外部的情况。因为p3一定在新的C3上,现在的任务转换为在p1、p2中找一个点或两个点,与p3一起两点定圆或三点定圆。重新定圆的过程,相当于回到步骤(1),把p3作为第1个点加入,然后再加入p1、p2。

(4) 加第4个点p4。分析和步骤(3)类似,为加强理解,这里重复说明一次。如果p4在C3内部或圆周上,忽略它;如果在C3外部,那么需要求新的最小圆,此时p4肯定在新的C4的圆周上。任务转换为在p1、p2、p3中找一个点或两个点,与p4一起构成最小圆。先检查能不能找到一个点,用两点定圆;如果两点不够,就找到第3个点,用三点定圆。重新定圆的过程和前3步类似,即把p4作为第1个点加入,然后加入p1、p2、p3。

(5) 持续进行下去,直到加完所有点。

算法思路概括如下。

假设已经求得前i-1个点的Ci-1,现在加入第i个点,有以下两种情况:

(1) i在C i-1的内部或圆周上,忽略i;

(2) i在C i-1的外部,需要求新的C i。首先,i肯定在C i上,然后重新把前面的i-1个点依次加入,根据两点定圆或三点定圆,重新构造最小圆。

现在分析最小圆覆盖的几何算法的复杂度。例8.7给出了模板代码。其中,有3层for循环,看起来复杂度似乎是O(n3)。不过,如果点的分布是随机的,用概率进行分析可以得出,程序的复杂度接近O(n)。在代码中,用random_shuffle()函数进行随机打乱。例如,如果前两个点p1 和p2恰好就是最后的两点定圆,那么其他点都只需要检查一次是否在C2内就可以了,程序在第1层for循环就结束了。
● 例8.7最小圆覆盖(洛谷P1742)

问题描述: 输入n个点的坐标,求最小圆覆盖。

输入: 第1行输入一个整数n,表示点的个数。后面n行中,每行输入两个实数,表示点的坐标。

输出: 第1行输出一个实数,表示圆的半径。第2行输出两个实数,表示圆心的坐标。保留10位小数。

下面给出最小圆覆盖的几何代码。circle_center()函数求三角形abc的外接圆的圆心,min_cover_circle()函数返回最小覆盖圆的圆心c和半径r。


#include <bits/stdc++.h>
using namespace std;
#define eps 1e-8
const int N = 1e5+1;
int sgn(double x){
    if(fabs(x) < eps)  return 0;
    else return x<0?-1:1;
}
struct Point{ double x, y; };
double Distance(Point A, Point B){return hypot(A.x-B.x,A.y-B.y);}
Point circle_center(const Point a, const Point b, const Point c){
    Point center;
    double a1=b.x-a.x, b1=b.y-a.y, c1=(a1*a1+b1*b1)/2;
    double a2=c.x-a.x, b2=c.y-a.y, c2=(a2*a2+b2*b2)/2;
    double d =a1*b2-a2*b1;
    center.x =a.x+(c1*b2-c2*b1)/d;
    center.y =a.y+(a1*c2-a2*c1)/d;
    return center;
}
void min_cover_circle(Point *p, int n, Point &c, double &r){
    random_shuffle(p, p + n);             //随机函数,打乱所有点。这一步很重要
    c=p[0]; r=0;                          //从第1个点p0开始。圆心为p0,半径为0
    for(int i=1;i<n;i++)                  //扩展所有点
        if(sgn(Distance(p[i],c)-r)>0){    //点pi在圆外部
            c=p[i]; r=0;                  //重新设置圆心为pi,半径为0
            for(int j=0;j<i;j++)          //重新检查前面所有的点。
                if(sgn(Distance(p[j],c)-r)>0){   //两点定圆
                    c.x=(p[i].x + p[j].x)/2;
                    c.y=(p[i].y + p[j].y)/2;
                    r=Distance(p[j],c);
                    for(int k=0;k<j;k++)
                        if (sgn(Distance(p[k],c)-r)>0){   //两点不能定圆,就三点定圆
                            c=circle_center(p[i],p[j],p[k]);
                            r=Distance(p[i], c);
                        }
                }
        }
}
Point p[N];  
int main(){
    int n; cin >> n;
    for(int i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
    Point c; double r;                  //最小覆盖圆的圆心和半径
    min_cover_circle(p,n,c,r);
    printf("%.10f\n%.10f %.10f\n",r,c.x,c.y);
    return 0;
}
目录
相关文章
|
21天前
|
算法 测试技术 C#
【数学】【计算几何】1453. 圆形靶内的最大飞镖数量
【数学】【计算几何】1453. 圆形靶内的最大飞镖数量
|
5月前
|
机器学习/深度学习 算法 C#
C# | 凸包算法之Andrew‘s,获取围绕一组点的凸多边形的轮廓点
这篇关于凸包算法的文章,本文使用C#和Andrew’s算法来实现凸包算法。 首先消除两个最基本的问题: 什么是凸包呢? 凸包是一个包围一组点的凸多边形。凸多边形是指多边形中的每个内角都小于180度的多边形。 凸包算法有什么用呢? 凸包算法的作用是找到这个凸多边形,并且使用最少的点来绘制出它的轮廓。凸包算法在计算机图形学、计算几何和机器学习等领域中有着广泛的应用。
63 0
|
5月前
【每日一题Day247】LC1401圆和矩形是否有重叠 | 数学
【每日一题Day247】LC1401圆和矩形是否有重叠 | 数学
19 0
|
9月前
|
算法 图形学
计算机图形学 之 DDA直线算法(数值微分法)
计算机图形学 之 DDA直线算法(数值微分法)
212 0
|
11月前
|
机器学习/深度学习
Leecode 892. 三维形体的表面积
Leecode 892. 三维形体的表面积
39 0
|
12月前
|
机器学习/深度学习
排列组合、古典概型、几何概型与伯努利概型
排列组合、古典概型、几何概型与伯努利概型
|
前端开发 数据可视化 图形学
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
114 0
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
|
前端开发 数据可视化 API
【数学篇】05 # 如何用向量和坐标系描述点和线段?
【数学篇】05 # 如何用向量和坐标系描述点和线段?
148 0
【数学篇】05 # 如何用向量和坐标系描述点和线段?
|
Python
LeetCode每日一题——883. 三维形体投影面积
在 n x n 的网格 grid 中,我们放置了一些与 x,y,z 三轴对齐的 1 x 1 x 1 立方体。
96 0
LeetCode每日一题——883. 三维形体投影面积
|
算法 Windows 小程序