타일 ​​세트의 경계에 대한 효율적인 알고리즘


12

맵을 형성하는 알려진 유한 크기의 타일 그리드가 있습니다. 지도 안의 타일 중 일부는 지역으로 알려진 세트에 배치됩니다. 이 영토는 연결되어 있지만 그 모양에 대해서는 알려진 바가 없습니다. 대부분의 경우 상당히 규칙적인 얼룩이 될 수 있지만 한 방향으로 매우 길어질 수 있으며 구멍이 생길 수도 있습니다. 나는 영토의 (외부) 국경을 찾는 데 관심이 있습니다.

즉, 나는 영토에 있지 않고 영토의 타일 중 하나에 닿는 모든 타일 목록을 원합니다. 이것을 찾는 효율적인 방법은 무엇입니까?

여분의 어려움을 겪기 위해 내 타일이 헥스 인 경우가 발생하지만 큰 차이는 없지만 각 타일에는 여전히 정수 x 및 y 좌표로 레이블이 지정되어 있으며 타일이 있으면 이웃을 쉽게 찾을 수 있습니다. 아래는 몇 가지 예입니다. 검은 색은 영토이고 파란색은 찾고 싶은 경계입니다. 영토와 국경의 예 이것은 그 자체로는 어려운 문제가 아닙니다. 의사 파이썬에서이를위한 간단한 알고리즘은 다음과 같습니다.

def find_border_of_territory(territory):
    border = []
    for tile in territory:
        for neighbor in tile.neighbors():
            if neighbor not in territory and neighbor not in border:
                border.add(neighbor)

그러나 이것은 느리고 더 나은 것을 원합니다. 나는 영토에 O (n) 루프가 있고 모든 이웃에 대한 다른 루프 (짧은 루프이지만 여전히)가 있고 두 개의 목록을 통해 멤버십을 확인해야합니다. 하나는 크기가 n입니다. 그것은 O (n ^ 2)의 끔찍한 스케일링을 제공합니다. 국경과 영토에 대한 목록 대신 세트를 사용하여 O (n)으로 줄일 수 있으므로 멤버쉽을 빠르게 확인할 수 있지만 여전히 좋지 않습니다. 영토는 크지 만 단순한 영역 대 라인 스케일링으로 인해 테두리가 작은 경우가 많이있을 것으로 예상합니다. 예를 들어 영토의 반지름이 5의 16 진수 인 경우 크기는 91이지만 테두리의 크기는 36입니다.

누구든지 더 나은 것을 제안 할 수 있습니까?

편집하다:

아래 질문에 답하십시오. 영토의 크기는 약 20에서 100 정도입니다. 영역을 구성하는 타일 세트는 객체의 속성이며 모든 경계 타일 세트가 필요한이 객체입니다.

처음에 영역은 블록으로 생성 된 다음 대부분 타일을 하나씩 얻습니다. 이 경우 가장 빠른 방법은 테두리 세트를 유지하고 얻은 타일에서만 업데이트하는 것입니다. 때때로 영토에 큰 변화가 일어날 수 있으므로 완전히 재 계산해야합니다.

나는 단순한 경계 찾기 알고리즘을 수행하는 것이 최선의 해결책이라고 생각합니다. 이것이 야기하는 추가적인 복잡성은 경계가 필요할 때마다 다시 계산되도록하는 것입니다. 현재 프레임 워크 에서이 작업을 안정적으로 수행 할 수 있다고 확신합니다.

타이밍에 관해서는, 현재 코드에는 영토의 모든 타일을 확인 해야하는 루틴이 있습니다. 모든 차례가 아니라 창조와 때로는 그 이후에 있습니다. 전체 테스트의 아주 작은 부분이지만 테스트 코드에 대한 실행 시간의 50 % 이상이 걸립니다. 그러므로 나는 반복을 최소화하기를 원했다. 그러나 테스트 코드는 프로그램을 정상적으로 실행하는 것보다 훨씬 더 많은 객체를 생성하므로 자연스럽게 관련이 없을 수도 있습니다.


10
모양에 대해 알려진 것이 없으면 O (N) 알고리즘이 합리적으로 보입니다. 더 빠른 것은 영토의 모든 요소를 ​​볼 필요가 없으며 모양에 대해 알고있는 경우에만 작동한다고 생각합니다.
amitp

3
아마 자주 그렇게 할 필요는 없습니다. 또한 n은 총 타일 수보다 훨씬 적지 않습니다.
Trilarion

1
이 영역들은 어떻게 생성 / 변경됩니까? 그리고 얼마나 자주 변경됩니까? 타일별로 선택하면 이웃과 함께 이웃 목록을 작성할 수 있으며, 자주 변경되지 않는 한 여러 영역과 경계를 저장하고 이동하면서 추가하거나 제거 할 수 있습니다 (아니오 지속적으로 다시 계산해야합니다).
DaveMongoose

2
중요 : 이것은 실제 진단 및 프로파일 링 성능 문제입니까? 그 문제를 작게 설정하면 (실제로 수백 가지 요소입니까?)이 O (n ^ 2) 또는 O (n)이 문제가 될 것이라고 생각하지 않습니다. 모든 프레임에서 실행되지 않는 시스템에서 조기 최적화와 같은 소리가납니다.
Delioth

1
최대 6 개의 이웃이 있기 때문에 간단한 알고리즘은 O (n)입니다.
Eric

답변:


11

알고리즘 찾기는 일반적으로 알고리즘을 쉽게 만드는 데이터 구조로 가장 잘 수행됩니다.

이 경우, 당신의 영토.

영역은 순서가없는 (O (1) 해시) 테두리 및 요소 집합이어야합니다.

영역에 요소를 추가 할 때마다 인접한 타일을 반복하여 경계 타일이어야하는지 확인합니다. 이 경우 요소 타일이 아닌 경우 테두리 타일입니다.

영역에서 요소를 뺄 때마다 인접한 타일이 여전히 영역에 있는지 확인하고 자신이 테두리 타일이되는지 확인합니다. 이것이 빠를 필요가 있다면, 경계 타일이 "인접한 수"를 추적하도록하십시오.

영역에 타일을 추가하거나 제거 할 때마다 O (1) 작업이 필요합니다. 국경을 방문하면 O (테두리 길이)가 걸립니다. 영토에서 요소를 추가 / 제거하는 것보다 "테두리가 무엇인지"를 훨씬 더 자주 알고 싶다면이기는 것입니다.


9

당신이 당신의 영토의 중간에서 구멍의 가장자리를 찾아야하는 경우, 영토 경계 지역의 선형이 우리가 할 수있는 최선입니다. 내부의 타일은 계산해야 할 구멍이 될 수 있으므로 모든 구멍을 찾았는지 확인하기 위해 영역의 경계에있는 영역의 모든 타일을 한 번 이상 살펴 봐야합니다.

그러나 외부 구멍 (내부 구멍이 아닌)을 찾는 데에만 관심이 있다면 좀 더 효율적으로 수행 할 수 있습니다.

  1. 영토를 분리하는 모서리를 찾으십시오. 당신은 이것을 할 수 있습니다 ...

    • (적어도 하나의 영역 타일을 알고 있고 맵에 정확히 하나의 연결된 영역 블롭이있는 경우)

      ... 영토의 임의 타일에서 시작하여지도의 가장 가까운 가장자리로 이동합니다. 이렇게하면 지역 타일에서 비 영토 타일로 전환 한 마지막 가장자리를 기억하십시오. 지도의 가장자리에 도달하면이 기억 된 가장자리가 시작 가장자리입니다.

      이 스캔은 맵의 직경이 선형입니다.

    • 또는 (지역 타일이 어디에 있는지 미리 알지 못하거나지도에 연결이 끊긴 지역이 여러 개있을 수있는 경우)

      ...지도의 가장자리에서 시작하여 지형 타일을 칠 때까지 각 행을 따라 스캔하십시오. 비 지형에서 지형까지 교차 한 마지막 가장자리가 시작 가장자리입니다.

      이 스캔은 지도 영역 에서 최악의 선형 일 수 있지만 (직경의 2 차) 검색을 제한 할 경계가있는 경우 (예를 들어 영토가 거의 항상 중간 행을 교차한다는 것을 알고 있음)이 최악의 상황을 개선 할 수 있습니다. 사건 행동.

  2. 1 단계에서 찾은 시작 가장자리에서 시작하여 지형의 둘레를 따라 시작하여 바깥쪽에있는 각 비 지형 타일을 경계 컬렉션에 추가 한 다음 시작 가장자리로 돌아갑니다.

    이 가장자리를 따르는 단계는 해당 지형이 아니라 지형 외곽 의 둘레 에 선형 입니다. 단점은 가장자리가 취할 수있는 각 종류의 회전을 설명하고 입구에서 이중 계산 테두리 타일을 피해야하기 때문에 코드가 더 복잡하다는 것입니다.

귀하의 예제가 실제 데이터 크기를 몇 자릿수 이내로 대표한다면, 나는 순진한 지역 검색을 원할 것입니다. 이러한 소수의 타일에서는 여전히 엄청나게 빠르며 작성하기가 훨씬 간단합니다. , 이해 및 유지 관리 (일반적으로 버그가 줄어 듭니다!)


7

알림 : 타일이 경계에 있는지 여부는 타일과 그 주변에만 의존합니다.

그것 덕분에:

  • 이 쿼리를 느리게 실행하는 것은 쉽습니다. 예를 들어 : 전체지도에서 경계를 검색 할 필요는없고 보이는 것만 검색하면됩니다.

  • 이 쿼리를 병렬로 쉽게 실행할 수 있습니다. 사실,이 작업을 수행하는 셰이더 코드를 이미지로 만들 수 있습니다. 시각화 이외의 다른 용도로 필요한 경우 텍스처로 렌더링하여 사용할 수 있습니다.

  • 타일이 변경되면 경계가 로컬로만 변경되므로 전체 항목을 다시 계산할 필요가 없습니다.

경계를 미리 계산할 수도 있습니다. 즉, 16 진을 채우는 경우 해당 시점에서 타일이 경계인지 여부를 결정할 수 있습니다. 이는 다음을 의미합니다.

  • 루프를 사용하여 그리드를 채우는 경우 경계를 결정하는 데 사용하는 것과 동일합니다.
  • 빈 그리드로 시작하여 변경할 타일을 선택하면 경계를 로컬로 업데이트 할 수 있습니다.

경계에 대한 목록을 사용하지 마십시오. 정말로해야 할 경우 세트를 사용하십시오 ( 무엇을 원하는지 모르겠습니다. ). 그러나 타일을 경계로 만들거나 타일의 속성이 아닌 것을 확인하는 경우 다른 데이터 구조로 이동하지 않아도 확인할 수 있습니다.


2

타일을 한 타일 위로 이동 한 다음 오른쪽 위, 오른쪽 아래 등으로 이동하십시오. 그런 다음 원래 영역을 제거하십시오.

6 개의 집합을 모두 병합하면 O (n), 정렬 O (n.log (n)), 차이 O (n)을 설정해야합니다. 원본 타일을 정렬 된 형식으로 저장하면 병합 된 집합도 O (n)으로 정렬 할 수 있습니다.

각 타일에 적어도 한 번 액세스해야하기 때문에 O (n) 미만의 알고리즘이 있다고 생각하지 않습니다.


1

방금이 작업을 수행하는 방법에 대한 블로그 게시물을 작성했습니다. 이것은 @DMGregory가 엣지 셀로 시작하여 주변에서 행진하는 것을 언급 한 첫 번째 방법을 사용합니다. 파이썬 대신 C #에 있지만 쉽게 적응할 수 있어야합니다.

https://dillonshook.com/hex-city-borders/


0

오리지널 포스트 :

이 사이트에 대해서는 언급 할 수 없으므로 의사 코드 알고리즘으로 답변하려고합니다.

모든 단일 영토에는 경계의 일부인 최대 6 개의 이웃이 있습니다. 영역의 모든 타일에 대해 6 개의 인접 타일을 잠재적 경계 목록에 추가합니다. 그런 다음 테두리에서 영역의 모든 타일을 빼면 테두리 타일 만 남습니다. 정렬되지 않은 세트를 사용하여 각 목록을 저장하는 것이 가장 효과적입니다. 도움이 되었기를 바랍니다.

편집 간단한 반복보다 훨씬 더 효과적인 방법이 있습니다. 아래의 (지금 삭제 된) 답변에서 진술하려고 시도했을 때 가장 좋은 경우에는 O (1), 최악의 경우에는 O (n)을 달성 할 수 있습니다.

영역 O (1)-O (N)에 타일 추가 :

이웃이없는 경우 새 영역을 만들면됩니다.

이웃이 하나 인 경우 기존 타일에 새 타일을 추가합니다.

이웃이 5 명 또는 6 명인 경우 모두 연결된 것으로 알고 있으므로 기존 지역에 새 타일을 추가합니다. 이것들은 모두 O (1) 연산이며, 새로운 경계 영역을 업데이트하는 것도 O (1)입니다. 왜냐하면 그것은 하나의 세트를 다른 세트와 간단히 병합하기 때문입니다.

2, 3 또는 4 개의 인접 지역의 경우 최대 3 개의 고유 지역을 병합해야 할 수도 있습니다. 결합 된 영역 크기에서 O (N)입니다.

영역 O (1)-O (N)에서 타일 제거 :

이웃이 없으면 영토를 지 웁니다. O (1)

한 이웃과 함께 영토에서 타일을 제거하십시오. O (1)

이웃이 두 개 이상인 경우 최대 3 개의 새로운 영역을 만들 수 있습니다. 이것은 O (N)입니다.

지난 몇 주 동안 간단한 16 진수 기반 영토 게임 인 데모 프로그램을 개발하는 여가 시간을 보냈습니다. 나란히 영역을 배치하여 소득을 높이십시오. Red, Green 및 Blue의 3 명 플레이어는 제한된 게임 필드에 전략적으로 타일을 배치하여 수익을 극대화하기 위해 경쟁합니다.

여기에서 게임을 다운로드 할 수 있습니다 (.7z 형식) hex.7z

간단한 마우스 컨트롤 LMB는 타일을 배치합니다 (호버로 강조 표시된 위치에만 배치 할 수 있음). 상단에, 하단에 소득. 효과적인 전략을 수립 할 수 있는지 확인하십시오.

코드는 여기에서 찾을 수 있습니다 :

이글 / 이글 테스트

소스 코드에서 빌드하려면 Eagle과 Allegro 5가 필요합니다. 둘 다 cmake로 빌드합니다. Hex 게임은 현재 CB 프로젝트로 빌드됩니다.

그 다운 보트를 뒤집어 놓습니다. :)


포함 전에 이웃 타일을 확인하는 것이 결국에는 완전히 제거하는 것보다 약간 빠르지 만 본질적으로 OP의 알고리즘이 수행하는 것입니다.
ScienceSnake

기본적으로 동일하지만 한 번만 빼면 더 효율적입니다
BugSquasher

내 답변을 완전히 업데이트하고 아래의 외적인 답변을 삭제했습니다.
BugSquasher
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.