Ефективний алгоритм для межі набору плиток


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, вона має розмір 91, але межа є лише розміром 36.

Хтось може запропонувати щось краще?

Редагувати:

Щоб відповісти на деякі запитання нижче. Територія може коливатися в розмірах, приблизно від 20 до 100 або близько того. Набір плиток, що утворюють територію, є атрибутом об'єкта, і саме цей об’єкт потребує набору всіх прикордонних плиток.

Спочатку територія створюється як блок, а потім здебільшого набирає плитку по черзі. У цьому випадку вірно, що найшвидший спосіб - це просто зберегти набір кордону та оновити його лише на отриманій плитці. Інколи може відбутися велика зміна території - тому її потрібно буде потім перерахувати повністю.

Я зараз вважаю, що простий алгоритм пошуку кордонів - найкраще рішення. Єдина додаткова складність, яка виникає в цьому, полягає в тому, щоб перерахувати кордон кожного разу, коли це може знадобитися, але не більше того. Я майже впевнений, що це можна зробити надійно в моїх теперішніх рамках.

Що стосується термінів, у моєму теперішньому коді є деякі процедури, які потребують перевірки кожної плитки території. Не кожен виток, але на створення та періодично після цього. Це займає понад 50% часу роботи мого тестового коду, хоча це дуже мала частина повної програми. Тому я хотів мінімізувати повторення. ЗАРАЗ, тестовий код передбачає набагато більше створення об'єктів, ніж звичайне виконання програми (природно), тому я розумію, що це може бути не дуже актуальним.


10
Я думаю, якщо нічого не відомо про форму, алгоритм O (N) видається розумним. Що-небудь швидше зажадає не дивитися на кожен елемент території, який би спрацював, лише якщо ви щось знаєте про форму, я думаю.
amitp

3
Вам, мабуть, це не потрібно робити дуже часто. Також n не дуже великий, набагато менше, ніж загальна кількість плиток.
Триларіон

1
Як створюються / змінюються ці області? І як часто вони змінюються? Якщо вони вибираються плиткою за плиткою, тоді ви можете складати свій список сусідів по мірі свого переходу, і якщо вони часто не змінюються, ви можете зберігати масив територій та їх межі та додавати або видаляти з них під час переходу (так ні потрібно постійно їх перераховувати).
DaveMongoose

2
Важливо: Це актуальна діагностована та профільована проблема продуктивності? З набором проблем, що малий (всього кілька сотень елементів, насправді?) Я не думаю, що це O (n ^ 2) або O (n) має бути проблемою. Здається, що передчасна оптимізація в системі, яка не запускатиметься кожен кадр.
Delioth

1
Простим алгоритмом є O (n), оскільки існує максимум 6 сусідів.
Ерік

Відповіді:


11

Пошук алгоритму, як правило, найкраще проводити зі структурою даних, яка полегшує алгоритм.

У цьому випадку ваша територія.

Територія повинна бути не упорядкованим (O (1) хеш) набором меж і елементів.

Щоразу, коли ви додаєте елемент на територію, ви перебираєтесь на сусідні плитки і бачите, чи вони повинні бути облямівкою; в цьому випадку вони є облямівковою плиткою, якщо вони не є елементарною плиткою.

Щоразу, коли ви віднімаєте елемент з території, ви переконуєтесь, що його сусідні плитки все ще знаходяться на території, і ви бачите, чи варто вам стати прикордонною плиткою. Якщо вам потрібно, щоб це було швидко, попросіть плитки облямівкувати їх "сусіднє число".

Це вимагає роботи O (1) кожного разу, коли ви додаєте або виймаєте плитку на територію чи з неї. Відвідування кордону займає O (довжина кордону). Поки ви хочете знати "що таке кордон" значно частіше, ніж ви додаєте / вилучаєте елементи з території, це має виграти.


9

Якщо вам потрібно знайти краї отворів і в середині вашої території, то ваша лінійна лінія в зоні пов'язаної території - це найкраще, що ми можемо зробити. Будь-яка плитка на внутрішній частині потенційно може бути отвором, на який нам потрібно порахувати, тому нам потрібно хоч раз переглянути кожну плитку в районі, обмеженому контуром території, щоб бути впевненим, що ми знайшли усі отвори.

Але якщо ви переймаєтесь лише пошуком зовнішньої межі (а не внутрішніх отворів), ми можемо зробити це трохи ефективніше:

  1. Знайдіть один край, що відокремлює вашу територію. Це можна зробити за допомогою ...

    • (якщо ви знаєте принаймні одну плитку території та знаєте, що у вас на карті є одна одна підключена територія)

      ... починаючи з довільної плитки на вашій території та рухаючись до найближчого краю вашої карти. Зробивши це, пам’ятайте про останній край, де ви перейшли з плитки на територію до не територіальної плитки. Після натискання на край карти цей запам'ятований край є вашим початковим краєм.

      Це сканування лінійне в діаметрі карти.

    • або (якщо ви не знаєте, де будь-яка з ваших територій плитки заздалегідь, або ваша карта може містити кілька від’єднаних територій)

      ... починаючи з краю вашої карти, скануйте уздовж кожного ряду, поки не потрапите на плитку місцевості. Останній край, який ви перейшли від не місцевості до місцевості, - ваш початковий край.

      Це сканування може бути в гіршому випадку лінійним у зоні карти (квадратичним у діаметрі), але якщо у вас є межі для обмеження пошуку (скажімо, ви знаєте, що територія майже завжди перетинає середні ряди), ви можете покращити цей гірший- випадкова поведінка.

  2. Починаючи з початкового краю, знайденого на кроці 1, дотримуйтесь його по периметру місцевості, додаючи кожну плитку не місцевості зовнішньої сторони до колекції кордонів, поки не повернетесь до початкового краю.

    Цей наступний крок крок є лінійним у периметрі контуру місцевості, а не його площі. Мінус полягає в тому, що код складніший, тому що вам потрібно враховувати кожен вид повороту, який може здійснити край, і уникати подвійного підрахунку граничних плиток на входах.

Якщо ваші приклади є реальними для вашого реального розміру даних приблизно за пару порядків, то я б сам взявся за пошук наївних районів - він все одно буде надзвичайно швидко на такій малій кількості плиток, і істотно простіше написати , розуміти та підтримувати (як правило, призводить до меншої кількості помилок!)


7

Зауважте : чи плитка знаходиться на межі, залежить тільки від неї та її сусідів.

Через те, що:

  • Запросити цей запит ліниво легко. Наприклад: Вам не потрібно шукати межу на всій карті, лише на тому, що видно.

  • Запустити цей запит можна паралельно. Насправді я міг би зобразити якийсь шейдерний код, який це робить. І якщо вам це потрібно для чогось іншого, ніж візуалізація, ви можете переглянути текстуру і використовувати це.

  • Якщо плитка змінюється, межа змінюється лише локально, це означає, що вам не потрібно обчислювати всю справу заново.

Ви також можете попередньо обчислити межу. Тобто, якщо ви заповнюєте шестигранник, ви можете вирішити, чи є плитка в цей момент межею. Це означає, що:

  • Якщо ви використовуєте цикл для заповнення сітки, і це саме ви використовуєте, щоб вирішити, що є межею.
  • Якщо ви почнете із порожньої сітки та виберіть плитки для зміни, то ви можете оновити межу на місцевому рівні.

Не використовуйте список для межі. Використовуйте набір, якщо вам дійсно доведеться ( я не знаю, для чого ви хочете межу. ). Однак, якщо ви робите будь-яку плитку граничною чи не є атрибутом плитки, то вам не доведеться переходити до іншої структури даних, щоб перевірити.


2

Перемістіть область вгору однією плиткою, потім вправо, потім вправо вниз тощо. Після цього видаліть початкову область.

Об'єднання всіх шести наборів повинно бути O (n), сортування O (n.log (n)), встановлення різниці O (n). Якщо оригінальні плитки зберігаються в якомусь відсортованому форматі, об'єднаний набір можна також сортувати в O (n).

Я не думаю, що існує алгоритм, менший ніж O (n), оскільки вам потрібно отримати доступ до кожної плитки хоча б один раз.


1

Я щойно написав допис у блозі про те, як це зробити. Для цього використовується перший метод, про який згадується @DMGregory, починаючи з осередку ребра та йдучи по периметру. Це в C # замість Python, але він повинен бути досить легким для адаптації.

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


0

ОРИГІНАЛЬНА ПОСТ:

Я не можу коментувати цей сайт, тому спробую відповісти алгоритмом псевдокоду.

Ви знаєте, що на кожній території є щонайбільше шість сусідів, які є частиною кордону. До кожної плитки на території додайте шість сусідніх плиток до потенційного списку кордонів. Потім відняти кожну плитку на території від кордону, і вам залишиться лише гранична плитка. Він буде найкращим чином працювати, якщо ви використовуєте не упорядкований набір для зберігання кожного списку. Сподіваюся, я був корисним.

EDIT Є набагато ефективніші способи, ніж проста ітерація. Як я намагався вказати у своїй (зараз видаленій) відповіді нижче, ви можете досягти 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).

Я витратив свій вільний час протягом останніх кількох тижнів, розробляючи демонстраційну програму, яка є простою територією на шестигранній грі. Спробуйте збільшити свій дохід, розмістивши території поруч. 3 гравці, червоний, зелений та синій, змагаються за отримання максимального доходу, стратегічно розміщуючи плитки на обмеженому ігровому полі.

Завантажити гру можна тут (у форматі .7z ) hex.7z

Простий контроль миші LMB розміщує плитку (може розміщуватися лише там, де виділено курсором). Оцінка зверху, дохід знизу. Подивіться, чи можете ви придумати ефективну стратегію.

Код можна знайти тут:

Орел / EagleTest

Щоб створити з вихідного коду, вам потрібні Eagle та Allegro 5. Обидва побудувати за допомогою cmake. Шестигранна гра будується разом із проектом CB.

Переверніть ці голоси вниз головою. :)


Це по суті те, що робить алгоритм в ОП, хоча перевірка сусідських плиток перед включенням трохи швидша, ніж видалення їх усіх наприкінці.
ScienceSnake

Це в основному те саме, але якщо ви
віднімете

Я повністю оновив свою відповідь і видалив зовнішню відповідь нижче.
BugSquasher
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.