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

    @Mercury13
    Программист на «си с крестами» и не только
    Вместо
    boolean[][] visited = new boolean[ROW][COL];

    надо сделать такое:
    static final byte DIR_UNKNOWN = 0;
    static final byte DIR_UP = 1;
    ...
    
    byte[][] bestDirection = new boolean[ROW][COL];  // изначально заполнено DIR_UNKNOWN


    for (int dir = DIR_UP; dir <= DIR_RIGHT; ++dir) {
      ...
      bestDirection[row][col] = dir;
    }

    Добравшись до финиша, делаем обратный ход. А именно: определяем, какой bestDirection у финиша, и если он, например, DIR_UP — смещаемся на 1 вниз. И так далее, пока не доберёмся до старта.

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

    @Mercury13
    Программист на «си с крестами» и не только
    Я бы работал по такой формуле:

    result = ((original / max)1 / gamma) · 255

    Original — это полученное алгоритмом Ву значение (float или хотя бы short!)
    max — максимально возможное значение original. Для short, например, это 65535.
    gamma — традиционно 2,2.
    Ответ написан
    Комментировать
  • Можно ли число представить в виде float?

    @Mercury13
    Программист на «си с крестами» и не только
    Float содержит 23 бита мантиссы и неявную единицу. Порядок float запредельный и даже qword не упрётся в него.
    Это значит: всё, что дальше 23 бит от верхней единицы, должно быть нулём.

    bool isPreciseFloat(unsigned long long n)
    {
      // Простейшая проверка: стираем нижние 24 бита
      // Если в них вписываемся — ДА.
      unsigned long long n1 = n & ~((1ULL << 24) - 1);
      if (n1 == 0)
        return true;
    
      // Получаем верхнюю единицу
      // (можно также двоичным поиском, но я этого с листа не напишу)
      while (true) {
        unsigned long long n2 = n1 & (n1 - 1);
        if (n2 == 0)
          break;
        n1 = n2;
      }
    
      // Получаем маску всего, что ниже 23 бит от верхнего бита.
      n1 >>= 23;
      --n1;
    
      // Проверяем по маске
      return (n & n1) == 0;
    }


    Писал «с листа», могут быть ошибки.
    Ответ написан
  • Как отсортировать массив с зависимостями элементов друг от друга?

    @Mercury13
    Программист на «си с крестами» и не только
    Это так называемая «топологическая сортировка». Делается через последовательный поиск в глубину, начиная со всех возможных элементов.

    алг recurse(поссылке x : элемент, поссылке результат : список)
      если x.метка = чёрный
        возврат
      если x.метка = серый
        стоп ("найдена циклическая зависимость")
      x.метка := серый
      для всех y : зависимых от x
        recurse(y)
      x.метка := чёрный
      результат.добавить(x)
    
    алг main()
      для всех x : массив
        x.метка := белый
      result := []
      для всех x : массив
        recurse(x, result)


    Вкратце: проводим стандартный поиск в глубину, начиная со всех элементов, и при выходе вносим элемент в список.
    Ответ написан
    1 комментарий
  • Почему n^3 работает быстрей чем 2^n?

    @Mercury13
    Программист на «си с крестами» и не только
    Одно из двух.
    А. O(n³) и O(2n) — сложность каких-то алгоритмов.

    Читайте определение символов Ландау, и будет всё понятно.
    n³ = o(2n) при n→∞, что означает:

    lim{n→∞} n³ / 2n = 0.

    Что означает: при безграничном повышении n алгоритм, работающий за n³, будет иметь всё большее и большее преимущество перед конкурентом.

    Б. n³ и 2n — функции, которые нам надо вычислить.

    Сложность первой O(1) (всегда два умножения), сложность второй в общем случае — O(log n) (из-за того, что логарифмы от разных оснований отличаются на константу, а константу символы Ландау не учитывают, основание логарифма не пишут).

    UPD. Что значит «в общем случае»? Оценку могут увеличить различные второстепенные алгоритмы вроде выделения памяти и преобразования в десятичный вид, и уменьшить — то, что 2n можно вычислть сдвигом. Не забудьте, что сложность алгоритмов определяется при n→∞.
    Ответ написан
    Комментировать
  • Как сформулировать алгоритм для визуального редактирования запросов?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    С вопросом 3 оказалось всё одновременно просто и сложно. Дырка — это НЕ тождественный TRUE, это нечто нейтральное к операциям до или после (что приоритетнее). Если OR — внешняя операция, AND — приоритетная, TRUE/FALSE — соотв. нейтральный элемент, то…
    A OR (B AND TRUE) OR C = A OR B OR C, то есть AND уходит
    (A AND B) OR (TRUE AND C) = (A AND B) OR C, то есть OR занимает место AND.
    Так что действительно получается наименее приоритетный из () AND () и AND.

    С вопросом 1 так пока и есть.

    С вопросом 2 задача оказалась похожа на алгоритм сортировочной станции. С одной стороны, у нас нет ни скобок, ни префиксных/постфиксных функций, ни правоассоциативных операций.

    С другой — для каждого элемента стека мы держим op, firstIndex и lastIndex (индексы начала/конца операнда).
    Для первого элемента кидаем (⌀, 1, 1). Или (⌀, 0, 0), если индексы с нуля.
    Для второго (AND, 2, 2).
    Когда попадается третий (OR, 3, 3), срабатывает условие, и мы делаем операцию «сброс AND», состоящую из трё1х вещей:
    1. Для первого lastIndex в случае TRUE делаем переход на второе firstIndex.
    2. Где-то записываем: у первого lastIndex переход в случае FALSE будет таким же, как и у второго lastIndex.
    3. Объединяем эти два элемента, присвоив второй lastIndex первому элементу.
    Ну и добавляем наш (OR, 3, 3) в стек — это делается всегда, независимо от того, сколько раз сработало условие сброса.
    Как сбросить OR — думаю, понятно.
    Таким образом, теперь наш стек будет такой: (⌀, 1, 2) (OR, 3, 3).
    Наш план: (+2 −?) (+? −?) …
    Наш список аналогов: (1, 2, FALSE)

    Четвёртый элемент уйдёт в стек, пятый вызовет сразу два сброса.
    Стек (⌀, 1, 4) (OR, 5, 5).
    План: (+2 −?) (+? −3) (+4 −?) (+? −?)…
    Наш список аналогов: (1, 2, FALSE), (3, 4, FALSE), (2, 4, TRUE)

    Когда список кончится, проводим операцию сброса, пока в стеке не останется (⌀, 1, 8), а затем заполним список аналогов с конца.
    Если вопросительный знак всё-таки остался — это будет +TRUE или −FALSE.
    Ответ написан
    Комментировать
  • Что выполняют эти функции в коде?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Найти «в лоб» НОД и похѣрить его.
    2. Найти «в лоб» НОК и отправить его туда же.
    Повторяю, обе функции крайне неэффективны, а их результат идёт в никуда. Как только управление пересечёт точку с запятой, переменная исчезнет.
    Ответ написан
    Комментировать
  • Как сравнить точность аналитического алгоритма и его программной реализации?

    @Mercury13
    Программист на «си с крестами» и не только
    Есть вычитание близких чисел? Какие-нибудь циклы вроде СЛАУ больших порядков и решения дифуров? Знакопеременные ряды? Если нет, можно писать, что вклад машинной арифметики в точность алгоритма незначителен.
    А если есть, нужно исследовать, кто и как может многократно усилить ошибки округления.
    Ради эксперимента можно также сравнить результаты во float и double.

    Пример алгоритма, который аналитически устойчив, а вычислительно — нет: e−x «в лоб» при достаточно больших x. Связан он с тем самым вычитанием близких чисел: промежуточные члены ряды могут быть достаточно большие, а в результате выходит 0,0…01.

    UPD. Вот такую книжку нашёл: info.alnam.ru/book_clm.php?id=26
    Ответ написан
    2 комментария
  • Как называется такая задача?

    @Mercury13
    Программист на «си с крестами» и не только
    Если считать, что любую передачу можно смотреть только целиком — это будет задача о заявках (=activity-selection problem).
    Решается жадным алгоритмом в таком виде. Берутся самые приоритетные передачи и решается классическая задача о заявках: берём ту, которая кончается раньше всех, затем ту, которая кончается раньше всех и не противоречит имеющейся… В «окна» тем же алгоритмом вставляют менее приоритетные, и т.д.

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

    @Mercury13
    Программист на «си с крестами» и не только
    Это так называемая асимптотическая сложность — сложность при n→∞.

    Что никто не упомянул — так называемые символы Ландау.
    a(n) = o(b(n)), (читается «о малое», «меньшего порядка чем»), если lim{n→∞}a/b = 0
    a(n) = O(b(n)), (читается «о большое», «не большего порядка чем»), если a/b ограничено сверху (для дискретного n этого хватит, для непрерывного надо добавить «для какого-то n>N»).

    Два символа Дональда Кнута дополняют символы Ландау.
    a(n) = Ω(b(n)), если b(n) = O(a(n)). «Не меньшего порядка чем»
    a(n) = Θ(b(n)), если a(n) = O(b(n)) и b(n) = O(a(n)). «Такого же порядка».

    Ну а дальше идут простые пределы.
    • axn + bxn−1 + … = O(cxk + dxk−1 + …), если n ≤ k;
    • те же два многочлена o(·), если n < k.
    • те же два многочлена Θ(·), если n = k.
    Константа не важна: во-первых, при n→∞ меньшая сложность рано или поздно перевесит меньшую константу. Во-вторых, особенности реализации того или иного алгоритма на том или ином компиляторе/железе (больше/меньше команд, лучше/хуже с кэшем) эту константу могут варьировать в широких пределах;

    А значит, многочлен n(n−1)/2 = n²/2 − n/2 = Θ(n²).

    Да, и ещё: вы спутали асимптотический порядок самогó многочлена и асимптотическую сложность его вычисления. Первое, как я уже сказал, n². Сложность вычисления в лоб — n+const = O(n). А «не в лоб» — «за константу» , O(1).
    Ответ написан
    Комментировать
  • Как изменить систему счисления из 0-f в 0-z-Z?

    @Mercury13
    Программист на «си с крестами» и не только
    Скорее всего, какой-то вариант BASE64.
    Ответ написан
    Комментировать
  • Как найти число которое будет делиться без остатка на все элементы массива?

    @Mercury13
    Программист на «си с крестами» и не только
    Берём НОК. При расчёте НОКа случилось переполнение ((a · b) div b ≠ a) → нет такого.
    Дальше (MAX_UNSIGNED_LONG div НОК) · НОК.

    UPD. Поставил новый код обнаружения переполнений при умножении; старый явно ошибочный: 11·11 = 121, и при двух десятичных знаках будет 21 > 11.
    UPD2. В курсе, как считают переполнение-устойчивый НОК? НОК(x,y) = (x div НОД(x,y)) * y
    Ответ написан
    Комментировать
  • Как реализовать метод буквенного номера по числу?

    @Mercury13
    Программист на «си с крестами» и не только
    Вот мой действующий код на «Си с крестами». Индексы — ВНИМАНИЕ — начинаются с нуля. Сумеете перевести на Яву, и из второго оставить одни буквы? Первое действует для любой длины, второе — для длины до 3 символов, присущей Excel’ю.

    std::wstring xl::colNameW(
            size_t aIndex)
    {
        wchar_t q[21];  // more than enough even for 64-bit system :)
        wchar_t* ptr = q + 20;
        *ptr = 0;   // debug
    
        // Getting size
        size_t n = 1;
        unsigned long long pw = 26;
        while (aIndex>=pw && n<20)
        {
            aIndex -= static_cast<size_t>(pw);
            pw *= 26;
            ++n;
        }
    
        FOR_S(i, 0, n)  // макрос, означающий for (size_t i = 0; i < n; ++i)
        {
            *(--ptr) = static_cast<wchar_t>(L'A' + (aIndex % 26));
            aIndex /= 26;
        }
        return std::wstring(ptr, n);
    }
    
    namespace {
    
        bool isCap(const char* x, const char* aEnd)
        {
            return (x != aEnd && *x >= 'A' && *x <= 'Z');
        }
    
        bool isDig(const char* x, const char* aEnd)
        {
            return (x != aEnd && *x >= '0' && *x <= '9');
        }
    
    }   // anon namespace
    
    
    xl::CellCoords xl::parseCellCoords(
            const char* aStart, const char* aEnd)
    {
        enum {
            AA = 26,
            AAA = 26 + 26 * 26
        };
        xl::CellCoords r;
        // Letter
        r.col = 0;
        if (!isCap(aStart, aEnd))
            return CellCoords::bad();
        r.col = *(aStart++) - 'A';
        if (isCap(aStart, aEnd)) {
            r.col = (r.col * 26) + *(aStart++) - 'A';
            if (isCap(aStart, aEnd)) {
                r.col = AAA + (r.col * 26) + *(aStart++) - 'A';
            } else {
                r.col += AA;
            }
        }
        // Number
        r.row = 0;
        while (isDig(aStart, aEnd)) {
            size_t r0 = r.row;
            r.row = r.row * 10 + *(aStart++) - '0';
            if (r.row < r0)
                return CellCoords::bad();
        }
        if (r.row == 0 || aStart != aEnd)
            return CellCoords::bad();
        --r.row;
        return r;
    }


    Принцип действия перевода цифра → буква.
    Находим, сколько букв в нашей колонке. Допустим, три. Вычитаем из цифры номер колонки AAA (т.е. 26 + 26·26, если нумеровать с нуля, и 1 + 26 + 26·26 — если с единицы), и оставшееся переводим в 26-ичную систему счисления (A=0, B=1, C=2…).

    Принцип действия перевода буква → цифра аналогичен. Находим количество букв. Если их три, то переводим из 26-ичной системы счисления в цифру и прибавляем номер колонки AAA.
    Ответ написан
    Комментировать
  • Как понимать алгоритмы?

    @Mercury13
    Программист на «си с крестами» и не только
    Есть три специфичных вида алгоритмов.
    • Математические. Из них я могу припомнить геометрические (выпуклая оболочка) и вычислительные (метод Рунге-Кутты).
    • Связанные с особыми структурами данных.
    • Параллельные.

    Для математических алгоритмов, естественно, нужна та область математики, с которой мы имеем дело. Скажем, для геометрических — векторная геометрия, для вычислительных — какие-то куски муть-анализа. Для вычислительных также важно понимать устройство чисел с плавающей запятой.

    Из общего — те части математики, которые служат основополагающими принципами работы компьютеров. Теория множеств, булева алгебра, комбинаторика, теория автоматов… Также важно изучить символы Ландау и понятие «асимптотическая сложность алгоритма».

    Для параллельных важно понимать архитектуру параллельных систем.
    Ответ написан
    Комментировать
  • Какой формулой определить где находится персонаж - за другим персонажем, перед, справа-слева и тд.?

    @Mercury13
    Программист на «си с крестами» и не только
    Считаем, что у нас FPS, т.е. есть некая абсолютная «горизонталь». Обычно в таких играх взгляд хранится через углы (yaw, pitch, roll).
    Взад-вперёд: знак скалярного произведения двух векторов: единичного вектора взгляда без учёта тангажа (cos(yaw), sin(yaw)) и вектора на персонажа (x1−x0, y1−y0).
    Влево-вправо: знак косого произведения той же пары векторов.
    Вверх-вниз: и так понятно.
    Все три в одних единицах, и у кого модуль больше, то и пишем: сверху, справа и т.д. Возможно, придётся сделать скидку на рост персонажей и их диаметр.

    В играх типа Descent нет верха и низа, горизонтали и вертикали. Верх и низ есть только относительно кабины нашего аппарата, а взгляд хранится в виде ортонормированной матрицы, куска этой матрицы или кватерниона.
    1) Скалярное произведение единичного 3D-вектора взгляда (обычно в таких играх хранится напрямую или считается через кватернион поворота) и 3D-вектора на персонажа.
    2) Скалярное произведение единичного вектора, который смотрит вбок (если такой есть), и вектора на персонажа. Либо смешанное произведение единичного вектора взгляда, единичного вектора макушки и вектора на персонажа.
    3) Скалярное произведение единичного вектора макушки и вектора на персонажа.

    Смешанный случай — с переменным вектором гравитации (в глобальных авиасимуляторах или играх на астероидах) — распиши сам.
    Ответ написан
    Комментировать
  • Как отличить "слово" от бессмысленного набора символов?

    @Mercury13
    Программист на «си с крестами» и не только
    Самый простой способ, пришедший на ум.
    Представить слово как цепь Маркова (это именно что теорвер). Вероятности перехода есть в знаменитой книге Жельникова (да и не только там, я уверен).
    Затем высчитываем вероятность пройти в цепи Маркова именно по этому пути. Если вероятность слишком мала — точно рандомный набор символов.

    Без заготовленных ранее словарей никуда — например, слова «Шымкент» или «парашют» с «невозможными» буквосочетаниями (их вероятность в ЦМ будет, разумеется, нулевая).
    Ответ написан
    Комментировать
  • Как решить хитрую задачку про этажи?

    @Mercury13
    Программист на «си с крестами» и не только
    Пусть турнир назначен на этаже X.
    На этажах 1…X живут L’ людей, на этажах X+1…N — H′ людей.
    Вопрос 1. Стоит ли переносить турнир на этаж X+1?
    Тогда для L′ людей добавляется лишний этаж, для H′ — исчезает лишний этаж, и выигрыш будет H′−L′.
    Стоит, если H′ > L′. Не стоит, если H′ < L′. Безразлично, если H′ = L′.

    Вопрос 2. Возможно ли такое: вверх на 1 перенести не удаётся (будет хуже или безразлично), но этаж Y > X оптимальнее (строго)?
    Раз вверх на 1 перенести нельзя, то H’ <= L’.
    Если этаж Y − 1 безразличен с Y, перенесём турнир туда. Если Y − 2 тже безразличен, то туда, и т.д. Остановимся на этаже Z. Видно, что Z > X + 1: если мы в результате этих движений добрались до X+1, то он лучше X, противоречие.
    Раз Z — оптимальный этаж, то у этажа Z−1 > X есть свои L″ и H″, и поскольку с Z−1 на этаж вверх перенести всё же можно, то H″ > L″.
    С другой стороны, при повышении этажа увеличивается L и уменьшается H, и L″ >= L′ >= H‘ >= H″, то есть H″ <= L″. Противоречие.

    Следствие 3. Если турнир выгодно перенести на несколько этажей выше/ниже, то его выгодно перенести и на этаж выше/ниже.

    Задача 4. Пусть на этажах 1…X−1 живут L людей, на этаже X — M людей, на этажах X+1…N — H людей.
    Для простоты считаем, что сверху и снизу есть по фиктивному пустому этажу — то есть теоретически можно (на практически неоптимально) проводить турнир в подвале и на чердаке.
    Задачу с переносом турнира вверх мы уже решали, и условие того, что турнир нельзя (или безразлично) перенести на этаж вверх: H <= L + M.
    Условие того, что турнир можно (или безразлично) перенести с этажа X−1: H + M >= L.
    Поскольку H + M + L = N (кол-во людей), то эти условия можно записать в виде: L + M >= N/2, L <= N/2
    (всё, получили чеканное условие, и ключи от чердака и подвала можно отдать техслужбам гостиницы.)

    Решение: Идём по этажам, считаем людей. Где накопится N/2, там проводим турнир.

    // считаем полное кол-во
    N = 0
    для i = [0..nFloors)
      N += a[i]
    // ищем, где половинка N
    sum = 0;
    для i = [0..nFloors)
      sum += a[i]
      если sum * 2 >= N
        вывести: i
        СТОП

    Расширенное решение: Если накопилось ровно N/2, безразличны данный этаж, все нулевые над ним, и ещё один ненулевой. (Можно без кода?)
    Ответ написан
    Комментировать
  • Как исправить данный алгоритм поиска максимального подмассива?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Учите, что означает операция «запятая». Если вы хотите выдать наружу три числа — так и пишите возвращаемым типом не int, а структуру из трёх чисел.
    2. Передавать vector по значению невыгодно, лучше по константной ссылке.

    struct ArrayOut {
      int low, high, sum;
    
      ArrayOut() : low(0), high(0), sum(0) {}
      ArrayOut(int aLow, int aHigh, int aSum) : low(aLow), high(aHigh), sum(aSum) {}
    };
    
    
    ArrayOut Find_Max_Crossing_Subarray(const vector<int>& A, int low, int mid, int high)  { ... }


    UPD. Операция «запятая» — это странный хак Си, позволяющий впихнуть несколько операторов туда, где разрешён только один. В основном в цикл for.
    Ответ написан
  • Как в графе найти самый "большой" полный подграф?

    @Mercury13
    Программист на «си с крестами» и не только
    Это штатная задача, т.н. «задача о клике», NP-полная. Ничего лучше усовершенствованного перебора не существует.
    https://ru.wikipedia.org/wiki/Алгоритм_Брона_—_Кербоша
    Ответ написан
    Комментировать
  • Как отсортировать "случайно", с возможностью отсортировать так же в будущем?

    @Mercury13
    Программист на «си с крестами» и не только
    У генератора псевдослучайных чисел есть такое понятие, как «случайная затравка» (random seed). Затравку берут из истинно случайных мест вроде счётчика тактов процессора. Достаточно сохранить затравку — и последующие запуски генератора дадут те же результаты.

    Допустим, манипуляции с затравкой есть вот тут.
    php.net/manual/ru/function.mt-srand.php
    Ответ написан
    Комментировать