最小半径圆

题目描述:

平面上有n个点(0<=n<=1000),每个点用一对坐标(xy)表示。其中xy分别为点的x轴和y轴坐标。同时约定0<=x<=100000<=y<=10000,且xy为整数。

n个点的坐标给出后,试找出一个半径最小的圆,将n个点全部包围,点可以在圆周上。

输入格式:

第一行包含一个整数mm<=10)表示有m组测试数据。每组测试数据的第一行为一个整数n,表示当前这组测试数据包含n个点,接下来的n行中每行有两个点,表示点的坐标。

输出格式:

对于每组数据,在一行内输出三个数,分别表示圆心坐标和半径,精确到小数点后两位。

输入输出样例:

Sample input

Output for the input

3

3

0 0

1 0

0 1

4

0 0

0 1

1 1

1 0

4

0 0

2 0

4 0

2 2

0.50 0.50 0.71

0.50 0.50 0.71

2.00 0.00 2.00

 

解:

一个比较直接的想法就是枚举所有点的情况,这样就可以找到最小半径的外接圆。但是这样做的时间复杂度太高,我们需要寻求其它的方法:

给定点集A,记其最小外接圆为mincircleA),显然对A来说mincircleA)是存在且唯一的。还要注意一些特殊情况:当A为空集时,mincircleA)为空;当A中只有一个点时,mincircleA)的圆心就为该点,并且半径为0;当A中只有两个点时,mincircleA)的圆心为两点连线的中点,半径为两点距离的一半。

显然,mincircleA)可由A上最多三点确定,也就是说存在点集合B|B|<=3,且B包含于A,有mincircleA)=mincircleB)。所以如果点a不属于集合B,那么mincircleA{a})=mincircleB);如果mincircleA{a}<> mincircleB),那么点a一定属于集合B

所以我们从一个空集R开始,不断向里面添加点,并维护R的外接圆最小。这样就可以得到解决该题的算法。

 

代码:

 

#include < stdio.h >
#include
< math.h >
#include
< string .h >
#include
< stdlib.h >


#define     sqr(a)    ((a)*(a))
#define  MAX 1000
#define  EPS 1E-6


struct  TPoint{     //
     double  x,y;
}point[MAX];

struct  TCircle{     //
    TPoint centre;
    
double  r;
}circle;

struct  TTriangle{     // 三角形
    TPoint p0,p1,p2;
}circleedge;

int  casenum,pointnum;

inline 
double  
distance(TPoint p1,TPoint p2){    
// 计算两点间的距离
     return  sqrt(sqr(p1.x - p2.x) + sqr(p1.y - p2.y));
}

inline 
double
triangleArea(TTriangle t){    
// 计算三角形面积
     return  fabs(t.p0.x * t.p1.y + t.p1.x * t.p2.y + t.p2.x * t.p0.y  -  t.p1.x * t.p0.y - t.p2.x * t.p1.y - t.p0.x * t.p2.y) / 2 ;
}

inline TCircle
circumcircle(TTriangle t){    
// 计算三角形的外接圆
    TCircle temp;
    
double  a,b,c,c1,c2;
    
double  xa,ya,xb,yb,xc,yc;
    a
= distance(t.p0,t.p1);
    b
= distance(t.p1,t.p2);
    c
= distance(t.p2,t.p0);
    temp.r
= a * b * c / triangleArea(t) / 4 ;
    xa
= t.p0.x;    ya = t.p0.y;
    xb
= t.p1.x;    yb = t.p1.y;
    xc
= t.p2.x;    yc = t.p2.y;
    c1
= (sqr(xa) + sqr(ya) - sqr(xb) - sqr(yb)) / 2 ;
    c2
= (sqr(xa) + sqr(ya) - sqr(xc) - sqr(yc)) / 2 ;
    temp.centre.x
= (c1 * (ya - yc) - c2 * (ya - yb)) / ((xa - xb) * (ya - yc) - (xa - xc) * (ya - yb));
    temp.centre.y
= (c1 * (xa - xc) - c2 * (xa - xb)) / ((ya - yb) * (xa - xc) - (ya - yc) * (xa - xb));
    
return  temp;
}

inline 
bool
incircle(TPoint p,TCircle c){    
// 判断点p是否在圆c内,如果在圆内放回true否则放回false
     return  distance(p,c.centre) < c.r + EPS ?   true : false ;
}


inline TCircle
minCircle(
int  pcount,TTriangle ce){     // 计算最小外接圆
    TCircle temp;
    memset(
& temp, 0 , sizeof (temp));
    
switch (pcount){
    
case   0 :         // 没有任何点时半径等于负数
        temp.r =- 2 ;    
        
break ;
    
case   1 :         // 只有一个点时这个点就是圆心,半径等于0
        temp.centre = ce.p0;    
        
break ;
    
case   2 :         // 如果有两个点,圆心为两点连线的中点,半径为两点距离的一半
        temp.r = distance(ce.p0,ce.p1) / 2 ;    
        temp.centre.x
= (ce.p0.x + ce.p1.x) / 2 ;
        temp.centre.y
= (ce.p0.y + ce.p1.y) / 2 ;
        
break ;
    
default :     // 三个点的情况
        temp = circumcircle(ce);
        
break ;
    }
    
return  temp;
}


void  
caculate(
int  t, int  ecount,TTriangle ce){
    
int     i;
    circle
= minCircle(ecount,ce);
    
if (ecount == 3 return ;
    
for (i = 0 ;i < t;i ++ ){
        
// 当新的点到圆心的距离大于半径时,重新计算重新计算外接圆
         if (distance(point[i],circle.centre) > circle.r + EPS){    
            
switch (ecount){
            
case   0 : ce.p0 = point[i];     break ;
            
case   1 : ce.p1 = point[i];     break ;
            
case   2 :    ce.p2 = point[i];     break ;
            }
            caculate(t
- 1 ,ecount + 1 ,ce);
        }
    }
}


void
main(){

    
int  i;
    TTriangle ce;
    scanf(
" %d " , & casenum);
    
while (casenum -- ){
        scanf(
" %d " , & pointnum);
        
for (i = 0 ;i < pointnum;i ++ )
            scanf(
" %lf%lf " , & point[i].x, & point[i].y);
        caculate(pointnum,
0 ,ce);
        printf(
" %.2lf %.2lf %.2lf " ,circle.centre.x,circle.centre.y,circle.r);
    }
}


 

 

 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值