Có một giải pháp đường dẫn chi phí nhưng bạn sẽ phải tự viết mã. Đây là những gì nó có thể trông giống như khi áp dụng cho mọi điểm trong hình ảnh trong câu hỏi (thô hơn một chút để tăng tốc các phép tính):
Các tế bào màu đen là một phần của đa giác xung quanh. Các màu, từ màu cam nhạt (ngắn) đến màu xanh lam (dài), hiển thị khoảng cách tối đa (tối đa 50 ô) có thể đạt được bằng cách di chuyển ngang tầm nhìn mà không chặn các ô đa giác. (Bất kỳ ô nào nằm ngoài phạm vi của hình ảnh này đều được coi là một phần của đa giác.)
Chúng ta hãy thảo luận một cách hiệu quả để làm điều đó bằng cách sử dụng biểu diễn dữ liệu raster. Trong biểu diễn này, tất cả các ô đa giác "xung quanh" sẽ có các giá trị khác 0 và bất kỳ ô nào có thể được "nhìn xuyên qua" sẽ có giá trị bằng không.
Bước 1: Tính toán trước cấu trúc dữ liệu vùng lân cận
Trước tiên, bạn phải quyết định ý nghĩa của việc một ô này chặn một ô khác. Một trong những quy tắc công bằng nhất mà tôi có thể tìm thấy là: sử dụng tọa độ tích phân cho các hàng và cột (và giả sử các ô vuông), chúng ta hãy xem xét các ô nào có thể chặn ô (i, j) từ chế độ xem tại điểm gốc (0,0). Tôi chỉ định ô (i ', j') gần nhất với đoạn đường nối từ (i, j) đến (0,0) trong số tất cả các ô có tọa độ khác với i và j nhiều nhất là 1. Bởi vì điều này không phải lúc nào cũng mang lại một giải pháp duy nhất (ví dụ, với (i, j) = (1,2) cả (0,1) và (1,1) sẽ hoạt động tốt như nhau), một số phương tiện để giải quyết các mối quan hệ là cần thiết. Sẽ là tốt đẹp cho độ phân giải các mối quan hệ này để tôn trọng các đối xứng của các vùng lân cận trong lưới: phủ định tọa độ hoặc chuyển đổi tọa độ bảo tồn các vùng lân cận này. Do đó, chúng ta có thể quyết định khối nào (i,
Minh họa quy tắc này là mã nguyên mẫu sau đây được viết bằng R
. Mã này trả về một cấu trúc dữ liệu sẽ thuận tiện cho việc xác định "độ bao quanh" của các ô tùy ý trong một lưới.
screen <- function(k=1) {
#
# Returns a data structure:
# $offset is an array of offsets
# $screened is a parallel array of screened offset indexes.
# $distance is a parallel array of distances.
# The first index always corresponds to (0,0).
#
screened.by <- function(xy) {
uv <- abs(xy)
if (reversed <- uv[2] > uv[1]) {
uv <- rev(uv)
}
i <- which.min(c(uv[1], abs(uv[1]-uv[2]), uv[2]))
ij <- uv + c(floor((1-i)/3), floor(i/3)-1)
if (reversed) ij <- rev(ij)
return(ij * sign(xy))
}
#
# For each lattice point within the circular neighborhood,
# find the unique lattice point that screens it from the origin.
#
xy <- subset(expand.grid(x=(-k:k), y=(-k:k)),
subset=(x^2+y^2 <= k^2) & (x != 0 | y != 0))
g <- t(apply(xy, 1, function(z) c(screened.by(z), z)))
#
# Sort by distance from the origin.
#
colnames(g) <- c("x", "y", "x.to", "y.to")
ij <- unique(rbind(g[, 1:2], g[, 3:4]))
i <- order(abs(ij[,1]), abs(ij[,2])); ij <- ij[i, , drop=FALSE]
rownames(ij) <- 1:length(i)
#
# Invert the "screened by" relation to produce the "screened" relation.
#
# (Row, column) offsets.
ij.df <- data.frame(ij, i=1:length(i))
#
# Distances from the origin (in cells).
distance <- apply(ij, 1, function(u) sqrt(sum(u*u)))
#
# "Screens" relation (represented by indexes into ij).
g <- merge(merge(g, ij.df), ij.df,
by.x=c("x.to", "y.to"), by.y=c("x","y"))
g <- subset(g, select=c(i.x, i.y))
h <- by(g$i.y, g$i.x, identity)
return( list(offset=ij, screened=h, distance=distance) )
}
Giá trị của screen(12)
đã được sử dụng để tạo ra mô tả về mối quan hệ sàng lọc này: mũi tên chỉ từ các ô đến các ô ngay lập tức sàng lọc chúng. Các màu sắc được chia theo tỷ lệ khoảng cách với nguồn gốc, nằm ở giữa khu phố này:
Tính toán này là nhanh và chỉ cần được thực hiện một lần cho một vùng lân cận nhất định. Chẳng hạn, khi nhìn ra 200 m trên lưới có 5 m ô, kích thước vùng lân cận sẽ là 200/5 = 40 đơn vị.
Bước 2: Áp dụng tính toán cho các điểm đã chọn
Phần còn lại rất đơn giản: để xác định xem một ô nằm ở (x, y) (tọa độ hàng và cột) có được "bao quanh" đối với cấu trúc dữ liệu lân cận này hay không, thực hiện kiểm tra đệ quy bắt đầu bằng phần bù của (i, j) = (0,0) (nguồn gốc khu phố). Nếu giá trị trong lưới đa giác tại (x, y) + (i, j) là khác không, thì khả năng hiển thị bị chặn ở đó. Mặt khác, chúng ta sẽ phải xem xét tất cả các độ lệch có thể đã bị chặn ở offset (i, j) (được tìm thấy trong thời gian O (1) bằng cách sử dụng cấu trúc dữ liệu được trả về screen
). Nếu không có cái nào bị chặn, chúng tôi đã đạt đến chu vi và kết luận rằng (x, y) không bị bao vây, vì vậy chúng tôi dừng tính toán (và không bận tâm kiểm tra bất kỳ điểm nào còn lại trong vùng lân cận).
Chúng ta có thể thu thập thông tin hữu ích hơn nữa bằng cách theo dõi khoảng cách tầm nhìn xa nhất đạt được trong thuật toán. Nếu giá trị này nhỏ hơn bán kính mong muốn, ô sẽ được bao quanh; nếu không thì không phải.
Đây là một R
nguyên mẫu của thuật toán này. Nó dài hơn dường như, vì R
thực chất không hỗ trợ cấu trúc ngăn xếp (đơn giản) cần thiết để thực hiện đệ quy, do đó, một ngăn xếp cũng phải được mã hóa. Thuật toán thực tế bắt đầu khoảng hai phần ba chặng đường và chỉ cần một chục dòng hoặc hơn. (Và một nửa trong số đó chỉ xử lý tình huống xung quanh mép lưới, kiểm tra các chỉ số ngoài phạm vi trong vùng lân cận. Điều này có thể hiệu quả hơn chỉ bằng cách mở rộng lưới đa giác bằng k
các hàng và cột quanh chu vi của nó, loại bỏ bất kỳ cần kiểm tra phạm vi chỉ mục với chi phí thêm một chút RAM để giữ lưới đa giác.)
#
# Test a grid point `ij` for a line-of-sight connection to the perimeter
# of a circular neighborhood.
# `xy` is the grid.
# `counting` determines whether to return max distance or count of stack ops.
# `perimeter` is the assumed values beyond the extent of `xy`.
#
# Grid values of zero admit light; all others block visibility
# Returns maximum line-of-sight distance found within `nbr`.
#
panvisibility <- function(ij, xy, nbr=screen(), counting=FALSE, perimeter=1) {
#
# Implement a stack for the algorithm.
#
count <- 0 # Stack count
stack <- list(ptr=0, s=rep(NA, dim(nbr$offset)[1]))
push <- function(x) {
n <- length(x)
count <<- count+n # For timing
stack$s[1:n + stack$ptr] <<- x
stack$ptr <<- stack$ptr+n
}
pop <- function() {
count <<- count+1 # For timing
if (stack$ptr <= 0) return(NULL)
y <- stack$s[stack$ptr]
#stack$s[stack$ptr] <<- NA # For debugging
stack$ptr <<- stack$ptr - 1
return(y)
}
#
# Initialization.
#
m <- dim(xy)[1]; n <- dim(xy)[2]
push(1) # Stack the *indexes* of nbr$offset and nbr$screened.
dist.max <- -1
#
# The algorithm.
#
while (!is.null(i <- pop())) {
cell <- nbr$offset[i, ] + ij
if (cell[1] <= 0 || cell[1] > m || cell[2] <= 0 || cell[2] > n) {
value <- perimeter
} else {
value <- xy[cell[1], cell[2]]
}
if (value==0) {
if (nbr$distance[i] > dist.max) dist.max <- nbr$distance[i]
s <- nbr$screened[[paste(i)]]
if (is.null(s)) {
#exited = TRUE
break
}
push(s)
}
}
if (counting) return ( count )
return(dist.max)
}
Trong ví dụ này, các ô đa giác có màu đen. Màu sắc cho khoảng cách tầm nhìn tối đa (tối đa 50 ô) cho các ô không đa giác, từ màu cam nhạt cho khoảng cách ngắn đến màu xanh đậm cho khoảng cách xa nhất. (Các ô có một đơn vị rộng và cao.) Các vệt rõ ràng rõ ràng được tạo ra bởi các "đảo" đa giác nhỏ ở giữa "dòng sông": mỗi ô chặn một hàng dài các ô khác.
Phân tích thuật toán
Cấu trúc ngăn xếp thực hiện tìm kiếm theo chiều sâu của biểu đồ khả năng hiển thị lân cận để tìm bằng chứng cho thấy một ô không được bao quanh. Khi các ô nằm xa bất kỳ đa giác nào, tìm kiếm này sẽ yêu cầu kiểm tra chỉ các ô O (k) cho một vùng lân cận bán kính-k. Các trường hợp xấu nhất xảy ra khi có một số lượng nhỏ các ô đa giác rải rác trong vùng lân cận nhưng ngay cả ranh giới của vùng lân cận cũng không thể đạt được: chúng yêu cầu kiểm tra gần như tất cả các ô trong mỗi vùng lân cận, đó là một O (k ^ 2) hoạt động.
Các hành vi sau đây là điển hình của những gì sẽ gặp phải. Đối với các giá trị nhỏ của k, trừ khi đa giác lấp đầy hầu hết lưới, hầu hết các ô không đa giác sẽ rõ ràng không có nền tảng và các tỷ lệ thuật toán như O (k). Đối với các giá trị trung gian, tỷ lệ bắt đầu trông giống như O (k ^ 2). Khi k thực sự lớn, hầu hết các ô sẽ được bao quanh và thực tế đó có thể được xác định rõ trước khi toàn bộ vùng lân cận được kiểm tra: nỗ lực tính toán của thuật toán do đó đạt đến giới hạn thực tế. Giới hạn này đạt được khi bán kính lân cận đạt đến đường kính của các vùng không đa giác được kết nối lớn nhất trong lưới.
Ví dụ, tôi sử dụng counting
tùy chọn được mã hóa vào nguyên mẫu screen
để trả về số lượng thao tác ngăn xếp được sử dụng trong mỗi cuộc gọi. Điều này đo lường nỗ lực tính toán. Biểu đồ sau biểu thị số trung bình của ngăn xếp ops như là một hàm của bán kính lân cận. Nó thể hiện hành vi dự đoán.
Chúng ta có thể sử dụng điều này để ước tính tính toán cần thiết để đánh giá 13 triệu điểm trên lưới. Giả sử rằng một vùng lân cận k = 200/5 = 40 được sử dụng. Sau đó, trung bình sẽ cần vài trăm thao tác ngăn xếp (tùy thuộc vào độ phức tạp của lưới đa giác và vị trí của 13 triệu điểm so với đa giác), ngụ ý rằng trong một ngôn ngữ được biên dịch hiệu quả, nhiều nhất là vài nghìn thao tác số đơn giản sẽ được yêu cầu (thêm, nhân, đọc, viết, bù, v.v.). Hầu hết các PC sẽ có thể đánh giá mức độ bao quanh của khoảng một triệu điểm với tốc độ đó. (CácR
triển khai chậm hơn rất nhiều so với điều đó, vì nó kém về loại thuật toán này, đó là lý do tại sao nó chỉ có thể được coi là một nguyên mẫu.) Theo đó, chúng tôi có thể hy vọng rằng việc triển khai hiệu quả bằng ngôn ngữ phù hợp và hiệu quả hợp lý - C ++ và Python xuất hiện - có thể hoàn thành việc đánh giá 13 triệu điểm trong một phút hoặc ít hơn, giả sử toàn bộ lưới đa giác nằm trong RAM.
Khi lưới quá lớn để vừa với RAM, quy trình này có thể được áp dụng cho các phần được lát gạch của lưới. Chúng chỉ phải chồng lên nhau bởi k
các hàng và cột; lấy cực đại tại các phần trùng lặp khi ghép kết quả.
Các ứng dụng khác
Các "lấy" của một cơ thể của nước có liên quan chặt chẽ với "surroundedness" của các điểm của nó. Trên thực tế, nếu chúng ta sử dụng bán kính lân cận bằng hoặc lớn hơn đường kính của nước, chúng ta sẽ tạo ra một lưới của đường truyền (không định hướng) tại mọi điểm trong thân nước. Bằng cách sử dụng bán kính vùng lân cận nhỏ hơn, ít nhất chúng ta sẽ có được giới hạn thấp hơn cho việc tìm nạp tại tất cả các điểm tìm nạp cao nhất, trong một số ứng dụng có thể đủ tốt (và có thể làm giảm đáng kể nỗ lực tính toán). Một biến thể của thuật toán này giới hạn mối quan hệ "được sàng lọc bởi" theo các hướng cụ thể sẽ là một cách để tính toán tìm nạp hiệu quả theo các hướng đó. Lưu ý rằng các biến thể như vậy yêu cầu sửa đổi mã cho screen
; mã cho panvisibility
không thay đổi gì cả.