Алгоритм подпрыгивающего пузырька для наименьшей охватывающей сферы

меня интересует Алгоритм Подпрыгивающего Пузыря чтобы найти приблизительную наименьшую охватывающую сферу для набора точек.

Я понимаю основную концепцию:"каждый раз, когда точка вне шара найдена, шар будет двигаться к нему и одновременно увеличивать радиус. Рост в каждом шаге конструирован так, что он не превысит оптимальный радиус, таким образом радиус будет получать ближе и ближе к оптимальный." enter image description here

Я не смог найти ничего более конкретного об этом месте в интернете. Как рассчитывается рост? Насколько далек сдвиг в сторону новой точки?

Я ищу либо пример реализации, либо достаточную информацию для реализации моего собственного решения.

3 ответов


Я понимаю, что этому сообщению год, но я реализовал алгоритм подпрыгивающего пузыря на C++ и подумал, что, возможно, некоторые люди найдут его полезным. Это основано на документе Тянь Бо, который я купил за 18 долларов.

BoundSphere calculateBoundSphere(vector<Vertex> vertices){
    Vector3D center = vertices[0].position;
    float radius = 0.0001f;
    Vector3D pos, diff;
    float len, alpha, alphaSq;
    vector<Vertex>::iterator it;

    for (int i = 0; i < 2; i++){
        for (it = vertices.begin(); it != vertices.end(); it++){
            pos = it->position;
            diff = pos - center;
            len = diff.length();
            if (len > radius){
                alpha = len / radius;
                alphaSq = alpha * alpha;
                radius = 0.5f * (alpha + 1 / alpha) * radius;
                center = 0.5f * ((1 + 1 / alphaSq) * center + (1 - 1 / alphaSq) * pos);
            }
        }
    }

    for (it = vertices.begin(); it != vertices.end(); it++){
        pos = it->position;
        diff = pos - center;
        len = diff.length();
        if (len > radius){
            radius = (radius + len) / 2.0f;
            center = center + ((len - radius) / len * diff);
        }
    }

    return BoundSphere(center, radius);
}

здесь - это реализация, которую вы просили. Для части анализа вы можете получить идею, читая следующее.

эти anaylsis предназначены для круга (2d), но расширение до сферы(3d) должно быть легким. Я уверен, что анализ поможет.

алгоритм O(n2)-time

  • нарисуйте круг в центре, c, такой, что точки данного множества лежат внутри круг. Ясно, что этот круг можно сделать меньше (или иначе мы есть решение.)
  • сделайте круг меньше, найдя точку, наиболее удаленную от центр circler, и рисовать новый круг с таким же центром и проходящей через точку A. Эти два шага меньше заключая круг. Причина того, что новый круг меньше, заключается в том, что этот новый круг по-прежнему содержит все точки данного множества, кроме теперь он проходит через самую дальнюю точку, x, вместо того, чтобы окружать ее.
  • если круг проходит через 2 или более точек, продолжайте к шагу 4. В противном случае сделайте круг меньше, перемещая центр в сторону точка A, пока окружность не соприкоснется с другой точкой B из набор.
  • на этом этапе мы круг, C, который проходит через два или более точки заданного множества. Если круг содержит интервал (точечный интервал) дуги больше половины окружности окружность, на которой нет точек, окружность может быть сделана меньший. Пусть D и E-точки На концах этой точки-свободные интервал. В то время как сохраняя D и E на границе круга, уменьшите диаметр окружности до тех пор, пока мы не получим случай (a) или случай (b).
    • Case (a) диаметр-это расстояние DE.
      Мы закончили!
    • Case (b) окружность C касается другой точки из множества, F.
      Проверьте, есть ли выходы без точечных дуговых интервалов длиной более половины окружности C.
      Если
      нет таких точечных интервалов дуги, выходящих тогда Мы готово!
      Иначе!--20--> Перейти к шагу 4. В этом случае три точки должны лежать на дуге длиной менее половины окружности. Мы повторяем шаг 4 на внешних двух из трех точек на дуге.

анализ

этот алгоритм является прямым, но дорого реализовать. Шаг 1, 2 и 3 требуют линейного времени в количестве точек в заданном наборе. На шаге 4 выше требуется линейное время, чтобы найти каждую новую точку F. Однако, находя пункт F не гарантирует прекращение работы алгоритма. Шаг 4 должен повторяться до тех пор, пока окружность не будет содержать интервала без точек, превышающего половину ее окружности. В худшем случае это требует (n-2) итераций шага 4, подразумевая, что общее время, проведенное на шаге 4, может быть порядка квадрата размера набора точек.

следовательно, этот алгоритм равен O (n2).


алгоритм O(n)

Нимрод Мегиддо предлагает алгоритм и он использует методы чернослива и поиска для линейного программирования, чтобы найти минимальный охватывающий круг за O (n) время.

чернослив-и-поиска

суть алгоритма Megiddo-это чернослив и поиск. В алгоритме чернослива и поиска на каждом шаге выполняется линейный объем работы для уменьшения размера ввода на постоянную долю f. Если это возможно, то общий объем проделанной работы сократится до O(n)*(1 + (1-f) + (1-f)2 + ...). В этой формуле бесконечный ряд является геометрическим и суммируется с постоянным значением, поэтому общее время работы равно O(n).

например, предположим, что, проверив наш набор из n входных элементов, мы можем отбросить 1/4 из них как не относящиеся к решению. Многократно применяя эту проверку к остальным элементам, мы можем уменьшить входные данные до размера, который тривиально решить, скажем, N≤3. Общее время, необходимое для достижения этого сокращения, будет пропорционально (n + 3n/4 + 9n/16 + ...). Легко показать, что эта серия приближается, и никогда не превышает, предел 4n. Таким образом, общее время работы пропорционально n, как требуется.

идея использования геометрических рядов для сокращения алгоритма до линейного времени предшествует работе Мегиддо; в частности, она ранее использовалась для разработки алгоритмов медианного поиска O(n). Однако он первым применил его к ряду фундаментальных задач вычислительной геометрии.

линейное время Megiddo Алгоритм

чтобы найти минимальный охватывающий круг (MEC) набора точек, алгоритм Мегиддо отбрасывает по крайней мере n/16 точек на каждой (линейно-временной) итерации. То есть, учитывая множество S из n точек, алгоритм идентифицирует n/16 точек, которые могут быть удалены из S, не влияя на MEC S. Эта процедура может быть повторно применена до тех пор, пока не будет достигнут некоторый тривиальный базовый случай (например, n=3), с общим временем работы, пропорциональным (n + 15n/16 + 225n/256 + ...) = 16n.

для того, чтобы найти N/16 точек для сброса, требуется много ума. Алгоритм интенсивно использует две подпрограммы:

  • медиана (S,>): принимает множество S элементов и метрику для сравнения их попарно, и возвращает медиану элементов.
  • MEC-center( S, L): принимает множество точек S и линию L и определяет, на какой стороне L лежит центр MEC S.

Как уже упоминалось выше, медиана() предшествует работе Мегиддо, тогда как алгоритм, описанный здесь как MEC-center (), был представлен как часть его статьи 1983 года. Детальное изучение этих процедур выходит за рамки этой схемы, но каждая из них использует чернослив и поиск для работы в линейном времени. Алгоритм, используемый MEC-center (), представляет собой упрощенную версию алгоритма в целом.

учитывая эти примитивы, алгоритм отбрасывания N / 16 входных точек выполняется как следует:

  • произвольно соедините n точек в S, чтобы получить n/2 пары.
  • построить линию биссектрисы для каждой пары точек, чтобы получить n / 2 всего биссектрисы.
  • вызовите median (), чтобы найти биссектрису с медианным наклоном. Назовем это наклоном mmid ни.
  • соедините каждый биссектрису наклона ≥ mmid с другим наклона
  • вызовите median (), чтобы найти точку в I с медиана y-значение. Назови это y-значение ymid.
  • вызовите MEC-center (), чтобы найти, какая сторона линии y=ymid MEC-center C находится на. (Без потери общности предположим, что это ложь. выше.)
  • пусть I ' - подмножество точек I, значения y которых меньше ymid. (I ' содержит n / 8 пунктов.)
  • найти линию L с наклоном mmid таким, чтобы половина точек в I ' лежала L слева, половина справа.
  • вызов MEC-center () на L. без потери общности, предположим, что C лежит на Правы л.
  • пусть I-подмножество I, точки которого лежат слева от L. (I" содержит n / 16 пунктов.)

теперь мы можем отбросить одну точку в S для каждой из точек пересечения n / 16 в I". Рассуждения таковы. После двух вызовов MEC-center () Мы обнаружили, что MEC-center C должен находиться выше ymid и справа от L, тогда как любая точка в I" находится ниже ymid и слева от L.

каждая точка в I " находится на Место встречи двух биссектрис. Один из этих биссектрис должен иметь наклон ≥ mmid и поэтому никогда не должен проходить через квадрант, где, как мы знаем, лежит C. Итак, мы знаем, на какой стороне в лежит Биссектриса В, поэтому из двух точек, чей биссектриса в, Пусть РС-та, которая лежит на той же стороне, что и С, а другая-РХ.

легко показать, что ПК должен быть ближе к C, чем PX. Из этого следует, что ПК не может лежать на минимальном замкнутом круге, и поэтому мы можем безопасно отбросить точку PC для каждой из точек пересечения n / 16 в I".

мы не обсуждали здесь, как этот алгоритм может быть сделан для работы с вырожденным входом (параллельные биссектрисы, коллинеарные точки и т. д.), Но оказывается, что мы получаем те же гарантии производительности для таких случаев. Дело в том, что для вырожденного ввода алгоритм способен отбросить более n/16 точек. Короче говоря, алгоритм Megiddo гарантирует сокращение по крайней мере n / 16 точек на каждой итерации независимых ввода.

таким образом, по аргументу, основанному на геометрическом ряду, алгоритм Мегиддо вычисляет минимальную окружность в линейном времени.

смотрите этой бумага для получения дополнительной информации об алгоритме O(n)


обновление: при ближайшем рассмотрении анимации и статьи Википедии я решил, что мой алгоритм не подпрыгивает пузырь. Из анимации ясно, что прыгающий пузырь перемещает ограничивающую сферу, в то время как мой алгоритм только увеличивает ее в одном направлении за раз. Кроме того, я понимаю, что прыгающий пузырь использует аппроксимацию и может не содержать всех точек; однако, кажется, есть параметр допуска, который вы можете контролировать.

Я думаю, что мой алгоритм по-прежнему имеет некоторые приятные свойства:

  1. Это O (n).
  2. это происходит постепенно.
  3. он гарантированно содержит все точки.
  4. это очень просто. Единственное, что проще было бы найти ограничивающую рамку, выровненную по оси, а затем поместить вокруг нее сферу, но это, безусловно, будет большим объемом, чем этот алгоритм.

Я придумал инкрементный алгоритм O(n) самостоятельно, который из описание в Википедии я считаю, что должен быть алгоритм подпрыгивающего пузыря. Я понятия не имею, насколько близко он подходит к минимально ограничивающему кругу. Сначала я попытаюсь объяснить причины, лежащие в основе алгоритма. Это может помочь нарисовать это на бумаге.

представьте, что у вас есть ограничивающий круг с некоторым центром C и радиусом r. Вы хотите расширить круг, чтобы содержать новую точку P. Вы вычисляете расстояние d от C to P и найти, что это больше, чем r, поэтому он должен быть вне круга. Теперь, если вы бросили Луч из P через C, он выходит из задней части круга на A.

теперь представьте, что вы закрепите круг на A а затем разверните его вдоль луча назад к P. Он будет продолжать содержать все предыдущие точки, и теперь он содержит P. Диаметр нового круга r + d, таким образом, его радиус составляет r ' = (r + d) / 2. Прогулка по лучу от P to A на расстоянии нового радиуса, и вы находитесь в центре нового круга.

вот эскиз алгоритма:

  1. инициализировать ограничивающую сферу с центром в первой точке C радиусом r на 0.
  2. для всех остальных точка P:
    1. если расстояние d С P to C is , пропустить его.
    2. если d > r:
      1. новый радиус r ' = (r + d) / 2.
      2. новый центр C ' = P + r '(C - P) / d.

и вот какой код Objective-C, который я написал только сегодня:

- (void)updateBoundingVolume {
    self.boundingSphereCenter = GLKVector3Make(0, 0, 0);
    self.boundingSphereRadius = 0;

    GLfloat* vertex = self.vertices;
    GLfloat* endV = vertex + 3 * self.vertNum;

    if (vertex < endV) {
        // initialize to zero radius sphere centered on the first point
        self.boundingSphereCenter = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
        vertex += 3;
    }

    while (vertex < endV) {
        GLKVector3 p = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
        vertex += 3;

        float d = GLKVector3Distance(self.boundingSphereCenter, p);
        if (d <= self.boundingSphereRadius) {
            // already inside the sphere
            continue;
        }

        // length of line from other side of sphere through the center to the new point
        // divide by 2 for radius
        self.boundingSphereRadius = (self.boundingSphereRadius + d) / 2;

        // unit vector from new point to old center
        GLKVector3 u = GLKVector3DivideScalar(GLKVector3Subtract(self.boundingSphereCenter, p), d);
        // step back from the new point by the new radius to get the new center
        self.boundingSphereCenter = GLKVector3Add(p, GLKVector3MultiplyScalar(u, self.boundingSphereRadius));
    }
}