Задать вопрос
Ответы пользователя по тегу Алгоритмы
  • Можно ли сравнить большие массивы по частям?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    А зачем вам это делать частями? Что вы хотите этим добиться?
    Ваша задача имеет сложность О(N) и не представляет никакой сложности, просто двигайтесь двумя курсорами синхронно по массивам и всё.
    Ответ написан
    4 комментария
  • Как составить список уникальных комплексных решений для уравнения? Как понять что число 0.999999 то же что 1.0000001?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Сравнение нужно делать с некоторой точностью. Получите разность между сравниваемыми значениями и сравнивайте модули с пороговым.
    In [8]: a=0.00001-0.99999j; b=-0.00001-1.000001j
    
    In [9]: abs(a-b)
    Out[9]: 2.2825424420965077e-05

    Вот этот модуль можете сравнить с 1e-4, если меньше, то считаем что числа равны.
    Ответ написан
    3 комментария
  • Как реализовать идеальный метод indexOf?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вы неверно понимаете суть О-нотации. Почитайте книги Дональда Кнута про это.
    O(3) - это то же самое, что O(1). Нет разницы. O(N), O(N+1000), O(10*N) - это тоже одно и то же.
    В таких случаях речь всегда идёт не про конкретный кейс, а про обобщенный. Вы не знаете в каком порядке элементы вашего массива, где находится искомый, сколько всего элементов будет в конкретных кейсах, поэтому определяется ряд случаев: средний (по вероятности, если входные данные рандомные), худший (чтобы понимать границы и сколько может "висеть" алгоритм теоретически). Лучшие варианты обычно никого не интересуют, потому что и вероятность их мала, и смысла никакого нет в столь малых величинах.

    У вас типичный случай компромисса в реализации структуры данных. Вы всегда балансируете между памятью и скоростью. Больших семь шапок из овцы не выкроить никак.
    То есть, вы можете сделать такую структуру данных, которая "под капотом" будет держать древовидный индекс с данными или отсортированную по ключу карту значений для бинарного поиска. Хотя эти варианты - суть одно и то же.
    Если не рассматривается вариант размена производительности на память, то в этой задаче у вас будет только O(N) без вариантов.
    Если усложнить структуру данных, то можно добиться и O(logN) при поиске, и даже O(1). Почитайте как устроен словарь в питоне.

    Да, помимо сложности поиска у вас будет сложность вставки в структуру новых элементов. И тут опять трейд-офф. Ну а что вы хотели?
    Ответ написан
    3 комментария
  • Как ускорить решение задачи "Детский праздник"?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вижу один ответ уже пометили как правильный, но ворвусь сюда со своею лептой.
    Очевидно, что при огромных значениях m и малых допуска по времени тут от решения ожидается какая-то радикальная оптимизация, а не полный перебор по одному шарику.

    А оптимизировать можно вот так.
    Давайте сперва представим, что надувальщикам не надо отдыхать и они шпарят каждый со своею скоростью шарики.
    Тогда можно сложить производительность каждого (1/t[i] -- шаров в минуту) и в сумме получим общую производительность толпы из N надувальщиков. Посчитать время теперь можно за один шаг по формуле.
    Теперь вспоминаем, что разные надувальщики будут в разное время отдыхать.
    Очевидно, что всё время их работы разобьётся на сегменты с различной производительностью, ведь надувают в рамках каждого сегмента разные надувальщики с разной собственной скоростью.
    Итак, первый сегмент будет с полной суммарной производительностью всех надувальщиков и длиться он будет вот сколько времени в минутах: T[j]=min(t[i]*z[i] for i in range(n)), но надуют они за это время много шаров с общей суммарной производительностью. В момент T[j] начнётся новый сегмент, в котором одного надувальщика не будет, он ушел на отдых и этот отдых будет длиться какое-то время r. Для следующего сегмента нужно посчитать новую производительность всех оставшихся участников. Можно из предыдущей производительности вычесть производительность выбывшего. Новый сегмент будет длиться, скорее всего, меньше. Нужно взять минимум между оставшимися временами работы оставшихся надувальщиков и временем r. То есть новый сегмент будет не длиннее r и продлится пока не выдохнется следующий из оставшихся надувальщиков. Так в каждом сегменте будет свой набор и своя производительность, каждый сегмент надует своё число шаров и как только оно перевалит за m нужно посчитать остаток последнего сегмента и сумма длин сегментов будет ответом.
    Ответ написан
  • Как определить игрока быстрее всех нажавшего кнопку (web)?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вам, похоже, не нужно какое-то абсолютное время. Достаточно отправить игроку сигнал и замерить локальное время реакции на этот сигнал по времени браузера игрока. Браузер игрока знает локальное время, знает когда пришел сигнал с сервера и знает когда отреагировал пользователь. Осталось отправить на сервер локальное время регистрации пришедшего сигнала от сервера и время реакции игрока.

    Минус у схемы только один, через отладочную панель можно жульничать.
    Чтобы жульничать было труднее, можно заставить клиенты подключиться к веб-сокету сервера и сигнал с реакцией прокидывать через него. Это тоже не убережет от жульничества с автоматизацией реакции.
    Наверно самое безопасное в плане жульничества решение потребовало бы стриминга видеопотока с физической реакцией игрока, а все подозрительные случаи выборочно потом проверять и модерировать людьми покадрово на предмет подделки видеосигнала.
    Ответ написан
    Комментировать
  • Почему код разбиения на слагаемые не работает?

    trapwalker
    @trapwalker Куратор тега Python
    Программист, энтузиаст
    Во-первых, "слагаемые".
    Во-вторых, почитайте код вашей рекурсивной функции, он выдаёт количество, а не сами варианты сложения. Если не можете это понять из кода, то вам рано такие задачи. Серьёзно.
    А ошибка у вас из-за того, что такой алгоритм упирается в размер максимальной глубины рекурсии. Если хотите такие значения считать, делайте итеративный алгоритм, а не рекурсивный. Если эти слова тоже для вас новые, то надо браться за учебник, эта задача слишком сложна будет для вас.
    Успехов.
    Ответ написан
    Комментировать
  • Как сделать эффективный алгоритм перебора всех возможных значений?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Опишите функцию получения следующего значения после предыдущего (функция инкремента строки).
    Для этого нужно просто заменить последний разряд на следующее его значение, а если строка пустая, то вернуть "а".
    Если произошло переполнение, то выполняем функцию инкремента к строке без последнего символа, и результат конкатенируем с "а".

    Такому алгоритму не нужно памяти сверх необходимого для хранения одной последовательности, плюс небольшая константа для переменных. В общем, инкрементация в среднем за O(log_M(N)), где M - размер алфавита, а N - число знаков.

    У вас, кстати, еще ошибка:
    То есть в диапазон должно входить 3^2=9 значений,

    3^2+3^1
    Ответ написан
  • Имеются ли какие алгоритмы оптимизации точек на карте?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    А что тут вообще сложного? Просто находите точки, угол между ребрами в которых ближе всего к 180 градусов. Их можно удалять. Угол между векторами считается просто, легко погуглить.
    Еще можно отправить ключевые точки в Построитель маршрута типа osrm и он выдаст красивую геометрию маршрута по дорогам.
    Ответ написан
    Комментировать
  • Как в игровых движках реализованы отскоки?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    А что вам не понравилось в приведенных формулах? Они совсем не сложные.
    Правда в векторной форме записать закон сохранения импульса можно гораздо компактнее, а некоторые языки вроде питона позволили бы в комплексных числах записать формулу прямо в векторной форме для плоского случая. Но можно записать и так, как здесь приведено у вас.

    У вас тут довольно простой случай столкновения точечных масс, но в физических движках обычно коллизии предусматривают более натуральные взаимодействия с вклчением вращения, как отдельной степени свободы. В этом случае играют важную роль размеры, формы и координаты центов масс сталкивающихся объектов. В этом случае формулы будут посложнее, но дифуры также по-прежнему считать не обязательно. Просто закон сохранения импульса и аккуратный учет всех составляющих.
    Оишите подробнее вашу задачу, потому что сейчас это можно сделать совсем просто, как в пинболле, или сложно с гравитацией и через дифуры.
    Ответ написан
    2 комментария
  • Задача на геометрию. Как быстро найти подходящую выборку элементов из матрицы?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Зачем искать расстановку, если этого не требуется по условию задачи?
    Представим, что мы знаем самую оптимальную расстановку жуков на окружности.
    Нужно найти время, когда все жуки займут свое место, и это время будет не ранее, чем последний жук займёт свое место, то есть жук, которому идти самый длинный путь.
    Таких жуков может быть несколько. В одном из частных случаев все жуки в 0.0 и им всем идти R секунд до своего места, а займут они свое место одновременно. В этом случае не важно какого жука мы выберем последним.

    Итак, у нас есть жук, которому ползти максимальное время до своей точки на круге. Для каждого жука можно найти самую дальнюю точку окружности. Последним жуком будет (скорее всего) тот, который ближе всего к своей самой дальней точке.
    На каждых 1\N радианах дуги окружности должна быть одна финальная точка.
    Нужно найти самую дальнюю дугу длиной R/N от всех жуков, а потом жука, который ближе всех к одному из краёв этой дуги. Остальные жуки уж точно доползут до своих точек быстрее этого.
    Ответ написан
  • Как определить коллизию квадратов?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Алгоритмов тут за вас никто придумывать не будет. Этот ресурс не для этого.
    Учитесь решать такие задачи начиная с более простых.
    Двухмерный случай с квадратами можно упростить понизив размерность задачи.
    Представьте, что у вас не квадраты, а отрезки и не на плоскости, а на оси.
    Нужно сформулировать булево выражение, которое будет истинно только в случае наложения отрезков (хотя бы частичного).
    Вам уже предлагали в комментариях попробовать представить квадраты не размерами, а координатами границ. Если у вас возникнут трудности и с вычислением координат правых нижних углов, то у меня для вас плохие новости...
    Попробуйте решить задачу для отрезков на оси, а потом подумать как расширить её для квадратов на плоскости.
    Если на этом этапе ещё не очевидно решение, то начинать следует с более простого. Хотя, казалось бы, куда уж проще.
    Ответ написан
    Комментировать
  • Как переставляя столбцы и строки матрицы, переместить самый большой элемент в верхний левый угол?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вы не уточнили, что понимается под словом "переставляя". Можно ли скажем, переставить первую и пятую строку местами? Или может быть можно, скажем, пятую строку поставить в начало...
    А может быть под "переставляя" имеется в виду перестановку рядом стоящих строк местами?
    В любом случае имеет смысл сперва найти самый ольшой элемент в матрице, а потом переставлять строки, чтобы он всплыл в угол.
    В чем конкретно у вас проблема? Если вы хотите готовый алгоритм, то этот ресурс не для этого. а на конкретные вопросы тут с удовольствием ответят и помогут.
    Ответ написан
    3 комментария
  • Какой алгоритм использовать для подбора ингредиентов по составу?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Выглядит как NP-полная задача, чем-то напоминающая задачу укладки многомерного рюкзака или n--мерную задачу раскроя.
    Но ваши ограничения в 10 компонентов позволяют решать эту задачу перебором. Тем более для кулинарии допустимо не слишком упарываться с точностью и ввести квантиикацию компонентов, чтобы иметь дело с целочисленными пропорциями.

    Кстати, я так понимаю, что во всех ингридиентах и результирующем блюде должен быть ещё какой-то специальный ингридиент Z, который будет дополнять выделенные компоненты (например БЖУ) до 100%. Типа инертный наполнитель.
    Допустим у вас есть полный перечень из N доступных ингридиентов. записанных в виде многомерных векторов.
    Вам нужно взять из него подмножество M <= 10 и просуммировать с коэффициентами так, чтобы получилось равенство. Найти нужно x1, x2, x3...x10 - коэффициенты.

    Фактически получится одно векторное уравнение с множеством переменных. А задача сводится к векторному преобразованию из пространства компонентов ингридиентов в подпространство ингридиентов.
    Аналитически такое не решается. Да и точного решения не получится в принципе, поэтому нужно будет придумать как строить норму отклонения от пропорции и минимизировать эту норму.

    Решать можно генетическими алгоритмами или роевыми.
    Фитнес функцией будет норма отклонения от пропорции.
    Ответ написан
  • Доказано ли, и можно ли сжать произвольные данные до 20 байтов к примеру?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вы на свой вопрос можете ответить и сами, если посчитаете.
    Произвольные данные не сжимаются. Вообще.
    Доказываем на пальцах. Для детей.
    1. Давайте получим произвольные данные. Для этого возьмём монетку и подбросим 10 раз. Таким образом мы получим 10 бит произвольных данных.
    2. Доказываем, что эти 10 бит нам не сжать даже до 9 бит.
    Сколько всего бывает разных 10-битных последовательностей? Два в степени 10, то есть 1024.
    Сколько всего бывает разных 9-битных последовательностей? Два в степени 9, то есть 512. Это значит, что 9-битным числом можно как-нибудь закодировать только половину произвольных 10-битных последовательностей. Ни одного бита сэкономить не удалось для действительно произвольных (читай случайных) данных.

    -- А как же работают архиваторы? - спросите вы.
    А архиваторы сжимают не произвольные данные, а какие-то осмысленные. Осмысленных данных меньше чем любых. Это очевидно. Вот архиваторы этим и пользуются.
    Давайте пример. Допустим наша "монетка" умеет падать не на две стороны, а на три. Ну циллиндрик такой. толстенький, который частенько падает на ребро. И мы его подбросили пять раз, но хотим почему-то записать полученную последовательность в вдоичной форме. Очевидно, что двумя значениями 1 и 0 мы не можем закодировать три стороны "монетки" (назовём её тринеткой). А два бита может кодировать уже четыре разных состояния: 00, 01, 10, 11. Нам хватит трёх из них, а четвертое, скажем 11, пусть будет ненужным.
    Тогда 5 бросков тринетки мы можем записать 10 двоичными битами. Но данных в этих 10 битах будет на самом деле храниться только 3 в степени 5 = 3*3*3*3*3=243. То есть 243 состояния тринетки кодируются 10 битами, в которые помещается на самом деле 1024 разных произвольных значений.
    Это как раз и есть то место, где можно успешно сжать данные.

    А насколько можно сжать? Давайте считать. 8 бит может представить 2*2*2*2*2*2*2*2=256 разных произвольных значений. А нам надо 243. Это значит, что любые 5 бросков тринетки мы можем закодировать не 10-ю битами, а всего лишь 8-ю. Сэкономили два бита, но больше сэкономить не получится ни одного бита!

    А откуда ж берутся огромные коэффициенты сжатия? Например тексты на человеческих языках довольно неплохо сжимаются. А всё просто. Мы кодировали каждый бросок тринетки для простоты 2-мя битами, и там одно сочетание было нам не нужно. А представьте, если бы нам не хотелось бы возиться с битами и мы посчитаи более простым хранить броски тринетки каждый в одном байте!
    То есть нам бы хватило и 2 бит слихвой, но м ырешили на каждый бросок использовать 8 только потому, что у нас компьютеры не сильно приспособлены быстро работать с более мелкими кусками данных.
    Получается, что 5 бросков тринетки мы закодируем в 8*5байт=40бит, а мы уже уяснили что и 10 бит хватило бы, и даже 8, но никак уж не меньше восьми.
    Получается, что расточительную 40-битную запись пяти бросков тринетки мы могли бы сжать аж в 5 раз до 8 бит! То есть мы используем 5 байт там, где хватило бы одного, вот и получилось, что можно сделать сжатие.

    Также и маленький рандомный сид может кодировать террабайтные миры в играх. Но соль в том, что миров этих разных получится ровно столько. сколько значений сида может быть. Мы просто в террабайтах храним мир в очень неэффективной но удобной для использования форме.

    Расписал как мог просто. Если у вас после этого остались иллюзии, что можно сжать гигабайтный фильм до одного байта, а потом распаковать его обратно, то вам уже ничто не поможет. Потому что фильмов человечество наснимала уже гораздо больше 256, а значений одного байта всего как раз столько.
    Вы можете написать архиватор, который будет сжимать произвольный фильм из ваших любимых 256 фильмов. Но при этом сам архиватор будет размером 256 гигабайт, а байт будет просто номером в библиотеке ваших любимых фильмов. Но да, вполне реально назвать вашему другу по телефону тремя байтами три любых фильма из вашей общей коллекции, а у друга на винте такой же "архиватор", как бы, "распакует" эти числа в целые три фильма, которые на самом деле и так уже были у друга.

    Других чудес не бывает.
    Ответ написан
  • Как расчитать координаты точек для шестиугольника вписанного в круг?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    А мне нравится такое на комплексных числах считать. То же, что и с синусами, но элегантнее.
    Вот пример на питоне:
    from math import e, pi
    f=lambda c, r, n=3, fi0=0: [c+r*e**(1j*(2*pi/n*i+fi0)) for i in range(n)]

    Получим функцию f которая может рассчитать точки любого вписанного n-угольника:
    >>> f(c=250+250j, r=250)
    [(500+250j),
     (375+466.50635094610965j),
     (125.00000000000006+466.5063509461097j),
     250.00000000000003j,
     (124.99999999999989+33.49364905389041j),
     (374.99999999999983+33.49364905389024j)]

    А если надо обяательно кортежами, а не комплексными точками. то вот:
    >>> [(round(p.real), round(p.imag)) for p in f(c=250+250j, r=250, n=5, fi0=pi/2)]
    [(250, 500), (12, 327), (103, 48), (397, 48), (488, 327)]

    Тут, заметьте, пятиугольник, причем вершинкой вниз (при оси Y, направленной вниз).

    Чтобы было понятно как это работает...
    Представим, что центр окружности в нуле координат. Нам нужно 6 точек, смещенных относительно нуля на радиус под нужными углами: 0, 60, 120, 180, 240 и 300 градусов. В формулах мы. конечно используем радианы: pi - это 180 градусов.
    Чтобы повернуть единичный вектор на какой-то угол, нужно его просто домножить на e в степени мнимая единица, умноженная на угол. Поскольку единичный вектор на комплексной плоскости это просто 1, то его даже писать не надо. Просто возводим e в нужную степень и получаем нужный вектор в виде комплексного числа. Осталось его домножить на требуемый радиус (он при этом удлинится: был длиной 1, а станет r) и добавить к нему желаемый центр (тоже в виде комплексного числа, где реальная часть - X, а мнимая - Y).
    Вот и всё!
    Красота же?..

    Да, забыл сказать, что если нужно повернуть весь n-угольник на какой-то угол, то для этого есть там параметр fi0, который по умолчанию ноль.
    c - это координаты центра в комплексной форме. Например если X=30, а Y=40, то c=30+40j.
    n - это число вершин.
    r - радиус.
    И да, в javascript'е нет такого элегантного способа работать с комплексными числами, как в питоне. Но для js есть много библиотек для работы с комплексными числами. Будет не так компактно и красиво, как на питоне, но в целом всё точно так же в плане математики.

    UPD: Исправил функцию. Там скобочек не хватало, поэтому поворот на fi0 работал неверно. Теперь все как надо.
    Ответ написан
    6 комментариев
  • Быстрый способ подбора всех возможных вариаций значений массива какие есть способы?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Судя по постановке задачи и примеру, речь идёт не о всех возможных начениях массива, а обо всех возможных подмножествах множества мощностью 179.
    Если каждый элемент надмножества может либо входить, либо не входить, то кажое из множеств можно сопоставить с 179-битным двоичным числом. Очевидно, что таких чисел 2^179. Если убрать из набора пустое множество (в примере его не было), то вариантов станет на один меньше: 2^179-1.
    В десятичной системе это вот столько вариантов: 766247770432944429179173513575154591809369561091801087

    Автор вопроса не говорит как именно он хочет получить все эти варианты, но в любом случае сохранить такое количество элементов невозможно, в нащем Солнце атомов примерно всего лишь в сто раз больше, чем это число. Чувствуете проблемочку, да?

    Но задачу-то решать как-то надо. Давайте воспользуемся кодом Грея, чтобы можно было при переходе от варианта к варианту ограничиться изменением всего лишь одного бита. Но и это не поможет нам перебрать все варианты за разумное время.
    Пусть на один вариант нам потребуется безумно мало времени: один такт процессора. Сохранять мы варанты никуда не будем (потребовалось бы десять Юпитеров, чтобы на их атомах записать все варианты), просто покажем на экране. Да, за один такт этого не получится, но предсьавим себе что у нас такой специальный процессор с частотой 3 гигагерца. И нам потребуется 8099185802817355231125623242284335104 лет его работы.
    И всё это бессмысленно. Протсо автор вопроса не понимает чего хочет.
    Ответ написан
  • Алгоритм поиска кратчайшего пути через все вершины?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Это NP-полная задача. При достаточном количестве мусорных баков эффективно и быстро решить её просто нельзя.
    Важно понимать какое вам нужно решение: достаточно хорошее или оптимальное. Возможно, что для поиска оптимального решения вам просто не хватит ресурсов.
    Оптимальный маршрут ищется перебором иногда с разными оптимизациями, которые, впрочем, не сильно помогут в произвольном случае.
    Субоптимальные ищутся, к примеру, генетическими алгоритмами.

    Эта задача называется задачей коммивояжера:
    Задача коммивояжёра относится к числу трансвычислительных: уже при относительно небольшом числе городов (66 и более) она не может быть решена методом перебора вариантов никакими теоретически мыслимыми компьютерами за время, меньшее нескольких миллиардов лет.
    Ответ написан
    8 комментариев
  • На плоскости расположены n предметов, их нужно переместить в заданные n позиций. Как это сделать?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Ну вы бы хотя бы присказку какую-то добавили о том как рассуждали, что у вас не получилось и всё такое.
    А-то вот решите мне эту задачу, а я думать совсем не хочу.

    Давайте, учитесь уже рассуждать. Задачка несложная, видимо олимпиадная, но для каких-нибудь девятых-десятых классов. Хотя че-то, помнится, там посложнее были.

    Вот у вас есть 50 точек. И ещё 50 точек. Между каждой парой из них (а пар 50 в квадрате) есть какое-то расстояние. Там написано, что точки на плоскости, а значит расстояния между точками вас учили считать в школе по теореме пифагора.
    Если знаете расстояние между точками и знаете скорость каждой точки, значит можно посчитать время.
    Осталось что?
    Ну думайте.
    Этот ресурс не для того, чтобы двоечникам помогать.
    Возможно ваши соперники по олимпиаде сейчас сами думают, а вы, позорник, тут халтурите.
    Ответ написан
    Комментировать
  • Какой математический метод или алгоритм выбрать для формирования числа из диапазона?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    То, что вы ищете называется хеш-функцией.

    Возьмите первые (или любые) два байта из результата любой хеш-функции от этих данных.
    Надеюсь вы понимаете, что мощность множества ваших адресных пар гораздо больше, чем 2**16. Это значит, что неизбежны коллизии.
    Вот как получить это число можно однострочником на баше с помощью питона:
    py '(lambda a, b: 256 * a + b)(*hashlib.md5(b"any bytes for hashing").digest()[:2])'

    Или вот так в командной строке можно захешировать любой текст в два байта:
    echo 'any text' | py '(lambda a, b: 256 * a + b)(*hashlib.md5(sys.stdin.read().encode("utf-8")).digest()[:2])'
    Ответ написан
    1 комментарий
  • Какой алгоритм движения курьеров для доставки из ресторанов?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Интересная задачка у вас: NP-полная, но при ограничениях реального мира вполне разрешимая.
    Коммивояжер ваш имеет ограниченный ресурс по числу пунктов доставки. Доставку, наверно, нужно делать в заданных временнЫх рамках (пока горячая).
    Итого ваша задача разбивается на две:
    1. Распределить заказы между курьерами. Причем какие-то курьеры еще в пути, какие-то в резерве. Скажем, курьеров у вас 7: один в пути далеко, еще один на подходе и трое стоят под загрузкой (плюс двое в резерве на подхвате на случай аврала). Есть поток задач на доставку и нужно распределить их между курьерами максимально эффективно.
    2. Расставить задачи одного курьера в очередь так, чтобы при обходе точек назначения минимизировать какой-то параметр. Обычно это время, поскольку бензин, расстояние и стоимость проезда вторичны и коррелируют со временем.

    Если вы не "веществами" приторговываете, то курьер на задании автономен и не может принять доп-заказы. Отдельная история с "заехать в магаз и купить водки", тут автономность нарушается, но в целом ничего особо не меняет.

    Если случай вырожденный, то есть курьеров много, а заказов мало, то и вопроса нет.
    Проблемы начинаются когда курьеров много и заказов много.

    Для начала я бы провёл кластеризацию адресного пространства. Построил бы матрицу "цены" перемещения между узлами. Вынес бы роутинг на отдельный изолированный слой, чтобы не быть сильно зависимым от конкретного построителя маршрутов.
    Можно глянуть, например, в сторону OSRM.

    Я не искал готовых сервисов для решения задачи коммивояжера, а вам стоило бы сперва поискать готовое решение. Саму задачу не обязательно решать полностью и находить максимально эффективный маршрут. Достаточно, чтобы он был достаточно хорошим. В любом случае погрешности прогнозирования пробок и прочих факторов сделают это бессмысленным. Подходы к решению хорошо перечислены на вики по ссылке выше.

    Вообще технически можно ещё круче сделать, чтобы один курьер второго мог перехватить по пути и перераспределить с ним часть заказов так, чтобы совокупный расход на перемещение был меньше.
    Здесь курьер, получается, может доставлять товар еще и в произвольную точку рандеву другому курьеру.
    Если у вас мультимодальная система доставки с пешими и "конными" курьерами, то часть товаров, возможно, будет проще выпускать и развозить по магистрали автомобилем, а пешие гонцы перехватывают грузовик по пути и разносят локально.
    Можно попробовать глубже копнуть роевые алгоритмы.
    Каждый акт перемещения курьера, приёма/передачи товара (от ресторана курьеру, от курьера курьеру), подготовки заказа в конкретном пункте выдачи - это ветвоение в дереве решений.
    Такие ветвления могут быть реальными и потенциальными:
    • Реальные необратимы и по своему факту отсекают потенциальные ветки связанные зависимостями.
    • Потенциальные ветки имеют свою цену и динамически характеризуются числом зависимостей. Зависимости бывают мягкие и критические: чем большим приростом потенциально цены обернётся отсечение ветки, тем более она критична.

    Где тут роевой алгоритм. Можно наплодить виртуальных агентов, которые рандомно (или руководствуясь сигналами нейронной сети) выбирают те или иные ветки из предложенных. Весь рой клубится в потенциальной части дерева решений. Время бежит по пятам и реальные курьеры принимают те или иные решения: система для них выбирает оптимальное действие, или курьер предполагает, что не успеет или форс-мажор и пробка. Стена настоящего времени обрубает недостижимые потенциальные ветки и убивает агентов, которые на них оказались. Это высвобождает ресурсы и дает возможность спаунить новых агентов.
    Нейронную сеть агентов можно мутировать в рамках генетических алгоритмов.
    Можно взять маркерно-феромонную концепцию муравьиных алгоритмов. Так получится феромонами отмаркировать быстрые маршруты, а когда ситуация изменится и они станут медленными, то эти участки будут перемаркированы сами сорбой следующими агентами. Никто, кстати, не мешает в мультимодальной системе сделать особый вид агентов, которые будут маркировать маршруты для автотранспорта данными от яндекс-пробок. Для пеших агентов можно сделать отдельныз муравьёв разведчиков, которые маркируют по данным тепловой карты Стравы или каких-то локальных сетей сбора пешеходных треков.

    Короче, добро поджаловать в логистический адок.
    Ответ написан
    2 комментария