비용 경로 솔루션이 있지만 직접 코딩해야합니다. 다음은 문제의 이미지에서 모든 지점에 적용될 때의 모습입니다 (계산 속도를 높이기 위해 조금 조잡했습니다).
검은 셀은 주변 다각형의 일부입니다. 연한 주황색 (짧은 색)에서 파란색 (긴색)까지의 색상은 다각형 셀을 가로 채지 않고 시선 통과를 통해 도달 할 수있는 최대 거리 (최대 50 셀까지)를 나타냅니다. 이 이미지의 범위를 벗어난 모든 셀은 다각형의 일부로 처리됩니다.
데이터의 래스터 표현을 사용하여이를 수행하는 효율적인 방법을 논의 해 봅시다. 이 표현에서, 모든 "주변"다각형 셀은 0이 아닌 값을 가질 것이고 "탐색"될 수있는 임의의 셀은 0을 가질 것이다.
1 단계 : 주변 데이터 구조 사전 계산
먼저 한 셀이 다른 셀을 차단하는 것이 무엇을 의미하는지 결정해야합니다. 내가 찾을 수있는 가장 공정한 규칙 중 하나는 행과 열에 적분 좌표를 사용하고 정사각형 셀을 가정하여 어느 셀이 원점 (0,0)의보기에서 셀 (i, j)을 차단 할 수 있는지 고려해 보겠습니다. 좌표가 i 및 j와 최대 1이 다른 모든 셀 중에서 (i, j)에서 (0,0)을 연결하는 선 세그먼트에 가장 가까운 셀 (i ', j')을 추천합니다. (i, j) = (1,2) (0,1) 및 (1,1) 모두 동일하게 작동하는 고유 한 솔루션을 생성하려면 관계를 해결하는 몇 가지 수단이 필요합니다. 이 관계가 그리드에서 원형 이웃의 대칭을 존중하는 것이 좋을 것입니다. 좌표를 부정하거나 좌표를 전환하면 이러한 이웃이 보존됩니다. 따라서 우리는 어느 세포를 차단할지 결정할 수 있습니다 (i,
이 규칙을 보여주는 것은로 작성된 다음과 같은 프로토 타입 코드입니다 R
. 이 코드는 그리드에서 임의의 셀의 "주변"을 결정하는 데 편리한 데이터 구조를 반환합니다.
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) )
}
값은 screen(12)
이 스크리닝 관계를 묘사하는 데 사용되었습니다. 화살표는 셀에서 해당 셀을 즉시 스크리닝하는 화살표를 가리 킵니다. 색조는이 이웃의 중앙에있는 원점까지의 거리에 따라 비례합니다.
이 계산은 빠르며 특정 이웃에 대해 한 번만 수행하면됩니다. 예를 들어, 5m 셀이있는 그리드에서 200m를 볼 때 주변 크기는 200/5 = 40 단위입니다.
2 단계 : 선택한 점에 계산 적용
나머지는 간단합니다. (x, y) (행 및 열 좌표로)에 위치한 셀이이 주변 데이터 구조와 관련하여 "주변"되는지 확인하려면 (i, j)의 오프셋으로 재귀 적으로 테스트를 수행하십시오. = (0,0) (이웃 원점). (x, y) + (i, j)에서 다각형 격자의 값이 0이 아닌 경우 가시성이 차단됩니다. 그렇지 않으면, 오프셋 (i, j)에서 차단 될 수있는 모든 오프셋을 고려해야합니다 screen
. 차단 된 것이 없으면 주변에 도달하여 (x, y)가 둘러싸이지 않았다고 결론을 내립니다. 따라서 계산을 중지합니다 (근처의 나머지 지점을 검사하지 않아도됩니다).
알고리즘 중에 도달 한 가장 먼 가시 거리를 추적하여 훨씬 유용한 정보를 수집 할 수 있습니다. 이것이 원하는 반경보다 작 으면 셀이 둘러싸입니다. 그렇지 않으면 그렇지 않습니다.
다음은 R
이 알고리즘 의 프로토 타입입니다. R
재귀를 구현하는 데 필요한 (단순한) 스택 구조를 기본적으로 지원하지 않기 때문에 스택이 코딩되어야하기 때문에 예상보다 길다 . 실제 알고리즘은 약 3 분의 2에서 시작하며 12 줄 정도만 필요합니다. (그들 중 절반은 그리드 주변의 상황을 처리하고 이웃 지역의 범위를 벗어난 인덱스를 확인합니다. 다각형 그리드를 k
주변의 행과 열로 확장하여 더 효율적으로 만들 수 있습니다. 다각형 그리드를 유지하기 위해 약간의 RAM 비용으로 인덱스 범위 검사가 필요합니다.)
#
# 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)
}
이 예에서 다각형 셀은 검은 색입니다. 색상은 비 다각형 셀에 대한 최대 가시 거리 (50 셀까지)를 제공합니다. 단거리는 연한 주황색에서 가장 먼 거리는 진한 파란색까지 다양합니다. (세포는 한 단위 너비와 높이입니다.) 눈에 띄는 줄무늬는 "강"의 중간에있는 작은 다각형 "섬"에 의해 만들어집니다. 각 줄무늬는 다른 세포의 긴 줄을 차단합니다.
알고리즘 분석
스택 구조는 셀이 둘러싸 이지 않았다는 증거에 대한 이웃 가시성 그래프의 깊이 우선 검색을 구현합니다 . 셀이 다각형과 멀리 떨어져있는 경우이 검색에서는 반경 k 원형 이웃에 대한 O (k) 셀만 검사해야합니다. 최악의 경우는 이웃 내에 흩어져있는 다각형 셀이 적을 때도 발생하지만 이웃의 경계에 도달 할 수는 없습니다. 각 이웃의 거의 모든 셀을 검사해야합니다 .O (k ^ 2) 조작.
다음은 일반적으로 발생하는 동작입니다. k가 작은 값의 경우, 다각형이 대부분의 그리드를 채우지 않는 한 대부분의 비 다각형 셀은 분명히 둘러싸이지 않으며 알고리즘은 O (k)와 같이 확장됩니다. 중간 값의 경우 스케일링은 O (k ^ 2)처럼 보입니다. k가 실제로 커지면 대부분의 셀이 둘러싸이고 전체 이웃을 검사하기 전에 사실을 알 수 있습니다. 그러므로 알고리즘의 계산 노력이 실제 한계에 도달합니다. 이 한계는 인접 반경이 그리드에서 연결된 가장 큰 비-다각형 영역의 직경에 도달 할 때 달성됩니다.
예를 들어, counting
프로토 타입에 코딩 된 옵션을 screen
사용하여 각 호출에 사용 된 스택 작업 수를 반환합니다. 이것은 계산 노력을 측정합니다. 다음 그래프는 인접 반경의 함수로 평균 스택 연산 수를 나타냅니다. 예상되는 동작을 보여줍니다.
이를 사용하여 그리드에서 1,300 만 포인트를 평가하는 데 필요한 계산을 추정 할 수 있습니다. k = 200/5 = 40의 이웃이 사용되었다고 가정하십시오. 그런 다음 평균적으로 수백 개의 스택 작업이 필요합니다 (다각형 그리드의 복잡성과 다각형에 대해 1 천 3 백만 개의 지점이있는 위치에 따라 다름). 효율적인 컴파일 된 언어에서 최대 수천 개의 간단한 숫자 연산을 의미합니다. 추가, 곱하기, 읽기, 쓰기, 오프셋 등이 필요합니다. 대부분의 PC는 그 속도로 약 백만 포인트의 포 위성을 평가할 수 있습니다. (그만큼R
구현은 이런 종류의 알고리즘이 열악하기 때문에 그보다 훨씬 느리므로 프로토 타입으로 만 간주 될 수 있습니다.) 따라서 우리는 합리적으로 효율적이고 적절한 언어 인 C ++로 효율적인 구현을 희망 할 수 있습니다. 파이썬은 모든 다각형 그리드가 RAM에 있다고 가정하면 1 분 안에 1,300 만 포인트의 평가를 완료 할 수 있습니다.
그리드가 너무 커서 RAM에 맞지 않으면이 절차를 그리드의 타일 부분에 적용 할 수 있습니다. k
행과 열만 겹치면됩니다 . 결과를 모자이크 처리 할 때 겹치는 부분을 최대한 활용하십시오.
다른 응용
는 바디 물 "페치" 밀접 포인트 "surroundedness"에 관한 것이다. 실제로, 우리가 수역의 지름보다 크거나 같은 이웃 반경을 사용한다면, 우리는 수역의 모든 지점에서 (비 방향성) 페치 그리드를 생성 할 것입니다. 더 작은 인접 반경을 사용함으로써 우리는 적어도 모든 최고 반입 지점에서 반입에 대한 하한을 얻습니다. 특정 방향과의 "차단 기준"관계를 제한하는이 알고리즘의 변형은 해당 방향으로 효율적으로 페치를 계산하는 한 가지 방법입니다. 이러한 변형은 코드를 수정해야합니다 screen
. 에 대한 코드 panvisibility
는 전혀 변경되지 않습니다.