Позволяет использовать C# в нашем примере.Учитывая только трехмерные точки, не будет ли этот наивный подход к «маленькому закрывающемуся шару» достаточно хорошим?
public class Sphere
{
public Point Center { get; set; }
public float Radius { get; set; }
public Sphere(IEnumerable<Point> points)
{
Point first = points.First();
Point vecMaxZ = first;
Point vecMinZ = first;
Point vecMaxY = first;
Point vecMinY = first;
Point vecMinX = first;
Point vecMaxX = first;
foreach (Point current in points)
{
if (current.X < vecMinX.X)
{
vecMinX = current;
}
if (current.X > vecMaxX.X)
{
vecMaxX = current;
}
if (current.Y < vecMinY.Y)
{
vecMinY = current;
}
if (current.Y > vecMaxY.Y)
{
vecMaxY = current;
}
if (current.Z < vecMinZ.Z)
{
vecMinZ = current;
}
if (current.Z > vecMaxZ.Z)
{
vecMaxZ = current;
}
}
//the lines bellow assure at least 2 points sit on the surface of the sphere.
//I'm pretty sure the algorithm is solid so far, unless I messed up the if/elses.
//I've been over this, looking at the variables and the if/elses and they all
//seem correct, but our own errors are the hardest to spot,
//so maybe there's something wrong here.
float diameterCandidateX = vecMinX.Distance(vecMaxX);
float diameterCandidateY = vecMinY.Distance(vecMaxY);
float diameterCandidateZ = vecMinZ.Distance(vecMaxZ);
Point c;
float r;
if (diameterCandidateX > diameterCandidateY)
{
if (diameterCandidateX > diameterCandidateZ)
{
c = vecMinX.Midpoint(vecMaxX);
r = diameterCandidateX/2f;
}
else
{
c = vecMinZ.Midpoint(vecMaxZ);
r = diameterCandidateZ/2f;
}
}
else if (diameterCandidateY > diameterCandidateZ)
{
c = vecMinY.Midpoint(vecMaxY);
r = diameterCandidateY/2f;
}
else
{
c = vecMinZ.Midpoint(vecMaxZ);
r = diameterCandidateZ/2f;
}
//the lines bellow look for points outside the sphere, and if one is found, then:
//1 let dist be the distance from the stray point to the current center
//2 let diff be the equal to dist - radius
//3 radius will then the increased by half of diff.
//4 a vector with the same direction as the stray point but with magnitude equal to diff is found
//5 the current center is moved by half the vector found in the step above.
//
//the stray point will now be included
//and, I would expect, the relationship between the center and other points will be mantained:
//if distance from p to center = r/k,
//then new distance from p to center' = r'/k,
//where k doesn't change from one equation to the other.
//this is where I'm wrong. I cannot figure out how to mantain this relationship.
//clearly, I'm moving the center by the wrong amount, and increasing the radius wrongly too.
//I've been over this problem for so much time, I cannot think outside the box.
//my whole world is the box. The box and I are one.
//maybe someone from outside my world (the box) could tell me where my math is wrong, please.
foreach (Point current in points)
{
float dist = current.Distance(c);
if (dist > r)
{
float diff = dist - r;
r += diff/2f;
float scaleFactor = diff/current.Length();
Point adjust = current * scaleFactor;
c += adjust/2f;
}
}
Center = c;
Radius = r;
}
public bool Contains(Point point) => Center.Distance(point) <= Radius;
public override string ToString() => $"Center: {Center}; Radius: {Radius}";
}
public class Point
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
public Point(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public float LengthSquared() => X * X + Y * Y + Z * Z;
public float Length() => (float) Math.Sqrt(X * X + Y * Y + Z * Z);
public float Distance(Point another)
{
return (float) Math.Sqrt(
(X - another.X) * (X - another.X)
+ (Y - another.Y) * (Y - another.Y)
+ (Z - another.Z) * (Z - another.Z));
}
public float DistanceSquared(Point another)
{
return (X - another.X) * (X - another.X)
+ (Y - another.Y) * (Y - another.Y)
+ (Z - another.Z) * (Z - another.Z);
}
public Point Perpendicular()
{
return new Point(-Y, X, Z);
}
public Point Midpoint(Point another)
{
return new Point(
(X + another.X)/2f,
(Y + another.Y)/2f,
(Z + another.Z)/2f);
}
public override string ToString() => $"({X}, {Y}, {Z})";
public static Point operator +(Point p1, Point p2)
{
return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
}
public static Point operator *(Point p1, float v)
{
return new Point(p1.X * v, p1.Y * v, p1.Z * v);
}
public static Point operator /(Point p1, float v)
{
return new Point(p1.X/v, p1.Y/v, p1.Z/v);
}
}
//Note: this class is here so I can be able to solve the problems suggested by
//Eric Lippert.
public class Line
{
private float coefficient;
private float constant;
public Line(Point p1, Point p2)
{
float deltaY = p2.Y - p1.Y;
float deltaX = p2.X - p1.X;
coefficient = deltaY/deltaX;
constant = coefficient * -p1.X + p1.Y;
}
public Point FromX(float x)
{
return new Point(x, x * coefficient + constant, 0);
}
public Point FromY(float y)
{
return new Point((y - constant)/coefficient, y, 0);
}
public Point Intersection(Line another)
{
float x = (another.constant - constant)/(coefficient - another.coefficient);
float y = FromX(x).Y;
return new Point(x, y, 0);
}
}
Могу ли я смело предположить, что это будет работать, по крайней мере так же быстро, как и фантазии алгоритмов, которые там обычно считают, ради робастности, возможности точек, имеющих любое число измерений, от 2 к чему-либо, как 1000 или 10000.
Мне это нужно только для 3-х измерений, никогда больше и не меньше. Поскольку у меня нет ученой степени по информатике (или в какой бы то ни было степени, я - второкурсник средней школы), мне сложно анализировать алгоритмы производительности и потребления ресурсов. Поэтому мой вопрос в основном заключается в следующем: «Является ли моя« самая маленькая замкнутая сфера для немых »альгоритмой хорошей в производительности и потреблении ресурсов по сравнению с причудливыми? Есть ли момент, когда мой алгоритм ломается, а профессиональные - нет, что означает, что он работает так плохо, что приведет к заметной потере (например, если у меня слишком много очков).
EDIT 1: Я редактировал код, потому что это не имело никакого смысла (я был голоден, было 4 вечера, и я не ел весь день). Думаю, это имеет смысл, но не уверен, что это правда. Первоначальный вопрос: если этот вопрос решает проблему, делает ли он это достаточно хорошо, чтобы конкурировать с профессиональными алгоритмами stardard в случае, если мы заранее знаем, что все точки имеют 3 измерения?
EDIT 2: Теперь я уверен, что производительность плохая, и я потерял всякую надежду реализовать наивный алгоритм, чтобы найти наименьшую охватывающую сферу. Я просто хочу что-то сделать. Проверьте последнее обновление.
EDIT 3: Не работает. С меня хватит.
EDIT 4: Наконец, после, я не знаю ... около 5 часов. Я понял. Иисус Христос. Это работает. Может ли кто-нибудь рассказать мне об эффективности? Это действительно плохо по сравнению с профессиональными алгоритмами? Какие строки я могу изменить, чтобы сделать их лучше? Есть ли момент, когда он ломается? Помните, я всегда буду использовать его для 3D-очков.
EDIT 5: Я узнал от Биченко, что предыдущий алгоритм все еще не работал. Я спал по этой проблеме, и это моя новая версия алгоритма. Я знаю, что это не работает, и у меня есть хорошая подсказка, где это неправильно, может ли кто-нибудь рассказать, почему эти конкретные вычисления ошибочны и как их исправить? Я склонен думать, что это имеет отношение к тригонометрии. Мои предположения не верны для евклидова пространства, потому что я не могу перестать видеть векторы как реальные числа вместо множеств реальных чисел, которые в моем случае я использую, чтобы указать точку в евклидовом пространстве. Я уверен, что я пропускаю синус или косинус где-то в последнем цикле (конечно, не совсем синус или косинус, но эквивалент в декартовых координатах, так как мы не знаем никаких углов.
Addendum РЕДАКТИРОВАТЬ 5: о проблемах, предложенных Эрик Липпертом: (1) Argh слишком тривиально: р (2) Я сделаю это для круга первого, я добавлю класс линию для этого
Point a, b, c; //they are not collinear
Point midOfAB = a.Midpoint(b);
Point midOfBC = b.Midpoint(c);
//multiplying the vector by a scalar as I do bellow doesn't matter right?
Point perpendicularToAB = midOfAB.Perpendicular() * 3;
Point perpendicularToBC = midOfBC.Perpendicular() * 3;
Line bisectorAB = new Line(perpendicularToAB, midOfAB);
Line bisectorBC = new Line(perpendicularToBC, midOfBC);
Point center = bisectorAB.Intersection(bisectorBC);
float distA = center.Distance(a);
float distB = center.Distance(b);
float distC = center.Distance(c);
if(distA == distB && distB == distC)
//it works (spoiler alert: it doesn't)
else
//you're a failure, programmer, pick up your skate and practice some ollies
Ваша четвертая программа явно неверна. Сожалею. Попробуйте 'var points = new [] {new Point (1, 0, 0), новая точка (2, 0, 0), новая точка (0,0,0)}; var s = новая сфера (точки); Console.WriteLine (s); результат: NaN, NaN, NaN, NaN. –
Ваша проблема здесь методологическая, а не математическая. Вы пишете программу, не имея прочной, проверенной математической модели и без каких-либо тестов. Не пишите программы, пишете что-то не так и возитесь; проектировать программу, чтобы быть четко правильной по дизайну и построить ее с использованием хороших инженерных принципов. ** Начните с написания тестовых примеров **. Десятки из них. Убедитесь, что вы знаете, каков должен быть вывод программы *, и вам будет намного легче узнать, когда у вас что-то не так. –
Кроме того, ваша идея попытаться решить проблему в случае меньшего размера из трех измерений является хорошей. Но, как показывает мой тестовый пример, ** вы даже не решили его правильно для коллинеарных точек **. Сначала возьмите ** одномерный случай. Тогда двумерный случай. Только тогда перейдем к 3-му корпусу. –