Algoritmo eficiente para obtener puntos en un círculo alrededor de un centro


8

Problema

Quiero obtener todos los píxeles que están dentro de un círculo de un radio dado sobre un punto dado, donde los puntos solo pueden tener coordenadas enteras , es decir, píxeles en un lienzo.

Ilustración

Así que quiero obtener todos los puntos en el área amarilla dada (x, y)y r.

Enfoques

La forma más eficiente que se me ocurre es recorrer un cuadrado (x, y)y verificar la distancia euclidiana para cada punto:

for (int px = x - r; px <= x + r; px++) {
  for (int py = y - r; py <= y + r; py++) {
    int dx = x - px, dy = y - py;

    if (dx * dx + dy * dy <= r * r) {
      // Point is part of the circle.
    }
  }
}

Sin embargo, esto significa que este algoritmo verificará los (r * 2)^2 * (4 - pi) / 4píxeles que no son parte del círculo. dx * dx + dy * dy <= r * r, que parece bastante caro, se llama redundantemente casi todo 1 / 4el tiempo.

Integrar algo como lo que se propuso aquí podría mejorar el rendimiento:

for (int px = x - r; px <= x + r; px++) {
  for (int py = y - r; py <= y + r; py++) {
    int dx = abs(x - px), dy = abs(y - py);

    if (dx + dy <= r || (!(dx > r || dy > r) && (dx * dx + dy * dy <= r * r))) {
      // Point is part of the circle.
    }
  }
}

Sin embargo, como el propio autor señaló, es probable que esto no sea más rápido cuando la mayoría de los puntos estarán dentro del círculo (especialmente debido a abs), que pi / 4son en este caso.


No pude encontrar ningún recurso sobre esta pregunta. Estoy buscando específicamente una solución en C ++ y no algo en SQL .


1
La multiplicación y suma de enteros es bastante rápida. No estoy seguro de que agregar un montón de comparaciones adicionales ayudará, especialmente debido a todos los posibles errores de predicción de ramificaciones que podría introducir.
François Andrieux

Las preguntas que hacen la pregunta "La mayoría" o "La mejor" generalmente no pueden ser respondidas definitivamente porque rara vez hay una sola mejor respuesta. La mejor solución puede variar según el caso de uso real y la arquitectura en la que se ejecutará.
François Andrieux

3
Aproveche el hecho de que conocer el rango de valores de x para un valor de y dado le da una idea de los rangos de valores de x para los valores de y anteriores y siguientes.
Scott Hunter

1
Al menos deberías poder salirte del bucle una vez que te muevas del punto anterior siendo bueno al siguiente punto malo. En ese momento, sabes que has cruzado el límite y no quedan más puntos buenos en esa línea.
NathanOliver

1
Otra opción, para hacer el ejemplo de Scotts, comienza en el medio superior o inferior, y luego gira hacia la izquierda y hacia la derecha hasta llegar al límite. Esto significará que sus bucles internos se detendrán tan pronto como pase el límite.
NathanOliver

Respuestas:


4

Bien, aquí están los puntos de referencia que prometí.

Preparar

Utilicé google benchmark y la tarea consistía en insertar todos los puntos dentro del perímetro del círculo en a std::vector<point>. Comparo un conjunto de radios y un centro constante:

radii = {10, 20, 50, 100, 200, 500, 1000}
center = {100, 500}
  • lenguaje: C ++ 17
  • compilador: msvc 19.24.28316 x64
  • plataforma: windows 10
  • optimización: O2 (optimización completa)
  • roscado: ejecución de un solo subproceso

La exactitud de los resultados de cada algoritmo se prueba (en comparación con la salida del algoritmo OP).

Hasta ahora, los siguientes algoritmos están comparados:

  1. Algoritmo de OP enclosing_square.
  2. Mi algoritmo containing_square .
  3. Algoritmo de creativecreatorormaybenot edge_walking .
  4. Algoritmo de Mandy007 binary_search .

Resultados

Run on (12 X 3400 MHz CPU s)
CPU Caches:
  L1 Data 32K (x6)
  L1 Instruction 32K (x6)
  L2 Unified 262K (x6)
  L3 Unified 15728K (x1)
-----------------------------------------------------------------------------
Benchmark                                   Time             CPU   Iterations
-----------------------------------------------------------------------------
binary_search/10/manual_time              804 ns         3692 ns       888722
binary_search/20/manual_time             2794 ns        16665 ns       229705
binary_search/50/manual_time            16562 ns       105676 ns        42583
binary_search/100/manual_time           66130 ns       478029 ns        10525
binary_search/200/manual_time          389964 ns      2261971 ns         1796
binary_search/500/manual_time         2286526 ns     15573432 ns          303
binary_search/1000/manual_time        9141874 ns     68384740 ns           77
edge_walking/10/manual_time               703 ns         5492 ns       998536
edge_walking/20/manual_time              2571 ns        49807 ns       263515
edge_walking/50/manual_time             15533 ns       408855 ns        45019
edge_walking/100/manual_time            64500 ns      1794889 ns        10899
edge_walking/200/manual_time           389960 ns      7970151 ns         1784
edge_walking/500/manual_time          2286964 ns     55194805 ns          308
edge_walking/1000/manual_time         9009054 ns    234575321 ns           78
containing_square/10/manual_time          629 ns         4942 ns      1109820
containing_square/20/manual_time         2485 ns        40827 ns       282058
containing_square/50/manual_time        15089 ns       361010 ns        46311
containing_square/100/manual_time       62825 ns      1565343 ns        10990
containing_square/200/manual_time      381614 ns      6788676 ns         1839
containing_square/500/manual_time     2276318 ns     45973558 ns          312
containing_square/1000/manual_time    8886649 ns    196004747 ns           79
enclosing_square/10/manual_time          1056 ns         4045 ns       660499
enclosing_square/20/manual_time          3389 ns        17307 ns       206739
enclosing_square/50/manual_time         18861 ns       106184 ns        37082
enclosing_square/100/manual_time        76254 ns       483317 ns         9246
enclosing_square/200/manual_time       421856 ns      2295571 ns         1654
enclosing_square/500/manual_time      2474404 ns     15625000 ns          284
enclosing_square/1000/manual_time     9728718 ns     68576389 ns           72

Código

El código de prueba completo está abajo, puede copiarlo y pegarlo y probarlo usted mismo. fill_circle.cppcontiene la implementación de los diferentes algoritmos.

main.cpp

#include <string>
#include <unordered_map>
#include <chrono>

#include <benchmark/benchmark.h>

#include "fill_circle.hpp"

using namespace std::string_literals;

std::unordered_map<const char*, circle_fill_func> bench_tests =
{
    {"enclosing_square", enclosing_square},
    {"containing_square", containing_square},
    {"edge_walking", edge_walking},
    {"binary_search", binary_search},
};

std::vector<int> bench_radii = {10, 20, 50, 100, 200, 500, 1000};

void postprocess(std::vector<point>& points)
{
    std::sort(points.begin(), points.end());
    //points.erase(std::unique(points.begin(), points.end()), points.end());
}

std::vector<point> prepare(int radius)
{
    std::vector<point> vec;
    vec.reserve(10ull * radius * radius);
    return vec;
}

void bm_run(benchmark::State& state, circle_fill_func target, int radius)
{
    using namespace std::chrono;
    constexpr point center = {100, 500};

    auto expected_points = prepare(radius);
    enclosing_square(center, radius, expected_points);
    postprocess(expected_points);

    for (auto _ : state)
    {
        auto points = prepare(radius);

        auto start = high_resolution_clock::now();
        target(center, radius, points);
        auto stop = high_resolution_clock::now();

        postprocess(points);
        if (expected_points != points)
        {
            auto text = "Computation result incorrect. Expected size: " + std::to_string(expected_points.size()) + ". Actual size: " + std::to_string(points.size()) + ".";
            state.SkipWithError(text.c_str());
            break;
        }

        state.SetIterationTime(duration<double>(stop - start).count());
    }
}

int main(int argc, char** argv)
{
    for (auto [name, target] : bench_tests)
        for (int radius : bench_radii)
            benchmark::RegisterBenchmark(name, bm_run, target, radius)->Arg(radius)->UseManualTime();

    benchmark::Initialize(&argc, argv);
    if (benchmark::ReportUnrecognizedArguments(argc, argv))
        return 1;
    benchmark::RunSpecifiedBenchmarks();
}

fill_circle.hpp

#pragma once

#include <vector>

struct point
{
    int x = 0;
    int y = 0;
};

constexpr bool operator<(point const& lhs, point const& rhs) noexcept
{
    return lhs.x != rhs.x
               ? lhs.x < rhs.x
               : lhs.y < rhs.y;
}

constexpr bool operator==(point const& lhs, point const& rhs) noexcept
{
    return lhs.x == rhs.x && lhs.y == rhs.y;
}

using circle_fill_func = void(*)(point const& center, int radius, std::vector<point>& points);

void enclosing_square(point const& center, int radius, std::vector<point>& points);
void containing_square(point const& center, int radius, std::vector<point>& points);
void edge_walking(point const& center, int radius, std::vector<point>& points);
void binary_search(point const& center, int radius, std::vector<point>& points);

fill_circle.cpp

#include "fill_circle.hpp"

constexpr double sqrt2 = 1.41421356237309504880168;
constexpr double pi = 3.141592653589793238462643;

void enclosing_square(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;

    for (int px = center.x - radius; px <= center.x + radius; px++)
    {
        for (int py = center.y - radius; py <= center.y + radius; py++)
        {
            int dx = center.x - px, dy = center.y - py;
            if (dx * dx + dy * dy <= sqr_rad)
                points.push_back({px, py});
        }
    }
}

void containing_square(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;
    int half_side_len = radius / sqrt2;
    int sq_x_end = center.x + half_side_len;
    int sq_y_end = center.y + half_side_len;

    // handle inner square
    for (int x = center.x - half_side_len; x <= sq_x_end; x++)
        for (int y = center.y - half_side_len; y <= sq_y_end; y++)
            points.push_back({x, y});

    // probe the rest
    int x = 0;
    for (int y = radius; y > half_side_len; y--)
    {
        int x_line1 = center.x - y;
        int x_line2 = center.x + y;
        int y_line1 = center.y - y;
        int y_line2 = center.y + y;

        while (x * x + y * y <= sqr_rad)
            x++;

        for (int i = 1 - x; i < x; i++)
        {
            points.push_back({x_line1, center.y + i});
            points.push_back({x_line2, center.y + i});
            points.push_back({center.x + i, y_line1});
            points.push_back({center.x + i, y_line2});
        }
    }
}

void edge_walking(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;
    int mdx = radius;

    for (int dy = 0; dy <= radius; dy++)
    {
        for (int dx = mdx; dx >= 0; dx--)
        {
            if (dx * dx + dy * dy > sqr_rad)
                continue;

            for (int px = center.x - dx; px <= center.x + dx; px++)
            {
                for (int py = center.y - dy; py <= center.y + dy; py += 2 * dy)
                {
                    points.push_back({px, py});
                    if (dy == 0)
                        break;
                }
            }

            mdx = dx;
            break;
        }
    }
}

void binary_search(point const& center, int radius, std::vector<point>& points)
{
    constexpr auto search = []( const int &radius, const int &squad_radius, int dx, const int &y)
    {
        int l = y, r = y + radius, distance;

        while (l < r)
        {
            int m = l + (r - l) / 2;
            distance = dx * dx + (y - m) * (y - m);
            if (distance > squad_radius)
                r = m - 1;
            else if (distance < squad_radius)
                l = m + 1;
            else
                r = m;
        }

        if (dx * dx + (y - l) * (y - l) > squad_radius)
            --l;

        return l;
    };

    int squad_radius = radius * radius;    
    for (int px = center.x - radius; px <= center.x + radius; ++px)
    {
        int upper_limit = search(radius, squad_radius, px - center.x, center.y);
        for (int py = 2*center.y - upper_limit; py <= upper_limit; ++py)
        {
            points.push_back({px, py});
        }
    }
}

2
@creativecreatorormaybenot np. Edite esta respuesta si ajusta los algoritmos para que podamos actualizar los resultados aquí.
Timo

1
@ Mandy007 He actualizado los puntos de referencia.
Timo

2
for (line = 1; line <= r; line++) {
   dx = (int) sqrt(r * r - line * line);
   for (ix = 1; ix <= dx; ix++) {
       putpixel(x - ix, y + line)
       putpixel(x + ix, y + line)
       putpixel(x - ix, y - line)
       putpixel(x + ix, y - line)
   } 
}

Para evitar la generación repetida de píxeles en los ejes, vale la pena iniciar bucles desde 1 y dibujar líneas centrales (ix == 0 o line == 0) en un bucle separado.

Tenga en cuenta que también existe un algoritmo de Bresenham entero puro para generar puntos de circunferencia.


No hay una ganancia real de usar simetría aquí. Cambié el código para generar un cuarto de círculo.
MBo

2

Muy bien, primero que nada calculamos el cuadrado interno del círculo. La fórmula para ello es sencilla:

x² + y² = r²    // circle formula
2h² = r²        // all sides of square are of equal length so x == y, lets define h := x
h = r / sqrt(2) // half side length of the inner square

Ahora, cada punto entre (-h, -h)y se (+h, +h)encuentra dentro del círculo. Aquí hay una imagen de lo que quiero decir:

1

La parte azul restante es un poco complicada, pero tampoco demasiado complicada. Comenzamos en la parte superior del círculo azul (x = 0, y = -radius). A continuación, caminamos a la derecha ( x++) hasta que dejamos el perímetro del círculo (hasta que x²+y² < r²ya no se mantenga). Todo entre (0, y) y (x, y) está dentro del círculo. Debido a la simetría podemos extender este 8 veces

  • (-x, -y), (+ x, -y)
  • (-x, + y), (+ x, + y)
  • (-y, -x), (-y, + x)
  • (+ y, -x), (+ y, + x)

ahora bajamos 1 línea ( y--) y repetimos los pasos anteriores (manteniendo el valor más reciente de x). Agregue el centro del círculo a cada uno de los puntos y listo.

Aquí hay una visualización. Hay algunos artefactos debido a la ampliación. El punto rojo muestra lo que estamos probando en cada iteración:

1

Aquí está el código completo (usando opencv para dibujar las cosas):

#include <opencv2/opencv.hpp>

constexpr double sqrt2 = 1.41421356237309504880168;

int main()
{
    cv::Point center(200, 200);
    constexpr int radius = 180;

    // create test image
    cv::Mat img(400, 400, CV_8UC3);
    cv::circle(img, center, radius, {180, 0, 0}, cv::FILLED);
    cv::imshow("img", img);
    cv::waitKey();

    // calculate inner rectangle
    int halfSideLen = radius / sqrt2;
    cv::Rect innerRect(center.x - halfSideLen, center.y - halfSideLen, halfSideLen * 2, halfSideLen * 2);
    cv::rectangle(img, innerRect, {0, 180, 0}, cv::FILLED);
    cv::imshow("img", img);
    cv::waitKey();

    // probe the rest
    int x = 0;
    for (int y = radius; y >= halfSideLen; y--)
    {
        for (; x * x + y * y < radius * radius; x++)
        {
            // anything between the following points lies within the circle
            // each pair of points represents a line
            // (-x, -y), (+x, -y)
            // (-x, +y), (+x, +y)
            // (-y, -x), (-y, +x)
            // (+y, -x), (+y, +x)

            // center + {(-X..X) x (-Y..Y)} is inside the circle
            cv::line(img, cv::Point(center.x - x, center.y - y), cv::Point(center.x + x, center.y - y), {180, 180, 0});
            cv::line(img, cv::Point(center.x - x, center.y + y), cv::Point(center.x + x, center.y + y), {180, 180, 0});
            cv::line(img, cv::Point(center.x - y, center.y - x), cv::Point(center.x - y, center.y + x), {180, 180, 0});
            cv::line(img, cv::Point(center.x + y, center.y - x), cv::Point(center.x + y, center.y + x), {180, 180, 0});

            cv::imshow("img", img);
            cv::waitKey(20);
        }
    }

    cv::waitKey();
    return 0;
}

@creativecreatorormaybenot Creo que mi versión debería ser más rápida para cualquier cosa, excepto imágenes muy pequeñas porque el cálculo del cuadrado en el centro es tiempo constante (sin ninguna función trigonométrica, solo usamos números básicos). Esto también significa que no tiene que realizar ningún cheque dentro del cuadrado. Para el resto de la imagen, los algoritmos son básicamente los mismos.
Timo

1
Acabo de notar eso también. Esto podría reducirse a micro optimizaciones a nivel de ensamblador. Quizás haga un punto de referencia más tarde.
Timo

2

Esta es una optimización que reduce 1/4 de la dimensión de búsqueda:

for (int px = x; px <= x + r; ++px) {
  bool find = false;
  int dx = x - px, dy;
  for (int py = y; !find && py <= y + r; ++py) {
    dy = y - py;
    if (dx * dx + dy * dy <= r * r)) {
      /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
       & (x+x-px+r, y+y-py+r) are part of the circle.*/
    }else{
      find = true; //Avoid increasing on the axis y
    }
  }
}

o mejor, mejorando el rendimiento la iteración del segundo círculo for evitando el ifcondicional

for (int px = x; px <= x + r; ++px) {
  int dx = x - px, py = y;
  for (; dx * dx + (py-y) * (py-y) <= r * r; ++py) {
    /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
     & (x+x-px+r, y+y-py+r) are part of the circle.*/
  }
}

Bueno, creo que otra opción es una búsqueda binaria para el límite superior:

int binarySearch(int R, int dx, int y){
  int l=y, r=y+R;
  while (l < r) { 
    int m = l + (r - l) / 2;  
    if(dx*dx + (y - m)*(y - m) > R*R) r = m - 1; 
    else if(dx*dx + (y - m)*(y - m) < R*R) l = m + 1; 
    else r = m;
  }
  if(dx*dx + (y - l)*(y - l) > R*R) --l;
  return l;
}

for (int px = x; px <= x + r; ++px) {
  int upperLimit = binarySearch(r, px-x, y);
  for (int py = y; py <= upperLimit; ++py) {
    /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
     & (x+x-px+r, y+y-py+r) are part of the circle.*/
  }
}

La idea de la búsqueda binaria es encontrar el límite superior de manera óptima, evitando la ifcondición y los cálculos dentro del forciclo. Para esto, se verifica cuál es el número entero más grande que hace la distancia entre el punto actual y el radio dentro del círculo.

PD: Perdón, mi inglés.


2

Código

Basado en la idea de @ScottHunter , se me ocurrió el siguiente algoritmo:

#include <functional>

// Executes point_callback for every point that is part of the circle
// defined by the center (x, y) and radius r.
void walk_circle(int x, int y, int r,
                 std::function<void(int x, int y)> point_callback) {
  for (int px = x - r; px < x + r; px++)
    point_callback(px, y);
  int mdx = r;
  for (int dy = 1; dy <= r; dy++)
    for (int dx = mdx; dx >= 0; dx--) {
      if (dx * dx + dy * dy > r * r)
        continue;
      for (int px = x - dx; px <= x + dx; px++) {
        point_callback(px, y + dy);
        point_callback(px, y - dy);
      }
      mdx = dx;
      break;
    }
}

Algoritmo explicado

Este algoritmo realiza una cantidad minuto de comprobaciones. Específicamente, solo verifica en cada fila hasta que se alcanza el primer punto que es parte del círculo. Además, saltará puntos a la izquierda del punto previamente identificado en la siguiente fila. Además, al usar la simetría, solo n/2 + 1/2se verifica la mitad de las filas ( como comenzamos en 0).

Visualización

Esta es una visualización del algoritmo que creé. El contorno rojo indica el cuadrado que previamente se habría verificado y los píxeles negros indican el círculo real (con el píxel rojo en el centro como centro). El algoritmo verifica los puntos (marcados en azul) y recorre los puntos válidos (marcados en verde).
Como puede ver, el número de píxeles azules al final es minuto, es decir, solo hay unos pocos puntos en bucle que no forman parte del círculo. Además, tenga en cuenta que solo el primer píxel verde cada vez necesita una verificación, los otros solo se repiten, por lo que aparecen instantáneamente.

Notas

Los ejes podrían invertirse fácilmente, obviamente.

Esto podría optimizarse aprovechando aún más la simetría, es decir, que las filas serán las mismas que las columnas (pasar por todas las filas es lo mismo que pasar por todas las columnas, de izquierda a derecha, de arriba a abajo, viceversa, tornillo de banco) y bajar solo una cuarta parte de las filas desde el centro sería suficiente para determinar exactamente qué puntos van a formar parte del círculo. Sin embargo, creo que el pequeño aumento de rendimiento que esto va a dar no vale el código adicional.
Si alguien quiere codificarlo, proponga una edición para esta respuesta.

Código con comentarios

#include <functional>

// Executes point_callback for every point that is part of the circle
// defined by the center (x, y) and radius r.
void walk_circle(int x, int y, int r,
                 std::function<void(int x, int y)> point_callback) {
  // Walk through the whole center line as it will always be completely
  // part of the circle.
  for (int px = x - r; px < x + r; px++)
    point_callback(px, y);
  // Define a maximum delta x that shrinks whith every row as the arc
  // is closing.
  int mdx = r;
  // Start directly below the center row to make use of symmetry.
  for (int dy = 1; dy <= r; dy++)
    for (int dx = mdx; dx >= 0; dx--) {
      // Check if the point is part of the circle using Euclidean distance.
      if (dx * dx + dy * dy > r * r)
        continue;

      // If a point in a row left to the center is part of the circle,
      // all points to the right of it until the center are going to be
      // part of the circle as well.
      // Then, we can use horizontal symmetry to move the same distance
      // to the right from the center.
      for (int px = x - dx; px <= x + dx; px++) {
        // Use y - dy and y + dy thanks to vertical symmetry
        point_callback(px, y + dy);
        point_callback(px, y - dy);
      }

      // The next row will never have a point in the circle further left.
      mdx = dx;
      break;
    }
}

JS es probablemente lo suficientemente rápido como para "fuerza bruta" calcular las coordenadas de píxeles sobre la marcha para cada círculo (tal vez no se necesita optimización). Pero, ¿probará solo 1 radio o solo unos pocos radios? En caso afirmativo, puede calcular previamente los puntos internos de un círculo en una matriz y reutilizar esos puntos calculados, desplazados al origen del círculo que está probando.
markE

1
Puedes ver la última actualización de mi código, creo que es mejor
Mandy007

2

El problema tiene una complejidad fija de O (n ^ 2) donde n es el radio del círculo. La misma complejidad que un cuadrado o cualquier forma 2D normal

No se puede pasar por alto el hecho de que no puede reducir la cantidad de píxeles en un círculo, incluso si aprovecha la simetría, la complejidad sigue siendo la misma.

Ignorando la complejidad y buscando la optimización.

En su pregunta, afirma que abses un poco demasiado caro por píxel (o cuarto píxel)

Una vez por fila es mejor que una vez por píxel

Puede reducirlo a 1 raíz cuadrada por fila. Para un radio de círculo 256 que 128 raíces cuadradas

void circle(int x, int y, int radius) {
    int y1 = y, y2 = y + 1, r = 0, rSqr = radius * radius;
    while (r < radius) {
        int x1 = x, x2 = x + 1, right = x + sqrt(rSqr - r * r) + 1.5;;
        while (x2 < right) {
            pixel(x1, y1);
            pixel(x2, y1);
            pixel(x1--, y2);
            pixel(x2++, y2);
        }
        y1--;
        y2++;
        r++;
    }
}

Para sacarle más provecho, puede crear una tabla de búsqueda para los cálculos de raíz sqrt.

Todo entero

Alternativamente, puede usar la variación en la línea de bresenham que reemplaza la raíz cuadrada con todas las matemáticas enteras. Sin embargo, es un desastre y no sería beneficioso a menos que el dispositivo no tenga una unidad de coma flotante.

void circle(int x, int y, int radius) {
    int l, yy = 0, xx = radius - 1, dx = 1, dy = 1;
    int err = dx - (radius << 1);
    int l2 = x, y0 = y, r2 = x + 1;
    int l1 = x - xx, r1 = r2 + xx;
    int y2 = y0 - xx, y1 = y0 + 1, y3 = y1 + xx;
    while (xx >= yy) {
        l = l1;
        while (l < r1) {
            pixel(l, y1);
            pixel(l++, y0);
        }
        l = l2;
        while (l < r2) {
            pixel(l, y3);
            pixel(l++, y2);
        }
        err += dy;
        dy += 2;
        y0--;
        yy++;
        y1++;
        l2--;
        r2++;
        if (err > 0) {
            dx += 2;
            err += (-radius << 1) + dx;
            xx--;
            r1--
            l1++
            y3--
            y2++
        }
    }
}

1

Puedes dibujar un cuadrado que encaje dentro del círculo y es bastante sencillo encontrar si el punto cae.

Esto resolverá la mayoría de los puntos (2 * r ^ 2) en el tiempo O (1), en lugar de buscar todos los puntos (4 * r ^ 2).

Editar: para el resto de los puntos, no necesita hacer un bucle con todos los demás píxeles. Necesita hacer un bucle para los 4 rectángulos dimensionados con dimensiones [(2r / sqrt (2)), r- (r / sqrt (2))] en los 4 lados (norte, este, sur, oeste) del cuadrado que es dentro. Significa que nunca tienes que buscar los cuadrados en las esquinas. Dado que es completamente simétrico, podemos tomar los valores absolutos de los puntos de entrada y buscar si el punto está dentro de medio cuadrado en el lado positivo del plano de coordenadas. Lo que significa que solo hacemos un bucle una vez en lugar de 4.

int square_range = r/sqrt(2);
int abs_x = abs(x);
int abs_y = abs(y);

if(abs_x < square_range && abs_y < square_range){
    //point is in
}
else if(abs_x < r && abs_y < r){  // if it falls in the outer square
    // this is the only loop that has to be done
    if(abs_x < abs_y){
        int temp = abs_y;
        abs_y = abs_x;
        abs_x = temp;
    }
    for(int x = r/sqrt(2) ; x < r ; x++){
        for(int y = 0 ; y < r/sqrt(2) ; y++){
             if(x*x + y*y < r*r){
                 //point is in
             }
         }    
    }    
}        

La complejidad general del código es O ((rr / sqrt (2)) * (r / sqrt (2))). Que solo se repite en la mitad de un solo rectángulo (simetría de 8 vías) que se encuentra entre el cuadrado interior y el borde exterior del círculo.


Este enfoque dibujará un cuadrado dentro del círculo. ¿Qué pasa con el resto del círculo?
MBo

Como mencioné para los otros puntos que están fuera del cuadrado, se buscarán con un bucle.
flujo elevado

La complejidad general O(n^2)no es O((r-r/sqrt(2))* (r/sqrt(2)))que se dé una notación cuadrática y grande de O no debe incluir los coeficientes y se debe ignorar todo excepto la potencia más alta. Este problema no se puede simplificar a continuaciónO(n^2)
Blindman67

Soy consciente de la gran notación o, pero mi punto era afirmar que este algoritmo realiza menos operaciones en comparación con otros O (n ^ 2).
flujo elevado
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.