Выпуклая оболочка - алгоритм Джарвиса



Дано множество точек
Найти минимальный многоугольник (по периметру), содержащий все эти точки

    Очевидно, что искомый многоугольник будет являться выпуклым, и будет строиться из исходных точек.
    Идея алгоритма Джарвиса заключается в последовательном построении выпуклой оболочки. Сначала нам необходимо найти точку, которая должна быть в оболочке. Для этого можно взять самую нижнюю из самых левых точек. Далее будем каждый раз добавлять такую точку, чтобы угол образуемый следующей, текущей и предыдущей точками был максимален. Для этого совсем не обязательно находить сам угол. Мы можем использовать ориентированную площадь треугольника: пусть p0 - текущая точка, pn - уже выбранная следующая точка и pi - текущая точка в переборе точек, тогда если Sign Area Triangle (p0, pi, pn) > 0, то точка pi находится справа от вектора p0->pn и, значит, она составляет больший угол в выпуклой оболочке, поэтому мы ей дадим большее предпочтение, чем точке pn.
    Также нам необходимо рассмотреть вырожденный случай - все три точки (p0, pi, pn) лежат на одной прямой (ориентированная площадь треугольника равна 0). В таком случае нам нужно выбрать ту точку, которая расположена дальше от p0. Для этого тоже можно не использовать сложных вычислений, достаточно узнать, лежит ли pn в прямоугольнике с диагональю p0, pi (это делается простым сравнением координат точек). Если pn лежит в этом прямоугольнике, то pi лежит дальше от p0 и мы точке pi вследствии этого отдадим большее предпочтение, чем pn.

Сложность O(nk).
k - количество точек в оболочке
n - количество заданных точек


Листинг C++

void hull_jarvis (vector < point > p, vector < int > &ip)
{
    int n = p.size();
    int first, q, next, i;
    double sign;
    // находим самую нижнюю из самых левых точек
    first = 0;
    for (i = 1; i < n; ++ i)
        if (p[i].x < p[first].x || (p[i].x == p[first].x && p[i].y < p[first].y))
            first = i;
  
    q = first; // текущая точка
    // добавляем точки в оболочку
    do
    {
        ip.push_back(q);
        next = q;
        // ищем следующую точку
        for (i = n - 1; i >= 0; -- i)
            if (p[i].x != p[q].x || p[i].y != p[q].y)
            {
                sign = area_triangle (p[q], p[i], p[next]);

                if (next == q || sign > 0 || (sign == 0 && point_in_box (p[next], p[q], p[i])))
                    next = i;
            }
        q = next;
    }
    while (q != first);
}

21.06.2007, 15:59

По всем вопросам обращаться: rumterg@gmail.com