Thuật toán hiệu quả để có được Điểm trong Vòng tròn quanh Trung tâm


8

Vấn đề

Tôi muốn nhận được tất cả các pixel nằm trong một vòng tròn có bán kính nhất định về một điểm đã cho, trong đó các điểm chỉ có thể có tọa độ nguyên , tức là các pixel trong một khung vẽ.

Hình minh họa

Vì vậy, tôi muốn có được tất cả các điểm trong khu vực màu vàng cho (x, y)r.

Phương pháp tiếp cận

Cách hiệu quả nhất mà tôi có thể nghĩ đến là vòng qua một hình vuông xung quanh (x, y)và kiểm tra khoảng cách Euclide cho mỗi điểm:

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.
    }
  }
}

Tuy nhiên, điều này có nghĩa là thuật toán này sẽ kiểm tra các (r * 2)^2 * (4 - pi) / 4pixel không phải là một phần của vòng tròn. dx * dx + dy * dy <= r * r, có vẻ khá đắt đỏ, được gọi là dư thừa gần như 1 / 4thời gian.

Tích hợp một cái gì đó giống như những gì được đề xuất ở đây có thể tăng cường hiệu suất:

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.
    }
  }
}

Tuy nhiên, như chính tác giả đã chỉ ra, điều này có thể sẽ không nhanh hơn khi hầu hết các điểm sẽ nằm trong vòng tròn (đặc biệt là vì abs), pi / 4trong trường hợp này.


Tôi đã không thể tìm thấy bất kỳ tài nguyên về câu hỏi này. Tôi đang tìm kiếm một giải pháp cụ thể trong C ++ và không phải là một cái gì đó trong SQL .


1
Phép nhân số nguyên và phép cộng khá nhanh. Tôi không chắc chắn việc thêm một loạt các so sánh bổ sung sẽ có ích, đặc biệt vì tất cả các dự đoán chi nhánh có thể bỏ lỡ mà nó có thể đưa ra.
François Andrieux

Câu hỏi đặt câu hỏi "Nhất" hoặc "Tốt nhất" thường không thể được trả lời dứt khoát vì hiếm khi có một câu trả lời hay nhất. Giải pháp nào là tốt nhất có thể thay đổi dựa trên trường hợp sử dụng thực tế và kiến ​​trúc mà nó sẽ chạy.
François Andrieux

3
Tận dụng thực tế là việc biết phạm vi của các giá trị x cho một giá trị y đã cho sẽ giúp bạn hiểu rõ hơn về phạm vi của các giá trị x cho các giá trị y trước đó và tiếp theo.
Scott Hunter

1
Ít nhất bạn sẽ có thể thoát khỏi vòng lặp một khi bạn chuyển từ điểm trước là tốt sang điểm tiếp theo là xấu. Tại thời điểm đó, bạn biết rằng bạn đã vượt qua ranh giới và không còn điểm tốt nào nữa trên đường đó.
NathanOliver

1
Một tùy chọn khác, để tạo ví dụ của Scotts, bắt đầu ở giữa trên cùng hoặc dưới cùng, sau đó lặp sang trái và sang phải cho đến khi bạn chạm vào ranh giới. Điều này có nghĩa là các vòng lặp bên trong của bạn dừng lại ngay khi bạn vượt qua ranh giới.
NathanOliver

Câu trả lời:


4

Được rồi đây là những điểm chuẩn tôi đã hứa.

Thiết lập

Tôi đã sử dụng điểm chuẩn của google và nhiệm vụ là chèn tất cả các điểm trong phạm vi của hình tròn vào a std::vector<point>. Tôi điểm chuẩn cho một bộ bán kính và một trung tâm không đổi:

radii = {10, 20, 50, 100, 200, 500, 1000}
center = {100, 500}
  • ngôn ngữ: C ++ 17
  • trình biên dịch: msvc 19.24.28316 x64
  • nền tảng: windows 10
  • tối ưu hóa: O2 (tối ưu hóa đầy đủ)
  • luồng: thực hiện luồng đơn

Kết quả của mỗi thuật toán được kiểm tra tính chính xác (so với đầu ra của thuật toán OP).

Cho đến nay các thuật toán sau được điểm chuẩn:

  1. Thuật toán của OP enclosing_square.
  2. Thuật toán của tôi containing_square .
  3. thuật toán của creativecreatorormaybenot edge_walking .
  4. Thuật toán của Mandy007 binary_search .

Các kết quả

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

Mã kiểm tra hoàn chỉnh bên dưới, bạn có thể sao chép và dán mã và tự kiểm tra. fill_circle.cppchứa việc thực hiện các thuật toán khác nhau.

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. Vui lòng chỉnh sửa câu trả lời này nếu bạn điều chỉnh các thuật toán để chúng tôi có thể cập nhật kết quả tại đây.
Timo

1
@ Mandy007 Tôi đã cập nhật điểm chuẩn.
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)
   } 
}

Để tránh việc tạo các pixel lặp đi lặp lại ở các trục, nên bắt đầu các vòng lặp từ 1 và vẽ các đường trung tâm (ix == 0 hoặc line == 0) trong vòng lặp riêng biệt.

Lưu ý rằng cũng có thuật toán Bresenham nguyên nguyên để tạo điểm chu vi.


IThere không có lợi ích thực sự của việc sử dụng đối xứng ở đây. Tôi đã thay đổi mã để tạo ra một phần tư vòng tròn.
MBo

2

Được rồi, trước hết chúng ta tính bình phương bên trong của vòng tròn. Công thức cho nó là thẳng về phía trước:

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

Bây giờ, mọi điểm giữa (-h, -h)(+h, +h)nằm trong vòng tròn. Đây là một hình ảnh của những gì tôi có nghĩa là:

1

Phần màu xanh còn lại là một chút khó khăn, nhưng cũng không quá phức tạp. Chúng tôi bắt đầu ở trên cùng của vòng tròn màu xanh (x = 0, y = -radius). Tiếp theo, chúng ta đi bên phải ( x++) cho đến khi chúng ta rời khỏi vòng tròn perimiter (cho đến khix²+y² < r² không giữ được nữa). Mọi thứ nằm giữa (0, y) và (x, y) đều nằm trong vòng tròn. Do tính đối xứng, chúng ta có thể mở rộng gấp 8 lần

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

bây giờ chúng tôi đi xuống 1 dòng ( y--) và lặp lại các bước trên (trong khi vẫn giữ giá trị gần đây nhất củax ). Thêm tâm của vòng tròn vào mỗi điểm và bạn đã hoàn thành.

Đây là một hình dung. Có một số hiện vật vì sự nâng cấp. Dấu chấm màu đỏ cho thấy những gì chúng tôi đang thử nghiệm ở mỗi lần lặp:

1

Đây là mã đầy đủ (sử dụng opencv để vẽ nội dung):

#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 Tôi nghĩ rằng phiên bản của tôi nên nhanh hơn cho mọi thứ trừ những hình ảnh rất nhỏ vì tính toán của hình vuông ở trung tâm là thời gian không đổi (không có bất kỳ chức năng nào, chúng tôi chỉ sử dụng các số cơ bản). Điều này cũng có nghĩa là bạn không cần phải thực hiện bất kỳ kiểm tra nào bên trong quảng trường. Đối với phần còn lại của thuật toán tha hình ảnh về cơ bản là giống nhau.
Timo

1
Chỉ cần chú ý rằng aswell. Điều này có thể đun sôi để tối ưu hóa vi mô ở cấp độ lắp ráp. Có lẽ tôi sẽ làm một điểm chuẩn sau.
Timo

2

Đây là một tối ưu hóa làm giảm 1/4 chiều tìm kiếm:

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
    }
  }
}

hoặc tốt hơn, cải thiện hiệu suất lặp lại vòng tròn thứ hai for tránh ifđiều kiện

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.*/
  }
}

tôi cũng nghĩ rằng tùy chọn khác là tìm kiếm nhị phân cho giới hạn trên:

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.*/
  }
}

Ý tưởng của tìm kiếm nhị phân là tìm giới hạn trên một cách tối ưu, tránh ifđiều kiện và tính toán trong forchu trình. Đối với điều này, nó được kiểm tra là số nguyên lớn nhất tạo ra khoảng cách giữa điểm hiện tại và bán kính trong vòng tròn.

PD: Xin lỗi tiếng Anh của tôi.


2

Dựa trên ý tưởng từ @ScottHunter , tôi đã đưa ra thuật toán sau:

#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;
    }
}

Thuật toán giải thích

Thuật toán này thực hiện một số phút kiểm tra. Cụ thể, nó chỉ kiểm tra trong mỗi hàng cho đến khi đạt đến điểm đầu tiên là một phần của vòng tròn. Hơn nữa, nó sẽ bỏ qua các điểm ở bên trái của điểm được xác định trước đó trong hàng tiếp theo. Ngoài ra, bằng cách sử dụng tính đối xứng, chỉ một nửa số hàng ( n/2 + 1/2khi chúng tôi bắt đầu từ 0) được chọn.

Hình dung

Đây là một hình dung của thuật toán tôi tạo ra. Đường viền màu đỏ biểu thị hình vuông đã được kiểm tra trước đó và các pixel màu đen biểu thị vòng tròn thực (với pixel màu đỏ ở giữa là trung tâm). Thuật toán kiểm tra các điểm (được đánh dấu màu xanh lam) và các vòng lặp thông qua các điểm hợp lệ (được đánh dấu màu xanh lá cây).
Như bạn có thể thấy, số pixel màu xanh lam ở cuối là phút, tức là chỉ có một vài điểm được lặp lại mà không phải là một phần của vòng tròn. Ngoài ra, lưu ý rằng chỉ pixel xanh đầu tiên mỗi lần cần kiểm tra, các pixel khác chỉ được lặp qua, đó là lý do tại sao chúng xuất hiện ngay lập tức.

Ghi chú

Các trục có thể dễ dàng đảo ngược, rõ ràng.

Điều này có thể được tối ưu hóa bằng cách tận dụng tính đối xứng hơn nữa, tức là các hàng sẽ giống như các cột (đi qua tất cả các hàng giống như đi qua tất cả các cột, từ trái sang phải, lên xuống, ngược lại, vise vera) và đi xuống chỉ một phần tư các hàng từ trung tâm sẽ đủ để xác định chính xác những điểm nào sẽ là một phần của vòng tròn. Tuy nhiên, tôi cảm thấy như vết sưng hiệu suất nhỏ mà điều này sẽ cung cấp không xứng đáng với mã bổ sung.
Nếu ai đó muốn mã hóa nó, hãy đề xuất một chỉnh sửa cho câu trả lời này.

Mã có ý kiến

#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 có thể đủ nhanh để "vũ phu" tính toán tọa độ pixel khi đang di chuyển cho mỗi vòng tròn (có thể không cần tối ưu hóa). Nhưng, bạn sẽ chỉ kiểm tra 1 bán kính hay chỉ một vài bán kính? Nếu có, bạn có thể tính toán trước các điểm bên trong của một vòng tròn thành một mảng và sử dụng lại các điểm được tính toán đó - bù vào điểm gốc của vòng tròn bạn đang kiểm tra.
đánh dấu

1
Bạn có thể thấy bản cập nhật cuối cùng của mã của tôi, tôi nghĩ rằng nó tốt hơn
Mandy007

2

Bài toán có độ phức tạp cố định là O (n ^ 2) trong đó n là bán kính của đường tròn. Độ phức tạp tương tự như hình vuông hoặc bất kỳ hình dạng 2D thông thường nào

Không thể vượt qua thực tế là bạn không thể giảm số lượng pixel trong một vòng tròn, ngay cả khi bạn tận dụng tính đối xứng, độ phức tạp vẫn giữ nguyên.

Vì vậy, bỏ qua sự phức tạp và tìm kiếm tối ưu hóa.

Trong câu hỏi của bạn, bạn nói rằng giá absquá đắt cho mỗi pixel (hoặc pixel thứ 4)

Một lần trên mỗi hàng tốt hơn một lần trên mỗi pixel

Bạn có thể giảm nó xuống 1 căn bậc hai mỗi hàng. Cho bán kính hình tròn 256 mà 128 căn bậc hai

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++;
    }
}

Để nhận được nhiều hơn từ nó, bạn có thể tạo một bảng tra cứu cho các tính toán gốc sqrt.

Tất cả số nguyên

Ngoài ra, bạn có thể sử dụng biến thể trên dòng bresenham thay thế căn bậc hai bằng tất cả toán học số nguyên. Tuy nhiên, nó là một mớ hỗn độn và sẽ không có ích lợi gì trừ khi thiết bị không có đơn vị dấu phẩy động.

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

Bạn có thể vẽ một hình vuông vừa vặn bên trong vòng tròn và khá dễ tìm nếu điểm rơi vào.

Điều này sẽ giải quyết hầu hết các điểm (2 * r ^ 2) trong thời gian O (1), thay vì tìm kiếm tất cả (4 * r ^ 2) điểm.

Chỉnh sửa: Đối với các điểm còn lại, bạn không cần lặp tất cả các pixel khác. Bạn cần lặp cho 4 hình chữ nhật có kích thước với kích thước [(2r / sqrt (2)), r- (r / sqrt (2))] trên 4 cạnh (bắc, đông, nam, tây) của hình vuông phía trong. Nó có nghĩa là bạn không bao giờ phải tìm kiếm các hình vuông trên các góc. Vì nó hoàn toàn đối xứng, chúng ta có thể lấy các giá trị tuyệt đối của các điểm đầu vào và tìm kiếm nếu điểm nằm trong nửa hình vuông ở cạnh dương của mặt phẳng tọa độ. Có nghĩa là chúng ta chỉ lặp một lần thay vì 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
             }
         }    
    }    
}        

Độ phức tạp chung của mã là O ((rr / sqrt (2)) * (r / sqrt (2))). Đó chỉ là vòng lặp cho một nửa hình chữ nhật duy nhất (đối xứng 8 chiều) nằm giữa đường viền bên trong của hình vuông bên trong và bên ngoài của cirle.


Cách tiếp cận này sẽ vẽ một hình vuông bên trong vòng tròn. Phần còn lại của vòng tròn thì sao?
MBo

Như tôi đã đề cập cho các điểm khác bên ngoài hình vuông sẽ được tìm kiếm với một vòng lặp.
heapoverflow

Độ phức tạp tổng thể O(n^2)không phải là O((r-r/sqrt(2))* (r/sqrt(2)))bạn đưa ra một ký hiệu O bậc hai và lớn không nên bao gồm các hệ số và tất cả nhưng công suất cao nhất phải được bỏ qua. Vấn đề này không thể được đơn giản hóa dưới đâyO(n^2)
Blindman67

Tôi biết về ký hiệu o lớn, nhưng quan điểm của tôi là nói rằng thuật toán này thực hiện ít thao tác hơn so với các O (n ^ 2) khác.
heapoverflow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.