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

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Проще всего - найти произвольный цикл, в нём - ребро наименьшего веса, и вычесть этот вес из всех рёбер. Продолжать, пока циклов не останется. Но результат может быть сильно неоптимальным.
    Можно искать цикл, в котором наименьший вес ребра максимален (добавлять рёбра начиная с самого тяжёлого, пока в графе не появится цикл). Тогда результат должен получиться заметно лучше (по сумме весов). Но число рёбер может быть не минимальным.
    Кстати, можно ли увеличивать веса дуг (из графа (1,2,w=1), (2,3,w=2), (1,3,w=4) получить (2,3,w=1),(1,3,w=5))? Если да, то после того, как циклы в ориентированном графе кончились, придётся искать циклы в неориентированном графе, и прибавлять ко всем рёбрам наименьший по модулю вес ребра с учётом направления рёбер (вычитать из попутных и прибавлять ко встречным).
    Ответ написан
  • Как составить алгоритм решения задачи на ДП или Рекурсию (Задача на лесенку)?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Решить задачу легко - заводите массив A[N,N], в каждой клетке [K,M] которого будет записано число лесенок из K кубиков, нижний слой которых состоит из M кубиков. Число будет ненулевым, когда M <= K <= M*(M+1)/2.
    Массив заполняется начиная со строчки K=1 по формуле A[K,M]=sum(A[K-M,r],r=1..M-1). Сумма элементов в строчке K=N будет искомым числом.

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

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Надеюсь, что остаток берётся всё-таки от деления на 390, а не на 389 - иначе смысла в коде вообще не видно.
    Итак, у нас есть 77 простых чисел, меньших 390. Четыре из них - 2,3,5,13 - не более, чем мусор, дающий лишние срабатывания алгоритма. В самом деле, если x%390==3, то x делится на 3, а значит, оно составное. Остальные 73 числа годятся.
    Получается такая картина. Для простого x величина x%390 - некоторое число, взаимно простое с 390. Причём распределены эти остатки более-менее равномерно (на картинке они выглядят как красные столбики). Всего этих остатков 96.
    Если x - простое число, то с вероятностью 73/96 остаток будет простым числом, и алгоритм честно скажет "скорее, простое". С вероятностью 23/96 - те самые 25% - остаток окажется составным, и алгоритм ошибётся.
    Если x - составное, то вероятность того, что число объявят "скорее, простым" будет равна 77/390-1/ln(x) (первое слагаемое - вероятность того, что остаток оказался простым, а второе - доля простых чисел в окрестности x).
    Можно легко избавиться от ошибки первого вида: для этого надо в массив charr положить не простые числа, а числа, взаимно простые с 390. Тогда если алгоритм скажет "составное", то так и есть.
    Можно было вместо 390 взять N=30030=2*3*5*7*11*13. Если массив будет заполнен числами, взаимно простыми с N, то отсеиваться, как составные, будет 4 числа из 5, а ложных отсеиваний простых чисел не будет вообще.
    Ответ написан
    2 комментария
  • Что такое машина Тьюринга и какое отношение она имеет к программированию?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    В первую очередь - это формальное определение алгоритма. Задача считается алгоритмически разрешимой тогда и только тогда, когда её решение можно запрограммировать на машине Тьюринга (или каким-нибудь другим эквивалентным способом). Это определение даёт, например, возможность предъявить алгоритмически неразрешимые задачи. Позволяет ввести понятие "Тьюринг-полного" языка - если на языке можно реализовать машину Тьюринга, то на нём можно написать любой алгоритм (язык С таким не является, а C# - является).
    В общем, МТ - способ определить некоторый класс алгоритмов:
    - некоторые задачи можно решить конечным автоматом;
    - для некоторых потребуется конечный автомат со стековой памятью;
    - для других достаточно машины Тьюринга;
    - для остальных требуется божественное откровение или другие неалгоритмизируемые методы.
    Ответ написан
    7 комментариев
  • Как оптимально найти подмножества в наборе данных многие-ко-многим?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Есть простое решение за (число сообществ)*(число связей "пользователь-сообщество").

    Для каждого сообщества Y:
    - заводим массив R[C], где C - число сообществ
    - для каждого пользователя X из сообщества Y:
    - - для каждого сообщества M, в которое входит пользователь X: R[M]=R[M]+1
    - для каждого сообщества M: если M!=Y и R[M] > 1, то пара (Y,M) - ядро.

    Быстрее пока не получается.
    Ответ написан
    Комментировать
  • Переменная vs цикл в данном случае?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Зависит от статистики обращений.
    Если проверки идут намного чаще, чем изменения массива - то переменная. Причём при "удалении" цифры придётся не просто очищать ячейку, а пройтись по всему массиву - нет ли там ещё одной такой цифры.
    Если проверки и изменения идут сериями (много изменений - много проверок), то две переменных. В одной - индекс, в другой признак того, не испортился ли он. Тогда при проверке с "испортившимся" индексом придётся пройтись по массиву и посмотреть, где теперь цифра. Хотя можно просто проверить, что лежит сейчас в ячейке с индексом. Если не нужная цифра - новый проход по массиву. Но здесь уже надо смотреть, какая ячейка портится чаще - та, в которой цифра появилась раньше всего, или та, где она появилась позже всего.
    Если проверки идут достаточно редко, то цикл.
    В сложных случаях - когда в массиве не 10, а 1000 ячеек, цифр может быть много, меняются они часто, и нужно часто проверять, есть ли цифра, и где именно - надо будет хранить индексы ячеек с этими цифрами в какой-нибудь структуре. Например, в двусвязном списке, построенном на двух массивах, параллельных рабочему. Тогда все изменения и проверки будут работать за O(1).
    В общем, всё зависит от ситуации.
    Ответ написан
    Комментировать
  • Какой вариант оптимальней?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    В первом случае варианты не эквивалентны. При a=3, b=4 в первом варианте CallMe() вызовется дважды, а во втором - только один раз.
    Первый вариант написан оптимально. Чтобы заставить его вести себя, как второй вариант (с одним вызовом CallMe()), достаточно вставить else:
    if(a == 3)    CallMe();
    else if(b == 4) {
        CallMe();
        Giveme();
    }

    Это будет оптимально по времени. Если же вместо CallMe() стоит более сложный вызов или длинный блок, и вас волнует длина кода (например, на микроконтроллере), то берите второй вариант. Если при этом и общее условие сложнее, чем b==4, то придётся его запомнить:
    bool cond1=(sin(x)>=sin(y));
    if(cond1 || cos(x)<cos(y)) Draw(tan(x),tan(y));
    if(cond1) CloseLine();


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

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Зависит от проекции. Пусть широта точки равна u (измеряется в радианах, от -pi/2 до pi/2), а долгота v (от 0 до 2*pi).

    Самая распространённая - равнопромежуточная цилиндрическая проекция. В ней карта имеет размер (2*pi) x pi, и координаты вычисляются как x=v, y=u+pi/2.

    В проекции Меркатора ширина карты равна 2*pi, а высота может быть любой, вся планета всё равно не поместится. Допустим, высота равна 2*H, ширина - 2*pi. Тогда координаты будут x=v, y=ln(tan(u/2+pi/4))+H. Для некоторых точек (около полюсов) y выйдет за пределы диапазона (0,2*H), этих точек на карте не будет.

    В равновеликой проекции Ламберта (сохраняющей площади) карта имеет размер (2*pi) x 2, и координаты вычисляются как x=v, y=sin(u)+1.

    В равнопромежуточной азимутальной проекции (с центром в северном полюсе) карта умещается в квадрат со стороной 2*pi. x=pi+(pi/2-u)*cos(v), y=pi+(pi/2-u)*sin(v).

    В центральной проекции (с центром в северном полюсе) на карте умещается только часть северного полушария. Допустим, карта - квадрат со стороной 2*M. Тогда x=M+ctg(u)*cos(v), y=M+ctg(u)*sin(v) (для u>0). Преимущество этой проекции - что кратчайшие пути между точками изображаются прямыми.

    В стереографической проекции (с центром в северном полюсе) планета на карте тоже не поместится. Допустим, карта - квадрат со стороной 2*M. Тогда x=M+ctg(u/2+pi/4)*cos(v), y=M+ctg(u/2+pi/4)*sin(v)

    Это первое, что пришло в голову (правда, три последних - не цилиндрические). А вообще, проекций (и алгоритмов) много. Вот краткий список (примерно 60 вариантов): https://en.wikipedia.org/wiki/List_of_map_projections
    Ответ написан
    Комментировать
  • Как найти определенную "фигуру" в двумерном массиве?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Если длина строк меньше 32, то можно искать с помощью битовых масок. Но для выбора оптимального алгоритма нужно знать полный набор масок для поиска. Искомые фигуры состоят только из нулей, или могут быть сочетания нулей и единиц?
    Ответ написан
    Комментировать
  • Пара вопросов. Стоит ли так использовать функции? Как лучше завершать выполнение функции?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Если a и b глобальные, то лучше сделать одну глобальную функцию, которая на них будет смотреть:
    int CmpAB(int same,int diff){ return a==b ? same : diff; }

    и положить её рядом с переменными. Потом туда же перетаскивать и другие обращения к ним. Глядишь, и соберётся класс с какими-нибудь осмысленными свойствами.
    return - вещь хорошая, но если возвращать значение, то в нём должен быть смысл. Ради сокращения пары скобок придумывать тип возврата не стоит. Так что
    if(a == b){
       print("test");
       return;
    }
    // код, если a != b
    Ответ написан
    Комментировать
  • Как верно реализовать внешнюю сортировку естественным слиянием с порогом?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Не очень понятно, зачем здесь Z. Если 1000 структур помещается в памяти, и мы их можем отсортировать, то казалось бы - читаем из файла 10 раз по 1000 структур (по порядку, за один проход), каждый раз структуры сортируем и в отсортированном порядке помещаем в новый файл. Всего получается 10 файлов. Дальше - обычный merge. Кстати, слить можно сразу все 10 файлов, если их можно открыть одновременно. Если нет - сливаем два самых маленьких файла, пока не останется один (выгодно, чтобы сливаемые файлы были примерно одинакового размера - как группы символов в коде Хаффмана).
    Возможно, Z придумали, чтобы файлы были не совсем одинаковыми, чтобы алгоритм не закладывался на одинаковость размера. Но тогда лучше было бы, например, "случайное число от 500 до 1000". В любом случае, если брать не максимум, то алгоритму только хуже.
    Ответ написан
    Комментировать
  • Каков алгоритм и суть работы реально существующего скрипта 100% предсказания результата, загаданного человеком?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Задумайте число X от 103 до 997, в котором первая и последняя цифра отличаются больше, чем на 1. Запишите его в обратном порядке (получится Y), и вычтите из большего из чисел X,Y меньшее. Получится Z. Теперь найдите сумму числа Z, записанного в обратном порядке, и самого Z. Получится 1089. Это магия следующего порядка.
    Ответ написан
    Комментировать
  • Как подогнать результат имея список цифр и итоговую сумму?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    А в этом примере не будет лучше 1*33+2*1+7*1+9*1=51? Задействованы все цифры, причём в максимально возможном количестве.
    Ответ написан
  • Как найти известный маркер на изображении?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Я для поиска таких маркеров (правда, без внешнего полукольца) начинаю с того, что для каждой точки строю "окружность" с центром в этой точке, состоящую из 20 точек (радиус может варьироваться) и проверяю, насколько она симметрична. Если средний квадрат разностей яркостей симметричных точек заметно меньше дисперсии, то это кандидат на маркер. Потом проверяю на подобие (беру окружность вдвое меньшего радиуса). Если оба критерия прошли - идёт проверка уже по площадям (опять же, проверяется симметрия, самоподобие и однородность белого и чёрного). Границы приходится распознавать только для определения центра с субпиксельной точностью (достигается точность 1/10 пикселя).
    К сожалению, процесс довольно медленный. Особенно, если заранее размер маркера неизвестен, и приходится проверять разные масштабы.
    Про яркостную границу можно предложить прогнать алгоритм, основанный на выделении границ, для нескольких порогов яркости. Какой-нибудь да сработает. Но как описать и быстро распознать ситуацию "при сканировании пройден центр маркера", я ещё не придумал.
    Ответ написан
    Комментировать
  • Существует ли алгоритм реорганизации списка оптимальный по количеству перестановок?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Если есть операция IndexOf для поиска элемента, то оптимальное число перестановок достигается так (превращаем L1 в L2):
    for(int i=0;i < L1.Count;i++){
      while(L1[i]!=L2[i]){
         L1.swap(i,L2.IndexOf(L1[i]));
      }
    }

    При каждом обмене текущий элемент L1[i] ставится на то место, на котором он стоял бы в списке L2, и больше с этого места не уходит.
    Ответ написан
    2 комментария
  • Как правильно сравнить массивы и оценить их схожесть?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Существует решение, работающее в худшем случае за O(N*sqrt(N*log(N))), а в типичном - за O(N*log(N)).
    Пусть наши массивы - A и B.
    Создадим массив Q из 2*N троек, содержащих (элемент массива, индекс в массиве, какой это массив - A или B).
    Сортируем по полю "элемент массива".
    Заводим массив C длины N, в котором будем считать C[k]=число совпадений при сдвиге на k позиций.
    Просматриваем отсортированный массив Q. Для каждого значения X в нём сразу видно, сколько раз и на каких местах X встретился в массивах A и B. Пусть он p раз встретился в A (в позициях a1,a2,...,ap) и q раз - в B (в позициях b1,b2,...,bq). Если p*q < N*log(N), то за p*q операций модифицируем C, увеличивая на 1 все C[(bj-ai) mod N].
    В противном случае строим массивы из 0 и 1, содержащие маски вхождения X в A и B, и считаем с помощью быстрого преобразования Фурье их свёртку. Прибавляем её к C.
    Наихудший для этого алгоритма случай - когда в массивах примерно sqrt(N/log(N)) различных значений, которые встречаются примерно одинаковое количество раз.
    Ответ написан
    Комментировать
  • 3D триангуляция?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    В общем, делается так.
    Пусть фигура задана набором треугольных граней с известной ориентацией. В каждом ребре сходится чётное число граней (возможна, например, фигура из двух тетраэдров, склеенных по ребру), и при обходе любого ребра ориентации граней, сходящихся в нём, чередуются (например, идут грани ABC, BAD, ABE, BAF). Даже если в исходной модели таких странных рёбер нет, они могут возникнуть на промежуточном этапе.
    Выбираем любые две грани со следующими свойствами:
    - у них есть общее ребро AB;
    - при обходе этого ребра они являются последовательными
    - внутренний угол между этими гранями меньше 180 гр.
    Такие грани найдутся всегда. Пусть это ABC и BAD.
    Рассмотрим тетраэдр ABCD. Проверим, нет ли у него внутри других вершин. Если нет - заменим грани ABC и BAD на CAD и BCD (вырезав, тем самым, ABCD из модели). Если есть - возьмём ту вершину E из них, которая ближе всего к грани ABC, и заменим грань ABC на три грани ABE, AEC, EBC.
    После этого просмотрим грани, и если оказалось, что какая-то грань встречается дважды с противоположными ориентациями (ABC и ACB) - выбрасываем обе копии.
    Процесс повторяем, пока все грани не исчезнут.
    Всё.
    Ответ написан
    4 комментария
  • Цикл в 100.000 итераций vs "умного" цикла?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Что известно про заполненные ячейки? Они образуют непрерывный фрагмент? Прижат ли он к началу массива, или к концу, или у него известна минимальная длина?
    Если ваш алгоритм применить к массиву, в котором заполнены ячейки с 10001 по 19999, он их не заметит, и скажет, что массив пуст. Так и предполагается?
    Если заполнен непрерывный участок с неизвестной заранее длиной L, то есть простой способ найти его за O(N/L) операций (а потом обработать за O(L)).
    Ответ написан
    4 комментария
  • Можно ли посчитать количество цифр в документе, в котором содержатся результаты подсчёта?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Я не уверен, можно ли решить эту задачу, если документ состоит из 97 девяток.
    Ещё одна девятка будет в левом столбце таблицы. Какая-нибудь из цифр от 0 до 8 вряд ли встретится 9 раз. Так что девяток будет 98+ число девяток в последней клетке таблицы. Вот и получается:
    Если в последней клетке 0 девяток, то в ней число 98+0=98
    Если в ней одна девятка, то в ней 98+1=99
    Если в ней две девятки, то в ней 98+2=100
    Во всех случаях получаем противоречие.

    UPD. Задача решается довольно просто.
    Пусть у нас уже собрана статистика о числе разных цифр в теле документа (B[0] нулей, B[1] единиц...)
    Сразу прибавим к этим числам по единичке, чтобы учесть левый столбик.
    Оценим сверху число цифр в правой части таблицы. Например, как S=10*(log_10(B[0]+..+B[9])+2).
    Каждая цифра в полном документе встретится не более B[n]+S раз, значит, в правой части таблицы для неё будет число, лежащее от D0[n]=B[n] до D1[n]=B[n]+S.
    Прооптимизируем диапазоны D0..D1. Для этого для каждой цифры переберём все числа от D0[n] до D1[n], посмотрим количество разных цифр в каждом из этих чисел, возьмём минимальное и максимальное значение вхождений. Например, если D0[n]=997, D1[n]=1013, то в клетке, соответствующей n, 0,1 и 9 могут встретиться от 0 до 3 раз, а остальные цифры - 0 или 1 раз.
    Просуммируем полученные диапазоны по всем n - получим новую таблицу диапазонов D0n,D1n. Пересечём их с D0,D1.
    Есть 3 варианта.
    Все полученные диапазоны совпали с D0..D1. Выходим из цикла.
    Один из новых диапазонов пуст. В этой ветке решений нет, возвращаемся из функции.
    Какой-то диапазон изменился. Продолжаем оптимизировать.

    После того, как диапазоны стабилизировались, смотрим их длины. Если все они длины 1 (т.е. D0=D1), то мы нашли решение. Если нет - то берём самый короткий диапазон длины больше 1, перебираем все его значения D0[n]<=u<=D1[n], подставляем каждое из них в копию таблицы диапазонов (D0n[n]=D1[n]=u) и рекурсивно вызываем функцию оптимизации.
    Перебор оказывается небольшим, вот примеры (ncall - число рекурсивных вызовов):

    For 1 2 3 4 5 6 7 8 9 10
    0->5 1->7 2->5 3->5 4->6 5->10 6->9 7->10 8->10 9->12
    ncall=696

    For 1 1 1 0 0 0 0 0 0 0
    0->2 1->7 2->5 3->1 4->1 5->2 6->1 7->2 8->1 9->1
    ncall=486

    For 0 0 0 0 0 0 1 0 0 0
    ncall=464

    For 0 0 0 0 0 0 0 1 0 0
    0->1 1->7 2->2 3->3 4->1 5->1 6->1 7->3 8->1 9->1
    0->1 1->6 2->3 3->3 4->1 5->1 6->2 7->2 8->1 9->1
    0->1 1->6 2->4 3->1 4->2 5->1 6->2 7->2 8->1 9->1
    ncall=482

    For 3997 19995 5677 998 799996 392 7 94 8998 99985
    0->4014 1->20000 2->5679 3->1000 4->800001 5->394 6->10 7->96 8->9000 9->99994
    0->4013 1->20001 2->5679 3->1001 4->800000 5->394 6->10 7->96 8->9000 9->99994
    ncall=47356
    Ответ написан
    4 комментария
  • Как найти повторяющееся слово в строке?

    Mrrl
    @Mrrl
    Заводчик кардиганов
    Попробуйте алгоритм Кнута — Морриса — Пратта. Если с его помощью найти строку в самой себе, то ответом будет как раз сдвиг на повторяющееся слово. Но придётся повозиться с таблицей побочных переходов.

    В общем, получается вот такой простой алгоритм (правда, на C++):
    int maxrepword(char *A){
    	int *ref=new int[strlen(A)];
    	int b=0,s=1;
    	ref[0]=-1;
    	while(A[s]){
    		if(A[s]==A[b]) ref[s++]=b++;
    		else if(b==0) ref[s++]=-1;
    		else b=ref[b-1]+1;
    	}
    	delete[] ref;
    	return s-b;
    }

    Возвращает длину искомого слова. Время работы - линейное.
    Ответ написан
    Комментировать