Skip to content

Polígonos

Edson Alves edited this page Dec 20, 2016 · 2 revisions

Polígonos são figuras planas delimitadas por caminhos fechados (o vértice de partida é o vértice de chegada), compostos por segmentos de retas que une pontos (vértices) consecutivos. Os segmentos que unem os vértices são denominados arestas.

A maior parte dos problemas de Geometria Computacional envolvem polígonos. Embora alguns polígonos especiais (triângulos, quadriláteros) já tenham sido expostos anteriormente, a abordagem desta seção é mais geral, e pode ser aplicada a qualquer polígono com qualquer número de vértices.

Representação de polígonos

A representação mais comum de um polígono é a listagem de seus vértices, sendo que as arestas ficam subentendidas (há sempre uma aresta unindo dois vértice consecutivos). Para facilitar a implementação de algumas rotinas, pode ser conveniente inserir, ao final da lista, o ponto de partida, mas é preciso tomar cuidado: ao fazer isso, o número de vértices do polígono passa a ser o número de elementos da lista subtraído de uma unidade.

// Definição da classe Point

using Polygon = vector<Point>;

Esta primeira implementação é a mais compacta possível, mas requer atenção a questão do número de vértices, conforme já comentado. A implementação abaixo é mais extensa, porém evita os problemas já mencionados.

// Definição da classe Point

class Polygon {
public:
    vector<Point> vertices;
    int n;

    // O parâmetro deve conter os n vértices do polígono
    Polygon(const vector<Point>& vs) : vertices(vs), n(vs.size())
    {
        vertices.push_back(vertices[0]);
    }
};

Importante notar que ambas implementações não checam a validade do polígono no que se refere ao número de vértices: um polígono deve ter, no mínimo, três vértices.

Perímetro e área

O perímetro de um polígono pode ser calculado diretamente a partir da representação proposta, pois consiste na medida do contorno do polígono, isto é, a soma dos comprimentos de cada aresta.

// Definição da classe Point

class Polygon {
public:
    // Membros e construtor

    double perimeter() const
    {
        double p = 0;

        for (int i = 0; i < n; ++i)
            p += vertices[i].distance(vertices[i + 1]);

        return p;
    }
};

Já a área de um polígono pode ser também determinada diretamente da representação dada. Ela corresponde a metade do valor absoluto do "determinante" abaixo (as aspas significam que a notação remete a um determinante, mas não é um determinante de fato, uma vez que a matriz não é quadrada).

Área de um polígono

// Definição da classe Point

class Polygon {
public:
    // Membros e construtor

    double area() const
    {
        double a = 0;

        for (int i = 0; i < n; ++i)
        {
            a += vertices[i].x * vertices[i+1].y;
            a -= vertices[i+1].x * vertices[i].y;
        }

        return 0.5 * fabs(a);
    }
};

Caso o polígono seja regular (isto é, todos os lados tenham a mesma medida), a área também pode ser computada através do conhecimento do número de lados n e um dos três valores abaixo:

  1. comprimento de um dos lados (s);
  2. apótema, ou raio do círculo inscrito (r);
  3. raio do círculo circunscrito (R);

Área do Polígono Regular

Polígonos côncavos e convexos

Um polígono é dito convexo se, para quaisquer dois pontos P e Q localizados no interior do polígono, o segmento de reta PQ não intercepta nenhuma das arestas do polígono. Caso contrário, o polígono é dito côncavo.

É possível determinar se um polígono é ou não convexo sem recorrer à busca completa (isto é, testar todos os possíveis pares de pontos interiores ao polígono): a rotina de orientação entre pontos e reta (discutida na seção Retas) pode ser utilizada para tal fim. Basta checar se, para quaisquer três pontos consecutivos do polígono, eles tem a mesma orientação (ou sempre a esquerda, ou sempre à direita).

// Definição da classe Point

// Implementação da função de orientação D

class Polygon {
public:
    // Membros e construtor

    bool is_convex() const
    {
        vector<Point> vs(vertices);

        if (n < 3)
            return false;

        vs.push_back(vs[1]);    // Temporário/inserção evitam um if no laço

        int orientation = 0, i;

        for (i = 0; i < n; ++i)
        {
            int d = D(vs[i], vs[i + 1], vs[i + 2]);

            if (d == 0)
                continue;

            orientation = d;
            break;
        }

        for (; i < n; ++i)
        {
            int d = D(vs[i], vs[i + 1], vs[i + 2]);

            if (d == -orientation)
                return false;
        }

        return orientation != 0;
    }
};

Relação entre pontos e polígonos

Para se verificar se um ponto P está localizado, ou não, no interior de um polígono, basta computar a soma dos ângulos formados por P e dois vértices do polígono (somando-se se o ponto está na mesma orientação do polígono, subtraíndo em caso contrário): se a soma totalizar 2PI, o ponto está no interior do polígono. Esta verificação vale tanto para polígonos convexos quanto côncavos.

// Definição da classe Point

// Definição de PI, da função equals() do discriminante D()

// Ângulo APB, em radianos
double angle(const Point& P, const Point& A, const Point& B)
{
    auto ux = P.x - A.x;
    auto uy = P.y - A.y;

    auto vx = P.x - B.x;
    auto vy = P.y - B.y;

    auto num = ux * vx + uy * vy;
    auto den = hypot(ux, uy) * hypot(vx, vy);

    // Caso especial: se den == 0, algum dos vetores é degenerado: os dois
    // pontos são iguais. Neste caso, o ângulo não está definido

    return acos(num / den);
}

class Polygon {
public:
    // Membros e construtores

    bool contains(const Point& P) const
    {
        if (n < 3)
            return false;

        auto sum = 0.0;

        for (int i = 0; i < n; ++i)
        {
            auto d = D(P, vertices[i], vertices[i + 1]);

            // Pontos sobre as arestas ou vértices são considerados interiores
            if (equals(d, 0))
                return true;

            auto a = angle(P, vertices[i], vertices[i + 1]);

            sum += d > 0 ? a : -a;
        }

        return equals(fabs(sum), 2*PI);
    }
};

Relação entre polígonos e retas

Dada uma reta r, que passa pelos pontos A e B, e um polígono convexo P, com n vértices, esta reta secciona o polígono em duas regiões, esquerda e direita, que podem ser ou uma vazias e outra contendo P integralmente, ou serem compostas de dois polígonos convexos P1 e P2, resultantes do corte de P por r.

A rotina cut_polygon(), apresentada abaixo e adaptada de Competitive Programming 3, retorna a região a esquerda do corte, considerando que P está descrito no sentido anti-horário.

// Definição da classe Point, da função equals() e do discriminante D()

// Interseção entre a reta AB e o segmento de reta PQ
Point intersection(const Point& P, const Point& Q, const Point& A, const Point& B)
{
    auto a = B.y - A.y;
    auto b = A.x - B.x;
    auto c = B.x * A.y - A.x * B.y;
    auto u = fabs(a * P.x + b * P.y + c);
    auto v = fabs(a * Q.x + b * Q.y + c);

    return Point((P.x * v + Q.x * u)/(u + v), (P.y * v + Q.y * u)/(u + v));
}

Polygon cut_polygon(const Polygon& P, const Point& A, const Point& B)
{
    vector<Point> points;

    for (int i = 0; i < P.n; ++i)
    {
        auto d1 = D(A, B, P.vertices[i]);
        auto d2 = D(A, B, P.vertices[i + 1]);

        // Vértice à esquerda da reta
        if (d1 > -EPS)
            points.push_back(P.vertices[i]);

        // A aresta cruza a reta
        if (d1 * d2 < -EPS)
            points.push_back(intersection(P.vertices[i], P.vertices[i+1], A, B));
    }

    return Polygon(points);
}

Relação entre polígonos e círculos

Um polígono regular (isto é, com lados de medidas iguais) de n lados possui um círculo circunscrito (cujos vértices do polígono pertencem ao círculo) e um círculo inscrito (cujos lados são tangentes ao círculo).

O raio R do círculo circunscrito é igual ao raio do polígono: a distância entre o seu centro e um de seus vértices. A área do polígono pode então ser computada a partir de R e de n, através da expressão dada abaixo.

Área do círculo circunscrito

// Definição da constante PI

// Área do polígono regular de n lados inscrito no círculo de raio R
double area(double R, int n)
{
    return 0.5*n*R*R*sin(2*PI/n);
}

O raio r do círculo inscrito pode ser determinado a partir da medida s de um dos lados do polígono regular, através da relação abaixo,

Raio do círculo inscrito

ou a partir do raio R do círculo circunscrito e n, pela relação dada a seguir.

Raio do círculo inscrito

Envoltório convexo

Dado um conjunto de pontos P, o envoltório convexo CH(P) de P (convex hull) é o menor polígono convexo tal que cada ponto de P ou pertence ao interior de CH(P) ou é um de seus vértices.

Existem vários algoritmos para se determinar o envoltório convexo, e como os vértices de CH(P) são pontos de P, a essência dos algoritmos é determinar, para cada ponto de P, se ele pertence ou não ao CH(P).

O algoritmo de Graham iniciamente ordena todos os n pontos de P de acordo com o ângulo que eles fazem com um ponto pivô fixado previamente. A escolha padrão para o pivô é o ponto de menor coordenada y e, caso exista mais de um ponto com coordenada y mínima, escolhe-se o de maior coordenada x dentre eles. Para simplificar o algoritmo, o pivô é movido para a primeira posição do vetor.

// Definição da classe Point

Point pivot(vector<Point>& P)
{
    size_t idx = 0;

    for (size_t i = 1; i < P.size(); ++i)
        if (P[i].y < P[idx].y or (equals(P[i].y, P[idx].y) and P[i].x > P[idx].x))
            idx = i;

    swap(P[0], P[idx]);

    return P[0];
}

Para realizar a ordenação, é preciso definir um operador booleano que receba dois pontos p e q e retorne verdadeiro se p antecede q de acordo com a ordenação proposta. Como é necessário o conhecimento do pivô para tal ordenação, há três possibilidades para a implementação deste operador:

  1. implementar o operator < da classe Point, tornando o pivô um membro da classe, para que o operador tenha acesso a ele;
  2. tornar o pivô uma variável global;
  3. usar uma função lambda no terceiro parâmetro da ordenação, capturando o pivô por referência ou cópia.

Abaixo segue um código que implementa a terceira estratégia.

// Definição da classe Point e do discriminante D()

// Definição da função pivot()

void sort_by_angle(vector<Point>& P)
{
    auto P0 = pivot(P);

    sort(P.begin() + 1, P.end(), [&](const Point& A, const Point& B)
        {
            // Corner case: pontos colineares. Escolhe-se o mais próximo do pivô
            if (equals(D(P0, A, B), 0))
                return A.distance(P0) < B.distance(P0);

            auto dx = A.x - P0.x;
            auto dy = A.y - P0.y;
            auto alfa = atan2(dy, dx);

            dx = B.x - P0.x;
            dy = B.y - P0.y;
            auto beta = atan2(dy, dx);

            return alfa < beta;
        }
    );
}

Com os pontos ordenados, o algoritmo procede da seguinte forma: ele empilha três pontos de P (inicialmente, os índices n -1, 0, 1) e mantem a invariante de que os três elementos do topo de pilha estão sempre em sentido anti-horário (D() > 0). Para cada um dos pontos de P, verifica-se se este ponto mantem o sentido anti-horário com os dois elementos do topo da pilha: se sim, o ponto é inserido na pilha e segue-se adiante; caso contrário, remove-se o topo da pilha e se verifica o invariante novamente. Como cada ponto é ou inserido ou removido uma única vez, este processo tem complexidade O(n), e o algoritmo como um todo tem complexidade O(nlog n), por conta da ordenação.

// Definição da classe Point e da função sort_by_angle()

Polygon convex_hull(const vector<Point>& points)
{
    vector<Point> P(points);
    auto n = P.size();

    // Corner case: com 3 vértices ou menos, P é o próprio convex hull
    if (n <= 3)
        return Polygon(P);

    sort_by_angle(P);

    vector<Point> s;
    s.push_back(P[n - 1]);
    s.push_back(P[0]);
    s.push_back(P[1]);

    size_t i = 2;

    while (i < n)
    {
        auto j = s.size() - 1;

        if (D(s[j - 1], s[j], P[i]) > 0)
            s.push_back(P[i++]);
        else
            s.pop_back();
    }

    if (s.front() == s.back())
        s.pop_back();

    return Polygon(s);
}

Outros algoritmos para o envoltório convexo são o Andrew's Monotone Chain Algorithm e o algoritmo Jarvis's March. Abaixo segue uma implementação da cadeia monótona. Os pontos devem ser ordenados pela menor coordenada x e, em caso de empate, pela menor coordenada y.

class Point {
public:
    ll x;
    ll y;

    Point(ll xv = 0, ll yv = 0) : x(xv), y(yv) {}

    bool operator<(const Point& P) const
    {
        return x == P.x ? y < P.y : x < P.x;
    }
};

ll D(const Point& P, const Point& Q, const Point& R)
{
    return (P.x * Q.y + P.y * R.x + Q.x * R.y) - (R.x * Q.y + R.y * P.x + Q.x * P.y);
}

vector<Point> monotone_chain_ch(vector<Point>& P)
{
    sort(P.begin(), P.end());

    vector<Point> L, U;

    for (auto p : P)
    {
        while (L.size() >= 2 and D(L[L.size() - 2], L[L.size() -1], p) < 0)
            L.pop_back();

        L.push_back(p);
    }

    reverse(P.begin(), P.end());

    for (auto p : P)
    {
        while (U.size() >= 2 and D(U[U.size() - 2], U[U.size() -1], p) < 0)
            U.pop_back();

        U.push_back(p);
    }

    L.pop_back();
    U.pop_back();

    L.reserve(L.size() + U.size());
    L.insert(L.end(), U.begin(), U.end()); 

    return L;
}

Exercícios

  1. Codeforces
    1. 1C - Ancient Berland Circus
  2. UVA
    1. 218 - Moth Eradication
    2. 478 - Points in Figures: Rectangles, Circles, Triangles
    3. 681 - Convex Hull Finding
    4. 1111 - Trash Removal
    5. 10432 - Polygon Inside A Circle
    6. 10451 - Ancient Village Sports
    7. 10652 - Board Wrapping
    8. 11265 - The Sultan's Problem
    9. 11626 - Convex Hull

Referências

HALIM, Steve; HALIM, Felix. Competitive Programming 3, Lulu, 2013.

Math Open Reference. Incircle of a Polygon. Acesso em 18/08/2016.

Mathwords. Area of a Regular Polygon. Acesso em 20/09/2016.

Wikipédia. Regular Polygon. Acesso em 18/08/2016.