Задать вопрос
Ответы пользователя по тегу Алгоритмы
  • Как сделать плавный переход высот в шуме перлина?

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

    Например, после какой-то высоты всегда снег. Ниже какой-то высоты всегда камень. А по середине нужно что-то случайное. Можно, например, генерировать 2 шума и сравнивать их как-то. Например, квадрат одного больше другого, умноженного на какую-то константу. С формулами надо поэкперементировать, посмотреть, что хорошо выглядит.
    Ответ написан
    1 комментарий
  • Как из массива целых чисел найти все возможные комбинации (не только двух чисел, а и более) дающие искомую сумму?

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

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

    Другой вариант, через битовые маски, без рекурсии. Перебирайте число от 0 до 2^n-1. Потом смотрите на него, как на битовую маску. Так вы переберете все подмножества из n элементов. Если i-ый бит установлен, то берите i-ое число в сумму. Если сумма совпала с искомой - вы нашли вариант.

    Ну и самый быстрый вариант: с использованием динамического программирования. Как в задаче о рюкзаке вам надо подсчитать F(i,j) - можно ли числами с i-ого по последнее собрать сумму равную j. Потом рекурсивый перебор оптимизируется с этой информацией. Вы текущее число берете или нет и запускаетесь рекурсивно, если оставшимеся числами можно набрать оставшуюся сумму до ответа.

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

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

    Еще, угол поворота надо искать троичным поиском, ибо функция не монотонна, а пооизводную фиг найдете. В итоге у вас будет троичный поиск, запускающий двоичный поиск, щапускающий поиск паросочетания.
    Ответ написан
  • Что значит O(1)?

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

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Вот тут программисту и нужна математика (совсем чуть чуть).

    Квадрат - это все точки (X, Y), которые удовлетворяют неравенствам:
    x1 <= X <= x1 + w1
    y1 <= Y <= y1 + w1


    Пересечение двух квадратов - это точки, которые удовлетворяют одновременно каждой паре неравенств, т.е. удовлетворяют сразу четырем неравенствам:
    x1 <= X <= x1 + w1
    y1 <= Y <= y1 + w1
    x2 <= X <= x2 + w2
    y2 <= Y <= y2 + w2


    Тут уже видно, что фактически есть пара неравенств на X, и есть пара неравенств на Y. Они независимые. Вот и получается, что пересечение можно искать отдельно по каждой оси.

    Рассмотрим одну пару:
    x1 <= X <= x1 + w1
    x2 <= X <= x2 + w2


    Вообще, такие системы неравнеств решают в школе, классе этак в 7.

    Сконцентрируемся на левых границах. Что значит, что X >= x1 и X >= x2? Это значит, что X больше обоих чисел x1 и x2. Это можно записать одним неравнеством - X больше максимума из двух чисел:
    max(x1, x2) <= X

    Так же и по правым границам:
    X <= min(x1 + w1, x2 + w2)

    В итоге:
    max(x1, x2) <= X <= min(x1 + w1, x2 + w2)

    Эти неравенства имеют решение, если левая граница не превосходит правой:
    max(x1, x2) <= min(x1 + w1, x2 + w2)

    Вот у вас условие, что по оси OX есть хоть одна точка пересечения. Если вам касающиеся квадраты не надо считать пересекающимеся, то замените знак <= на строгое неравенство.

    Точно также проверьте, что есть пересечение по OY. Если пересечение есть и там и там, то квадраты пересекаются. Т.е. весь ваш код должен найти 2 минимума, 2 максимума, сделать 2 сравнения и соединить их через логичиское И.
    Ответ написан
    5 комментариев
  • Алгоритм для планирования меню, как реализовано на сайте?

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

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

    Поэтому иногда имеет смысл написать не самый быстрый алгоритм, но зато гораздо более простой. Если он проходит, то зачем кодить больше?

    В вашем алгоритме еще проблема, что не очевидно, почему он работает. Надо аккуратно рассматреть все 6 случев расположения arr[0], arr[1] и numbers[i] и убедиться, что инваринат "2 минмальных числа лежат в arr" поддерживается. Тут легко накосячить, перепутать 0 и 1, не там проверку поставить и все.

    Обычно, когда такой алгортм реализуют, поддерживают более строгий инвариант "минимальное число в arr[0], следующее минимальное число в arr[1]". Тогда проверки чуть чуть упрощаются и за логикой решения следить проще.
    Ответ написан
    1 комментарий
  • Какой алгоритм использовать для нахождения точки?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам уже подсказали: Триангуляция по 4 точкам.

    При чем не надо решать систему в общем виде. Если вы возьмете ваши 4 точки как (0,0,0) и (0,0,1), (0,1,0) и (1, 0, 0) то вы уже можете найти координаты искомой точки, не решая даже квадратных уравнений или систем.

    Например, для первых двух точек:
    x^2+y^2+z^2 = d1^2
    x^2+y^2+(z-1)^2 = d2^2


    Вычтите одно уравнение из другого, сократите все сокращаемое, поделите на 2 и в одно арифмитическое действие найдете z.

    Точно также можно найти x и y, рассматривая другие пары уравнений.
    Ответ написан
    Комментировать
  • Кратчайший путь через несколько точек?

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

    Так, вашу задачу можно переформулировать в виде максимального потока минимальной стоимости из нескольких разных жидкостей (исток k-ой жидкости в точке k, сток - в k+1. Чтобы вершины нельзя было переиспользовать их надо раздвоить на входную и выходную, соедененные ребром).

    Похоже, что в вашей задаче может хорошо работать метод отжига, или генетический алгоритм.

    Я сомневаюсь, что даже для графа-сетки есть какой-то более простой алгоритм.
    Ответ написан
    2 комментария
  • Как поменять порядок битов в байте C?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вручную, наивно. Представьте, что это массив из 8 значений. У вас есть операции "прочитать i-ое значение" ((x >> i) & 1) и "записать значение a в позицию i" ((x & ~(1 << i)) | (a << i)). Дальше остается написать цикл и руками менять биты местами.

    Дальше, конечно, можно извращаться с оптимизацией и всякмими битовыми хитростями.

    Но если вам важна именно скорость, то быстрее предподсчета (как вам угодно) и хранения результата в таблице вы ничего не сделаете.
    Ответ написан
    1 комментарий
  • Что за ошибка во время вывода?

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

    У вас опять куча мелких ошибок в коде.

    На вскидку:
    f надо уменьшать там, где у вас закомментированно, после ввода. А то вы после основного цикла обращаетесь по индексу не уменьшенного f и выходите за границу массива.

    Массив p надо переписывать только при удачной релаксации, вы же в основном цикле, после вызова функции еще зачем-то всегда переписываете предка. Из-за этого у вас там полный бред в массиве ссылок к предку получается и цикл вывода, скорее всего, виснет в бесконечном цикле из ссылок.
    Ответ написан
  • Как исправить алгоритм Дейкстры?

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

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

    Похоже, что первые index позиций в массиве arr считаются зафиксированными.
    Тогда вам надо ставить на следующее место все оставшиеся числа по порядку и рекурсивно запускаться дальше.

    Но ваш подход со swap-ами первого не зафиксированного числа со всеми остальными перемешивает эти оставшиеся числа. Если вы пройдетесь по вашему решению отладчиком или будете выводить arr в начале функции всегда, то вы заметите, что там числа после index идут не по порядку. И ваш алгоритм, беря их индесы по порядку, может сначала поробовать не самое маленькое число.

    Вам надо добиться того, что оставшиеся в arr числа после index всегда шли по порядку на момент рекурсивного вызова.

    Пусть ставшиеся числа 1,2,3,4. Сначала вы ставите на первую позицию 1 (оставляете все как есть).
    Потом вы должны получить в массиве 2,1,3,4 и запуститься рекурсивно. Это один swap.
    Потом надо получить в массиве 3,1,2,4. Это можно сделать одним swap из предыдущего состояния.
    Важно тут, что вы не возвращаете массив назад после каждого рекурсивного вызова. Иначе вам пришлось бы делать циклический сдвиг части массива на каждой итерации (1,2,3,4 -> 3,1,2,4).

    В конце, после последнего рекурсивного вызова у вас будет 4,1,2,3. Чтобы вернуть все как было вам придется после цикла с рекурсивными вызовами сделать циклический сдвиг массива влево на 1 позицию.

    Т.е. рекурсивные вызовы будут как-то так:
    perm( arr, size, index + 1 );
    for( i = index + 1; i < size; i++ )
    {
        swap( arr[i], arr[index] );
        perm( arr, size, index + 1 );
    }
    int tmp = arr[index];
    for (i = index; i < size-1; i++)
        arr[i] = arr[i+1];
    arr[size-1] = tmp;
    Ответ написан
  • А как предков вывести?

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

    В очередь вы должны класть только идентификатор вершины/состояния. В той задаче это были 2 координаты клетки поля. Тут - это четырехзначное число.

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

    Еще раз, в очереди у вас только число, а в массиве рядом пометки о песещении и путь к предку (расстояние конкретно в этой задаче считать и выводить не нужно).

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

    В конце надо циклом while от конечной вершины по сохраненным предкам идти, пока не упретесь в начало. Потом список обойденных чисел надо развернуть. Или хитрость - проводите обратные операции начиная с конечного состояния, пока не найдете начальное. Операции развернуть просто - вычитайте из первой цифры и прибавляйте к последней. Сдвиги симметричны и остаются как есть. Тогда не надо разворачивать ответ после восстановления циклом while.
    Ответ написан
    Комментировать
  • А конь по кругу ходит?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Что-то не то с bfs. Вместо set visited надо иметь двумерный массив, в котором надо считать расстояния. Значение оттуда же и возвращать. Еще плохой код в том что локальные x1, y1 имеют те же имена, что и аргументы функции.
    Ответ написан
  • Существует ли быстрый алгоритм поиска общих подстрок во множестве больших строк?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Пока главная проблема, что надо сравнивать все строки со всеми. Если бы их поделить на какие-то части, когда сравнивается не так много строк, пусть даже этих частей много, то задача решается Гораздо проще.

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

    Вот построили вы деревья для всех строк, дальше надо их попарно сравнить простым рекурсивным обходом. Если оба дерева содержат переход по какому-то символу, рукурсивно идите по нему. Если дошли до глубины N - вы нашли совпадение. Можно вообще идти пока не обломитесь и взять максимальную глубину. Такой обход обойдет дерево один раз для каждой пары строк. Да, еще надо будет хорошенько потрахаться с хранением дерева на диске и подгрузкой его кусками, ибо в оперативку оно все не поместится никак.

    Второй вариант, возможно более подходящий для таких объемов данных - это полиномиальные хеши. Можно для каждой строки вычислить L-N+1 хешей для всех подстрок длины N. Первый хеш считается тупо по формуле, а дальше дописывание одного символа справа и удаление одного символа слева можно за 2 операции пересчитать. Вот так вы быстро, за линейное время, можете построить все хеши для одной строки. Запишите их в файл, отсортируйте его (гуглите - известная задача сортировки очень большого файла). А потом операцией слияния можно найти повторяющиеся числа во всех файлах.

    Более того, можно не сравнивать каждый файл с каждым, а выполнять слияние сразу на всех файлах. Для этого надо завести приоритетную очередь, она же куча, она же heap, в которую складывать текущие числа из всех файлов (по одному из файла) вместе с указателем на сам файл. Вам надо из этой очереди вынуть минимальное число, и потом вынимать дальше, пока минимум в очереди не изменился. Т.е. вынуть все одинаковые минимальные числа. Файлы, на которые они указывают - это строки с совпадениями. Пометьте это где-то, и для каждого файла прочитайте следующее число и положите его в очередь.

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

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

    Если все-таки надо искать совпадения по всем строкам глобально, то придется помучиться. Разбейте ваши данные на K частей примерно одинакового размера так, что каждый компьютер может обработать по 2 части, а хранить хотя бы по 3 части.

    В идеале у вас должно быть еще и K/2 компьютеров, иначе схема усложняется.

    Надо будет провести K-1 раундов. На первом раунде части 1 и 2 лежат на компьютере 1, части 3 и 4 на втором, и т.д. На втором раунде вы храните части 2 и 3 на компе 1, 4 и 5 на компе 2 ... K и 1 на последнем. При переходе между раундами каждый комп отдает одну часть куда-то, и одну откуда-то получает. На третьем и четвертом раунде вы обрабатываете все пары, в которых вторая часть имеет номер на 2 больше первой части (если брать по модулю K). И так далее. На последнем раунде будут обрабатываться пары, где одно число больше другого на (K-1)/2.

    Например, для K=4 вы получаете такие пары на компах:
    1. (1,2) (3,4)
    2. (2,3) (4,1)
    3. (1 3) (2 4)


    Тут надо порисовать и составить схему так, чтобы поменьше данных перекладывалось. Для некоторых K так красиво не получится, и какие-то компы будут простаивать на каких-то раундах.

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

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

    Это известная, очень сложная (NP-сложная) задача об упаковке.

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

    Соглашусь с Dr. Bacon, через yield код вашего решения может быть чуть красивее, но я не совсем понимаю, что в вашей реализации вам так "не красиво". Хотя я не питонист.

    Далее, вы упоминули, что ключи во входных данных - это номера домов, а числа в списках - номера квартир. Вы как-то объединяете дома с ограничением на количество квартир. Не знаю, что у вас за задача на самом деле, но мне кажется логичным, что вам надо бы объединять дома только рядом. С одной улицы, или вообще идущие подряд. Ведь если в одной группе идут квартиры из домов в разных концах города, то какая вообще разница, что квартиры из одного дома могут быть в разных группах? Если такое ограничение ввести, то задача упрощается и ваше решение будет оптимальным.
    Ответ написан
    3 комментария
  • Найти в строке два одинаковых фрагмента?

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

    Более простое но и более медленное решение (за O(n^2)) - считайте полиномиальный хеш всех подстрок и храните в хешмапе минимальный индекс для каждого хеша. Оптять же, как только встретили подстроку, которую уже видели и ее первый индекс такой, что нет пересечения с текущей строкой - вы нашли ответ.

    Еще за O(n^2) же есть решение через динамическое программирование: считайте F(i,j) - максимальная длина совпадающих строк, начинающихся с символов i и j (F(i,j) = 1 + F(i+1, j+1), если s[i] == s[j] и 0 иначе). Потом для каждой пары начал берите минимум из f(i,j), abs(j-i) - это и будет максимальная длина непересекающихся фрагментов с этих индексов. Если где-то нашли больше 5 - это ответ.

    Ну и самое простое и медленное решение (за куб) - три цикла. Переберите 2 начала строк и сравнивайте их в цикле, пока они совпадают, не пересекаются и не выходят за границы строки. Если насчитали больше 5 символов - вы нашли ответ. Это почти как предыдущее решение, только без динамического программирования.
    Ответ написан
    Комментировать
  • Будет ли работать бинарный поиск, если в массиве есть пробелы?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Будет. Бинарному поиску главное, чтобы числа в массиве не убывали. Даже если там числа больше предыдущего не только на 1.
    Ответ написан
    Комментировать
  • Как работает алгоритм перебора перестановок (рекурсия)?

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