#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <stdlib.h>
using namespace std;
#define eps 1e-6
const int N = 5e5+5;
int n;
double x[N],y[N],z[N];
struct POINT
{
double x,y;
};
POINT t1[N],t2[N],t3[N];
double getd(POINT a,POINT b) //求两个点之间的长度
{
return sqrt( (a.x-b.x)*(a.x-b.x) + (a.y-b.y) * (a.y-b.y) );
}
POINT getO(POINT p1,POINT p2,POINT p3) //已知三点求圆心
{
POINT ans;
double a = p2.x - p1.x;
double b = p2.y - p1.y;
double c = p3.x - p2.x;
double d = p3.y - p2.y;
double e = (p2.x * p2.x) + (p2.y * p2.y) - (p1.x * p1.x) - (p1.y * p1.y);
double f = (p3.x * p3.x) + (p3.y * p3.y) - (p2.x * p2.x) - (p2.y * p2.y);
ans.x = (f*b - e*d) / (c*b - a*d) /2.0;
ans.y = (a*f - e*c) / (a*d - b*c) /2.0;
return ans;
}
double R;
void mincir() //求最小圆
{
// O相当于圆心,R相当于半径
POINT O = t1[1]; //选择第一个点
R = 0;
for(int i=1;i<=n;i++) //点是一个一个增加的
{
if(getd(t1[i],O)-R > eps) //如果两点之间的距离大于R,则说明这个点在圆之外,就进入下一步,且i这个点一定在新圆的边界上
{
O = t1[i];
R = 0; //R变为零,接下来,任何一个点与O点之间的距离都大于0,所以一定进行下一步
for (int j=1;j<i;j++) //在[1,i)之间的点进行判断,看是否在范围内
{
if(getd(t1[j],O)-R > eps) //如果当前点不在范围内,找到第二个点在新圆的边界上
{
//O 变成两个边缘点的中点 , R变成以两个边缘点为直径的半径,即以这两个点为直径做圆
O = (POINT){(t1[i].x + t1[j].x)/2.0 , (t1[i].y + t1[j].y)/2.0};
R = getd(t1[i],t1[j])/2.0;
for (int k=1;k<j;k++)
{
if(getd(t1[k],O)-R >eps) //如果出现第三个点不在两个边缘点组成的圆上,直接构成新的圆,更新 新的半径。
{
O = getO(t1[i], t1[j], t1[k]);
R = getd(t1[i],O); //求出半径
}
}
}
}
}
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lf %lf %lf",&x[i],&y[i],&z[i]);
//t1表示xy面
t1[i].x=x[i];
t1[i].y=y[i];
}
random_shuffle(t1+1,t1+1+n); //将点打乱
mincir();
double ans = R;
for (int i=1;i<=n;i++)
{
//表示xz面
t1[i].x=x[i];
t1[i].y=z[i];
}
random_shuffle(t1+1,t1+1+n); //将点打乱
mincir();
ans = min(ans,R);
for (int i=1;i<=n;i++)
{
//t3表示yz面
t1[i].x=y[i];
t1[i].y=z[i];
}
random_shuffle(t1+1,t1+1+n);
mincir();
ans = min(ans , R);
printf("%.4lf\n",ans*2);
return 0;
}