Ответы пользователя по тегу Алгоритмы
  • Почему в Python последовательность append-ов суммируется в O(n), а не в O(n logn)?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    а общее число перераспределений для n добавлений уменьшается как O(logn). Почему тогда в таком случае для n последовательности append-ов общая сложность равна O(n), а не O(n logn)?


    Потому что эти распределения разного размера. Первое - совсем маленькое. Второе чуть больше и т.д. Чтобы суммарно было O(n Log n), каждое из них должно быть пропорционально n.

    1+4+10+⋯≤O(n)


    Вот это и есть ответ. Первые слагаемые маленькие. Получается геометрическая прогрессия, которая дает линейную сумму от n.
    Ответ написан
    Комментировать
  • Как корректно распределить сумму внутри элементов массива?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Во-первых, давайте считать в копейках. Умножте все цены на 100 перед началом алгоритма, и поделите на 100 при выводе. Потому что никогда нельзя деньги считать или хранить в вещественных числах. При сериализации и внешних интерфейсах, конечно, придется запятые ставить, но лучше при выводе выводить x/100, "," и x%100. И молиться, что у читателя хватит точности это прочитать, если он, конечно, не заморачивается с подобным трюком.

    Далее алгоритм (на псевдокоде):
    total_price - изначальная цена. goods[i].price - цена товара, goods[i].amount - количество товара, discount - сколько скидки.
    discount_left = discount
    for i in 1..n {
      cur_discount = discount * goods[i].price / total_price # целочисленное деление нацело с округлением вниз.
      goods[i].price -= cur_discount
      discount_left -= cur_discount*goods[i].amount
    }
    for i in 1..n {
      if discount_left == 0: break
      if goods[i].amount <= discount_left {
        goods[i].price -= 1;
        discount_left -= goods[i].amount
      }  else {
       // разбиваем категорию на 2 штуки, в одной discount_left товаров, в другой остальное.
       new_good = Good{.price = goods[i].price-1, .amount = discount_left}
       goods[i].amount -= discount_left
       discount_left = 0
       goods.append(new_good)
       break
      }
    }


    Как это работает: если бы мы могли идеально делить копейки с любой точностью, то каждый товар получил бы скидку price*discount/total_price. Одинаковый процент скидки везде. но у нас-то целые копейки, поэтому если мы эти скидки округлим вниз, то мы сколько-то копеек недоскинем. Но для каждой штуки товара мы потеряли меньше одной копейки, а значит суммарно - меньше общего количества товаров. Значит можно из каких-то товаров вычесть 1 и все сойдется. Вот и вычитаем из первых discount_left товаров. Если категория целиком покрыта - просто уменьшаем у нее цену. Если нет, то разбиваем на 2 куска и у одного цену уменьшаем.

    Тут могут быть проблемы, если скидка очень большая и есть товары очень маленькой стоимости. Скажем, товар стоимостью 5 копеек и кидка в 85% уже между 0 и 1. Округленная вниз она будет 4, но вот если вы из этого товара потом еще и 1 вычтите, то может так получится, что вы снизите цену до 0. Допустимо ли это? Чтобы этого избежать надо товары отсортировать по убыванию цены. Но все равно может не помочь, тогда надо тогда такие товары ценой в 1 копейку исключить из рассмотрения и запустить цикл вычитания 1 опять, если после цикла discount_left не 0.

    Второй момент, если вам очень хочется избегать разбиения категорий на части, то можно попробовать реализовать динамическое программирование для решения задачи размена монет, и таким образом найти какие категории надо взять целиком, чтобы набрать discount_left. Но вряд ли вам это так уж надо, ибо предвижу весьма частый случай, когда разбивать все-равно придется. Да и разбиение тут максимум одно происходит.
    Ответ написан
    Комментировать
  • Как доказать что мы действительно не пропустим такую пару i,j которая дает правильный ответ в методе двух указателей?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В такой реализации это плохо видно, но можно переписать основной цикл так:
    j = Len(B)-1
    for i in range(len(A)):
      while j >= 0 and A[i] + B[j] > c:
        --j;
      if A[i] + B[j] == c:
        found = True
        break


    В этом случае после цикла while поддерживается инвариант, что a[i]+b[j] <= c и это максимальное такое j.
    Ведь, если этот инвариант поддерживался для пердыдущей итерации, то у нас было a[i-1] +b[j] <=c и a[i-1]+b[j+1]>c. Отсюда получается, a[i]+b[j+1] >= a[i-1]+b[j+1] > c. Т.е. если мы уменьшим j в цикле while инвариант останется действовать - это будет самое большое j, т.ч. a[i]+b[j] <= c.

    А этот инвариант гарантирует, что когда в цикле по i переберется значение из ответа, j гарантированно будет указываать на j из ответа. Потому что, если существуют i', j', т.ч. a[i']+b[j'] = c, то можно увеличить j' пока b там не меняется, т.ч. b[j']>b[j'], отсюда получается a[i']+b[j'] <=c и a[i']+b[j+1] > c - а это и есть наш инвариант. Т.е. на итерации i=i' найдется именно j=j'.
    Ответ написан
    3 комментария
  • Как найти ближайщую точку из списка географических координат?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Если у вас задача найти ближайшую к одной один раз, то весь "алгоритм" - цикл for.
    Перебираете все точки из файла, считаете расстояние по формуле.

    Как сказал Alexandroppolus можно считать не расстояние, а квадрат. Если координаты близкие, то можно упрастить формулу еще больше, но это будет небольшой выигрыш в скорости.

    Если же вам надо много раз для многих точек находить ближайшую из заданных, то имеет смысл построить какой-то индекс по опорным точкам. Какое-нибудь R-tree или квадро-дерево.
    Ответ написан
    Комментировать
  • Есть ли у этой задачи название?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Если вам можно брать вершины только из одной доли, то это задача set cover (покрытие множества). Каждая вершина в R задает множество в L и вам надо взять минимальное их количество, чтобы все L было покрыто. Это NP-сложная задача и у нее есть только медленные решения, вроде перебора. Еще можно свести ее к задаче целочисленного линейного программирования и решать каким-нибудь солвером.
    Ответ написан
    2 комментария
  • Как использовать квадро-дерево, если его элементы необходимо изменять?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В дерево можно добавлять вершины и удалять их. При изменении удаляйте старый элемент и добавляйте измененный. Не забудьте удалить и пучтые проиежуточные вершины.
    Ответ написан
  • Как сгенерировать непрерывные случайные величины с заданным законом распределения?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Во-первых, компьютеры вообще не могут в непрерывность. Так что вы получите лишь некоторое приближение: дискретную случайную величину с большим количеством возможных рациональных значений. Более того, компьютеры не умеют и в случайность, поэтому вы получите лишь псевдо-случайные числа.

    Обычно, сначала реализуют дискретную случайную равномерно распределенную величину. Гуглите алгоритмы генерации псевдослучайных чисел, если вам нельзя даже какой-нибудь rand() использовать.

    Далее получают равномерно распределенную случайную величину на отрезке [0,1]. Для этого генерируют случайное число от 0 до MAX_RAND и делят на MAX_RAND.

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

    Пусть x искомая случайная величина Fx(t) = P(x < t). u -равномерно распределенная случайная величина. Тогда x = Fx^(-1)(u).

    Например, для экспоненциальной случайной величины Fx(t) = 1-e^(-lt). Обратная функция будет Fx^(-1)(y) = -ln(1- y)/l. Значит считаете ваше случайное число, делите на MAX_RAND, подставляете в формулу -ln(1-y)/l. Или можно упрастить и брать просто -(ln y)/l, потому что равномерная случайная величина от 0 до 1 симметрична.

    Проблема тут с тем, что не для всех распределений можно получить обратную функцию в виде формулы. Для нормального распределения формулы как выше нет - надо использовать функцию erf(), или считать ее приблеженно руками через какие-нибудь ряды.
    Ответ написан
    Комментировать
  • Почему неправильно работает сортировка вставками из "Алгоритмов..." Кормена и др.?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Потому что в книге нумерация элементов идет с 1, а в питоне - с 0.

    Надо поменять условия в циклах for и while.
    Ответ написан
  • Как составить наиболее эффективный алгоритм групповой рассылки сообщений по каналам WebSocket?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам по-любому придется отправить сообщение каждому пользователю, кто онлайн и подписан на канал.

    Единственное, что вы можете соптимизировать - это нахождение списка пользователей.

    Вам надо как-то побыстрее определить, какие пользователи сейчас онлайн из данного канала. Можно или поддерживать эту информацию в памяти (это будет map из channel_id в set user_id). Когда пользователь выходит в онлайн, надо добавить его в структуру данных для всех каналов, на которые он подписан. Когда пользователь отваливается - удаляйте его из памяти и чистите структуру (надо удалить ключ из map, если там значение осталось пустым).

    Еще вариант: сделать это прямо в базе данных. Для каждого пользователя поддерживайте флаг - онлайн ли он. И в базе данных спрашивайте список всех онлайн пользователей, которые подписаны на нужный канал. Можно даже ваш map user_id->socket_channel_id в базу запихать.
    Ответ написан
    Комментировать
  • Бинарный поиск. Правильно ли работает?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    if i == middle - 1 or i >= 5 and len(list) <= 4:
                return -1


    Вот эта часть вызвает вопросы. Отладочный код остался?

    Проблема в вашем коде, что он виснет, если числа в массиве вообще нет. Условие должно быть не про равенство, а про то, что у вас отрезок пуст (или длины 1) - надо сравнивать first и last.

    Обычно middle не поддерживают между итерациями цикла, а просто считают внутри. Между итерациями переходят только first и last.

    А так, да, бинпоиск - это весьма простой алгоритм.
    Ответ написан
    1 комментарий
  • Дейкстра почти за линейное время (для неориентированного графа)?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В сетевом роутинге, в основном, нет* центрального узла с информацией о всей топологии, чтобы искать пути. Так что там вообще не работает Дейкстра, а работают распределенные алгоритмы.

    А так, да, на очень специфичных гарфах всеядный алгоритм Дейкстры не самый лучший вариант. И ваш алгоритм тут получается быстрее.

    * Вообще, есть такая тема, как Software Defined Networking, где как раз есть центральный узел, который все и решает, но это чисто теория пока, и там суть не в поиске кратчайших путей в графе а во всяких сложных механизмах приоритезации трафика и устойчивости к сбоям.
    Ответ написан
    1 комментарий
  • Как набрать нужную сумму из определенного количества монет?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно целочисленным линейным программированием решать. Бинплиском перебтрайте ответ (k), потом составляйте задачу, где у вас переменные: сколько монет каждого типа берём в каждый из k разменов. И еще будут неравенства, что каждый тип монет используется суммарно не больше, чем есть монет. Целевая функция - любая.

    Еще можно чуть по-другому: находите переблром все варианты одного размена. Потом у вас переменные будут, сколько раз каждый из вариантов берете. Ограничения на общий расход каждой монеты, а целевая функция - сумма переменных. Тут нет бинплиска, но тоже ЦЛП.
    Ответ написан
    Комментировать
  • Доработка алгоритмической задачи JAVA. Требуется помощь >?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вы не ищите 2 максимума, а перебирайте все пары заборов, считайте площадь и выбирайте лучший вариант. Это точно будет работать, хоть и за квадрат.

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

    Поэтому решение будет из двух указателей - левого и правого. Пока они не сошлись, считайте площадь, сравнивайте ее с текущим ответом, а потом двигайте указатель на меньшее число в сторону второго.
    Ответ написан
    8 комментариев
  • Как вывести кратчайший путь в графе?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам надо в вашем алгоритме еще завести словарь, куда вы для каждой вершины будете складывать предыдущую в пути. Там, где вы соседнюю вершину кладете в очередь - там надо сарзу смотреть, посещенная она или нет и класть только если нет. Вот в этот момент вы знаете, что вы пришли в graph[city][i] из city.

    В конце циклом по этому массиву пердыдущих вершин пройдитесь и так получите путь в обратном порядке.
    Ответ написан
  • Алгоритмы и структуры данных. Сложность алгоритмов?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Потому что это 2 разных графика, 2 разных примера. Могли бы на втором обозвать вместо f и g f` и g`. Но авторам, наверно, было лень.
    Ответ написан
    1 комментарий
  • Как проходить список и одновременно удалять элементы, в том числе впереди курсора?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Для такой вот работы идеально подходят связные списки. Там можно менять объект не портя никакие итераторы. Проблема только в том, что связные списки в питоне не реализованы на уровне языка. Поищите реализацию, наверняка есть удобный модуль со списками.

    Еще можноиспользовать обычный list, только вы итерируйтесь циклом while и используйте индексы:
    i = 0
    while i < len(arr):
      x = arr[i]
      j = i+1
      while j < len(arr):
        if ShouldDelete(arr[j], arr[i]):
          del a[j]


    Это будет в n раз медленнее, к сожалению. Можно наверно слайсами сделать быстрее:

    arr[i+1:] = y for y in arr[i+1:] if not ShouldDelete(x, arr[i])


    Я не питонист и возможно ошибку допустил. И мне этот код не кажктся очень понятным, но возможно это истиный путь для питона.

    А так, в общем случае, идет двойная итерация с пометками элементов на удаление.

    Какого-то названия этот прием не имеет, ибо это костыль для конкретных реализаций контейнеров.
    Ответ написан
    Комментировать
  • Как транспонировать биты числа максимально быстро?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Занумеруйте все биты от 0 до 16. Потом транспонируйте эту матрицу. Потом посмотрите на разность результата и конца. Вот эти вот числа - это на сколько бит надо сдвинуть исходные биты, чтобы они встали на нужное место.
    0 1 2 3
    4 5 6 7
    8 9 10 11
    12 13 14 15
    
    0 4 8 12
    1 5 9 13
    2 6 10 13
    3 7 11 15
    
    0 -3 -6 -9
    3 0 -3 -6
    ...


    Все биты с одинаковым смещением можно подсчитать за 3 операции: Сдвиг, битовая маска и побитовое или в ответ.
    Я тут вижу 7 разных чисел -9,-6,-3,0,3,6,9.
    Например, для 0 и 3 у вас будет
    answer = (source & 0x1248) | ((source << 3) | 0x2480) | ...


    Это не 8 пока еще операций, а аж 20. Возможно можно как-то еще их сгруппировать.

    Edit: Возможно, еще подход с таблицей будет быстрее. Для каждого из 16 возможных значений строк выдавайте битовое число - столбец, где эти 4 бита на позициях 0, 4, 8,12. Тогда ответ будет table[source&0xf0] | (table[(source>>4)&0xf] << 1) | (table[(source>>8)&0xf] << 2) | (table[(source>>12)&0xf] << 3).

    Тут 13 битовых операций и 4 чтения из памяти.
    Ответ написан
    2 комментария
  • По какому принципу работает алгоритм с массивом очереди?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Много разных вариантов для структур данных. Например, кольцевой буфер. Или связный список. В этом случае можно удалять элемент с начала, не сдвигая все элементы в массиве.
    Ответ написан
  • Проверка на достижимость в направленном графе?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Под конденсацией вы подразумеваете сжатие компонент сильной связности? Ну так у вас остается в конце какой-то произвольный DAG, а не дерево.

    В нем уже нельзя ввести поняие LCA вообще. Например, граф 0->2, 0->3, 1->2, 1->3. Тут для вершин 2 и 3 есть два общих предка: 0 и 1. И какой из них самый нижний?
    Ответ написан
  • Как называется такая алгоритмическая задача?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Задача о покрытии, наверное
    Ответ написан
    Комментировать