Рассмотрим квадрат сетки n на n, который выглядит следующим образом.
Важно отметить, что этот график 11 на 11 .
В любой момент человек стоит на перекрестке, и он движется только вертикально или горизонтально только на один шаг за раз до следующего перекрестка. К сожалению, он выпил немного слишком много, поэтому он выбирает направление, в котором он движется случайным образом, из 4 возможных направлений (вверх, вниз, влево, вправо). Это до 4, как будто он стоит у стены, у него есть только 3 варианта, а в углу у него только 2.
Он начинает в левом нижнем углу, и его цель - добраться до дома, который находится в верхнем правом углу. Время - это просто количество шагов, которое ему требуется.
Тем не менее, вы злой противник, который хочет, чтобы он вернулся домой как можно медленнее. Вы можете удалить любое количество ребер из графика в любой момент во время его ходьбы. Единственное ограничение заключается в том, что вы всегда должны оставить ему какой-то путь домой, и вы не можете удалить ребро, которое он уже использовал.
Задача состоит в том, чтобы придумать как можно более злобного противника и затем протестировать его на графике
100 на 100 на20 на 20 со случайным пьяным ходоком. Ваша оценка - это просто среднее время, которое требуется случайному ходячему, чтобы добраться домой за101000 пробежек.
Вы можете использовать любой язык и библиотеки, которые вам нравятся, если они свободно доступны и легко устанавливаются в Linux.
Что мне нужно реализовать?
Вы должны реализовать код для случайного обходчика, а также для злоумышленника, и код должен быть скомбинирован так, чтобы результат при запуске был просто средним значением 1000 прогонов с использованием вашего кода противника. Случайный код Уокера должен быть очень простым для написания, поскольку он просто выбирает из (x-1, y), (x + 1, y), (x, y-1) и (x, y + 1), убедившись, что ни один из них не был удален или находится вне диапазона.
Код противника, конечно, сложнее, и ему также необходимо помнить, какие грани пьяница уже преодолел, чтобы он не пытался удалить ни одну из них, и чтобы убедиться, что для пьяницы все еще есть путь домой, что немного сложнее. делать быстро.
Addendum 10 пробегов на самом деле недостаточно, но я не хотел наказывать людей, которым удалось действительно долго гулять. Я теперь увеличил его до 1000 из-за популярного запроса. Однако, если ваша прогулка настолько длинная, что вы не можете выполнить 1000 пробежек за реалистичное время, просто укажите максимальное количество пробежек, которое вы можете.
Таблица рекордов 100 на 100.
- 976124,754 от оптимизатора.
- 103000363.218 Питера Тейлора.
Изменить 1. Изменен размер графика до 20 на 20, чтобы помочь время выполнения тестов людей. Я сделаю новый высокий балл за этот размер по мере того, как люди отправляют результаты.
Таблица рекордов 20 на 20.
230,794.38 (100k runs) by justhalf
227,934 by Sparr
213,000 (approx) by Peter Taylor
199,094.3 by stokastic
188,000 (approx) by James_pic
64,281 by Geobits
Ответы:
230 794,38 на 20х20, 100 000 пробежек
Последнее обновление: я наконец-то создал идеальное динамическое решение с двумя путями. Я сказал «отлично», так как предыдущая версия на самом деле не симметрична, было проще получить более длинный путь, если пьяница прошел один путь над другим. Текущий симметричен, поэтому он может получить большее ожидаемое количество шагов. Похоже, что после нескольких испытаний он составляет около 230 тыс., По сравнению с предыдущим, около 228 тыс. Но, говоря статистически, эти цифры все еще находятся в пределах их огромного отклонения, поэтому я не утверждаю, что это значительно лучше, но я считаю, что это должно быть лучше, чем в предыдущей версии.
Код находится внизу этого поста. Он обновлен так, что он намного быстрее, чем предыдущая версия, завершая 1000 запусков за 23 секунды.
Ниже приведен пример запуска и образца лабиринта:
Предыдущие представления
Наконец-то я могу сравниться с результатом Спарра! = D
Основываясь на моих предыдущих экспериментах (см. Нижнюю часть этого поста), лучшая стратегия состоит в том, чтобы иметь двойной путь и закрывать один, когда пьяница достигает любого из них, и переменная зависит от того, насколько хорошо мы можем динамически предсказать, куда пойдет пьяница. увеличьте шанс его попадания на более длинный путь.
Поэтому, основываясь на своей
DOUBLE_PATH
стратегии, я построил еще одну, которая меняет лабиринт (мойDOUBLE_PATH
лабиринт легко изменялся) в зависимости от движения пьяницы. Поскольку он выбирает путь с более чем одним доступным вариантом, я закрою пути, чтобы оставить только два возможных варианта (один, из которого он пришел, другой - неисследованный).Это звучит похоже на то, чего достиг Спарр, как показывает результат. Разница с ним слишком мала, чтобы его можно было считать лучше, но я бы сказал, что мой подход более динамичен, чем у него, поскольку мой лабиринт более изменчив, чем Спарр =)
Результат с образцом конечного лабиринта:
Секция экспериментов
Лучшей оказывается та же стратегия, что и в Stokastic, я горжусь экспериментированием с использованием различных стратегий и выводом хороших результатов :)
Каждый из печатных лабиринтов ниже является последним лабиринтом после того, как пьяница добрался до дома, поэтому они могут немного отличаться от бега к бегу из-за случайности в движении пьяницы и динамичности противника.
Я опишу каждую стратегию:
Единый путь
Это самый простой подход, который создаст единый путь от входа до выхода.
Остров (уровень 0)
Это подход, который пытается поймать пьяницу на почти изолированном острове. Не работает так, как я ожидал, но это одна из моих первых идей, поэтому я включил ее.
Есть два пути, ведущие к выходу, и когда пьяница приближается к одному из них, противник закрывает его, заставляя его найти другой выход (и, возможно, снова попадает в ловушку на острове)
Двойной путь
Это наиболее обсуждаемая стратегия, которая состоит в том, чтобы иметь два пути равной длины к выходу и закрыть один из них, когда пьяница приближается к одному из них.
Остров (уровень 1)
Вдохновленные множеством путей острова и большим количеством пешеходных дорожек в одном пути, мы соединяем остров с выходом и делаем лабиринт по одному пути на острове, создавая в общей сложности три пути для выхода и, как и в предыдущем случае, закрываем любой из выход, как пьяница приближается.
Это работает немного лучше, чем чистый единственный путь, но все еще не побеждает двойной путь.
Остров (уровень 2)
Пытаясь расширить предыдущую идею, я создал вложенный остров, создав в общей сложности пять путей, но, похоже, он работает не так хорошо.
Остров (уровень 3)
Заметив, что двойной путь на самом деле работает лучше, чем один путь, давайте сделаем остров двойным путем!
Результатом является улучшение по сравнению с островом (уровень 1), но оно по-прежнему не преодолевает двойной путь.
Для сравнения, результат для двойной траектории размером с остров составляет в среднем 131 134,42 хода. Так что это добавляет довольно значительное количество ходов (около 40 тыс.), Но этого недостаточно, чтобы пройти двойной путь.
Остров (уровень 4)
Опять же, экспериментируя с вложенным островом, и снова это не так хорошо работает.
Вывод
В общем, это доказывает, что лучше всего иметь один длинный путь от текущей позиции пьяницы до выхода, что достигается стратегией двойного пути, поскольку после закрытия выхода пьяница должна пройти максимально возможное расстояние, чтобы добраться до выход.
Это также намекает на то, что базовой стратегией должен быть двойной путь, и мы можем только изменить то, как динамически создаются пути, что было сделано Sparr. Поэтому я верю, что его стратегия - это путь!
Код
источник
227934 (20х20)
Моя третья попытка Использует тот же общий подход, что и @stokastic с двумя путями к выходу. Когда ходок достигает конца одного пути, он закрывается, требуя, чтобы он вернулся, чтобы добраться до конца другого пути, чтобы выйти. Мое улучшение состоит в том, чтобы генерировать пути по мере продвижения ходящего, так что какой бы путь он ни продвинулся дальше в первой половине процесса, он оказался бы длиннее, чем другой путь.
вывод (со временем):
Пример моего лабиринта, примерно равной длины, равной половине пути, показывающей левый / нижний путь, отрезанный от выхода (внизу справа):
PS: я знаю об очень незначительном улучшении этого алгоритма, который требует более умного кода, чтобы генерировать различную форму для двух путей, лестниц вместо последовательных зигзагов высоты.
источник
135 488 307,9 для 98х98
199094,3 для 20x20
Я реализовал решение, которое создает два пути к финишу и закрывает ровно один из них, как только пьяница достигает его. Это имитирует длину пути, которая, по крайней мере, будет в 1,5 раза больше длины одного пути от начала до конца. После 27 пробежек я набрал в среднем около 135 миллионов. К сожалению, это занимает несколько минут на прогулку, поэтому мне придется запустить его в течение следующих нескольких часов. Одно предостережение - мой генератор двойного пути работает только в том случае, если размер графика находится в форме 4 * n + 2, что означает, что самое близкое к 100 значение, которое я могу получить, это 102 или 98. Я собираюсь опубликовать результаты, используя 98, что, как я ожидаю, все еще превосходить метод зигзагообразного пути. Я буду работать над лучшей системой маршрутизации позже. В настоящее время выводит результаты в виде (numSteps, currentAverage) после каждой прогулки.
РЕДАКТИРОВАТЬ: исправлено, поэтому код теперь работает с размерами графа, кратными 2, а не 4 * n + 2.
Код: (добавьте аргумент 'True' в конструктор ходунка в строке 187 для рисования графика черепахой).
Необработанные данные: (текущий numSteps, скользящее среднее)
источник
Подход с 4 путями, 213 КБ
Односторонний подход
и набирает в среднем
N^2
.Двухсторонний подход
но затем в первый раз, когда пьяница оказывается в пределах досягаемости от конечной точки, его режут:
Это оценка в среднем
(N/2)^2 + N^2
.Подход с четырьмя путями использует два сокращения:
Предположим, что длина внешнего цикла равна длине
xN
внутреннего цикла(1-x)N
. Для простоты я приведу в нормуN=1
.От начала до первого среза баллы в среднем составляют
(x/2)^2
. От первого реза до второго реза имеет два варианта длиныx
и1-x
; это дает в среднем(1-x)x^2 + x(1-x)^2 = x-x^2
. Наконец оставшийся путь дает1
. Таким образом, общий счетN^2 (1 + x - 3/4 x^2)
.Первоначально я предполагал, что сохранение доступных путей одинаковой длины на каждом шаге будет оптимальным, поэтому мой первоначальный подход
x = 1/2
основывался на оценке1.3125 N^2
. Но после проведенного выше анализа выясняется, что оптимальное разделение дается, когдаx = 2/3
с оценкой1.3333 N^2
.с кодом
источник
f
к доc
в вашем коде примерноN/2
, черезe
(иd
) или нет, верно?N^2
не так2^N
. И да, создание этой динамики сделает ее лучшей, проблема в том, как сделать ее динамичной, сохраняя при этом свойство с четырьмя путями. @PeterTaylor: красивые картинки!Я экспериментировал с разрезанием сетки почти целиком по всем
k
строкам. Это эффективно превращает его в нечто похожее на случайное блужданиеk
поN * N/k
сетке. Самый эффективный вариант - разрезать каждый ряд так, чтобы мы заставили пьяницу зигзагообразно.Для случая 20x20 (
SIZE=19
) у меня естьс кодом
источник
Для тех, кто не хочет изобретать велосипед
Не волнуйся! Я изобрету это для тебя :)
Это на Java, кстати.
Я создал класс Уокера, который занимается случайной ходьбой. Он также включает в себя полезный метод определения того, является ли ход действительным (если он уже пройден).
Я предполагаю, что все вы, умные люди, можете найти случайные числа для конструктора, я оставил это на ваше усмотрение, чтобы вы могли проверить определенные случаи. Кроме того, просто вызовите функцию walk (), чтобы (как вы уже догадались!) Совершить прогулку пьяницы (случайным образом).
Я реализую функцию canComeHome () в другой раз. Желательно после того, как я найду лучший способ сделать это.
источник
previouslyTraveled.add(new int[]{x,y,move[0],move[1]})
должно бытьx+move[0]
иy+move[1]
.Width-1
ИHeight-1
, и неэффективность проверки удалённых путей. Я отредактировал ваш код (с дополнительной функцией для печати лабиринта). Не стесняйтесь откат, если вы считаете, что это неуместно.Edge
не правильно реализуетеComparable<Edge>
. Если вы хотите, чтобы ребра сравнивались как равные, даже если вы перевернули их, вам необходимо принять во внимание и обращение в неравном случае. Самый простой способ сделать это - изменить конструктор, чтобы сохранить упорядоченные точки.Comparable
?A
иB
те же ребра обращены иC
отличаются, вы можете получить,A.compareTo(B) == B.compareTo(A) == 0
ноA.compareTo(C) < 0
иB.compareTo(C) > 0
.canComeHome()
)64281
Обновление, поскольку сетка была изменена с 100x100 на 20x20 (1000 тестов). Оценка по 100x100 (100 тестов) была примерно 36M.
Хотя это не будет лучше, чем 1D, я хотел поиграть с моей идеей.
Основная идея состоит в том, что сетка разделена на квадратные комнаты, и только один путь ведет «домой» от каждого. Открытый путь зависит от того, какой пьяный приближается к последнему , что означает, что он должен исследовать каждый возможный выход, только чтобы всем, кроме одного, ударить его по лицу.
Поиграв с размерами комнаты, я пришел к тому же выводу, что и Питер: лучше нарезать его на мелкие. Лучшие оценки приходят с размером комнаты 2.
Код небрежный, не обращайте внимания на беспорядок. Вы можете нажать на
SHOW
переключатель, и он будет показывать изображение путей на каждомSHOW_INT
шагу, чтобы вы могли наблюдать его в действии. Завершенный пробег выглядит примерно так:(Это изображение из предыдущей сетки 100x100. Точно так же, как и 20x20, но меньше. Код ниже был обновлен для новых размеров / прогонов.)
источник
188К, с 2 дорожками
Все лучшие записи, кажется, используют подход, состоящий в том, чтобы генерировать 2 пути, а затем отрезать один, когда пьяница приближается к концу пути. Я не думаю, что смогу побить вступление Джастхалфа, но я не мог не задаться вопросом: почему 2 пути? Почему не 3, или 5, или 20?
TL; DR : 2 пути кажутся оптимальными
Итак, я сделал эксперимент. На основе фреймворка Stretch Maniac я написал статью для проверки различных путей. Вы можете настроить
featureSize
параметр, чтобы варьировать количество путей. AfeatureSize
из 20 дает 1 путь, 10 - 2 пути, 7 - 3, 5 - 4 и так далее.Есть несколько оптимизаций, которые я мог бы сделать, но не сделал, и он не поддерживает ни одного адаптивного трюка, который использует justhalf.
Во всяком случае, вот результаты для различных
featureSize
значений:И вот карта с 3 путями:
источник
N
будет длиной пути (то естьn^2-1
), один путь в среднем потребуетN^2
перемещений, тогда как три пути(N/3)^2 + (2N/3)^2 + (2N/3)^2 = N^2
плюс некоторое относительно небольшое значение, поэтому три пути не имеют существенного усиления по одиночному пути, не говоря уже о двойном пути. (Расчет основан на вероятностном результате, который гласит, что для случайного перемещения по одномерной траектории длиныN
требуется в среднемN^2
перемещение от одного конца к другому.)131k (20x20)
Моя первая попытка состояла в том, чтобы удалить все горизонтальные края, кроме верхнего и нижнего ряда, затем каждый раз, когда ходок достигал нижней части столбца, я удалял край впереди него, пока он не посетил дно каждого столбца и, наконец, быть в состоянии добраться до выхода. Это привело в среднем к 1/8 столько же шагов, сколько и к 1-му ходу @ PeterTaylor.
Затем я решил попробовать что-то более крутое. Я разбил лабиринт на серию вложенных полых шевронов и требую, чтобы он пересек периметр каждого шеврона не менее 1,5 раз. Это имеет среднее время около 131 тыс. Шагов.
источник
Ничего не делать
Поскольку человек движется случайным образом, можно подумать, что удаление любого узла только увеличит его шансы на возвращение домой в долгосрочной перспективе.
Во-первых, давайте посмотрим на одномерный случай, это может быть достигнуто путем удаления узлов, пока вы не получите волнистый путь, без затуханий или циклов, который посещает (почти) каждую точку сетки. На
N x N
сетке максимальная длина такого пути равнаL = N*N - 2 + N%2
(98 для сетки 10х10). Ходьба по дорожке может быть описана матрицей перехода, генерируемойT1d
.(Небольшая асимметрия затрудняет поиск аналитического решения, за исключением очень маленьких или бесконечных матриц, но мы получаем численное решение быстрее, чем это потребовалось бы для диагонализации матрицы в любом случае).
Вектор состояния имеет единицу
1
в начальной позиции и послеK
шагов(T1d**K) * state
дает нам распределение вероятностей нахождения на определенном расстоянии от начала (что эквивалентно усреднению по всем2**K
возможным прогулкам по пути!)Запуск симуляции для
10*L**2
шагов и сохранение последнего элемента вектора состояния после каждого шага, что дает нам вероятность того, что мы дойдем до цели после определенного общего количества шагов - накопленного распределения вероятностейcd(t)
. Дифференцирование дает нам вероятностьp
достижения цели точно в определенное время. Чтобы найти среднее время, которое мы интегрируем,t*p(t) dt
среднее время достижения цели пропорционально
L**2
коэффициенту, который очень быстро возрастает до 1. Стандартное отклонение почти постоянно и составляет около 79% от среднего времени.Этот график показывает среднее время достижения цели для разных длин пути (соответствует размерам сетки от 5x5 до 15x15)
Вот как выглядит вероятность достижения цели. Вторая кривая выглядит заполненной, потому что на каждом нечетном временном шаге позиция нечетна и, следовательно, не может достигать цели.
Из этого мы видим, что сбалансированная стратегия двойного пути работает здесь лучше всего. Для более крупных сеток, где затраты на создание большего числа путей незначительны по сравнению с их размером, мы могли бы лучше увеличить число путей, подобно тому, как это описал Питер Тейлор, но сохраняя сбалансированную длину
Что если мы вообще не удаляем какие-либо узлы?
Тогда у нас будет вдвое больше проходных узлов, плюс четыре возможных направления вместо двух. Казалось бы, из-за этого вряд ли удастся когда-либо добраться. Тем не менее, результаты моделирования показывают обратное: после всего лишь 100 шагов по сетке 10х10 человек, скорее всего, достигнет своей цели, поэтому ловить его на островах - тщетная попытка, поскольку вы торгуете по потенциально
N**2
длинному извилистому пути со средним временем завершенияN**4
для остров, который проходит поN**2
ступенямисточник