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

    @Mercury13
    Программист на «си с крестами» и не только
    class Event {  //  интерфейс
    public:
      virtual double time() = 0;   // когда потребуется внимание со стороны менеджера событий
      virtual void process() = 0;  // команда обработать событие
      // virtual int type() = 0;      // можно пристроить, если по какой-то причине надо отличать кресло от генератора клиентов
    };
    
    class EventSink {  // интерфейс
      virtual void add(Event& x);
    }
    
    class Shop;
    
    class Chair : protected Event {
    private:
      double fTime;
      bool fIsOccupied;
    public:
      Shop& shop;
      virtual double time() override { return fTime; }
      virtual void process() override;
      {
         что-то типа…
         если shop.nWaiting != 0 {
            --shop.nWaiting;
            occupy(time);
         } else {
             isOccupied = false;
         }
      }
      void occupy (double aTime) {
            time = aTime + время обслуживания клиента
            shop.manager.add(*this);
       }
       bool isOccupied() { return fIsOccupied; }
    };
    
    class CustomerGenerator : protected Event {
       // Устроен аналогично Chair, служит для генерации потока клиентов
    public:
      virtual void process() override {
         если shop.nWaiting == 0 и кресло свободно {
            shop.chair.occupy(fTime);
         } else {
            ++shop.nWaiting;
         }
         fTime += экспоненциальное время ожидания;
         manager.add(*this);
      }
      void init() {
         fTime = 0;
         process();
      }
    };
    
    class Shop {
    public:
       Chair chair;  // можно несколько кресел
       CustomerGenerator generator;
       int nWaiting;  // сколько человек в очереди
       EventSink& manager;
    }
    
    class Manager : public EventSink {
       Event* events[2];   // события отсортированы по времени
       int nEvents;
    
       void add(Event& x) override {
          // вставить двоичным поиском x в нашу очередь;  тут же можно сделать ограничение
          // «обработать 1000 клиентов» или «работать 8 часов».
       }
    
       И самое главное — «жизненный цикл».
       shop.generator.init();
       Пока очередь не пуста…
       • вытащить из неё событие
       • process его!
    }


    Как оно работает?
    1. на 5-й минуте приходит посетитель. Значит, генератор вносится в очередь со временем 5 минут.
    2. Выбираем генератор из очереди. Поскольку в парикмахерской очереди нет, он садится в кресло (в список вносится кресло со временем 5+8 = 13 минут), в список вносится генератор со временем 5 + 3 = 8 минут.
    3. Из этой пары выбираем генератор (у него 8 минут, против 13 минут у кресла). Он вносит нашего товарища в очередь (теперь в очереди один), в очередь вносится генератор со временем 8+4 = 12 минут.
    4. Снова в очереди кресло и генератор, и минимальное время у генератора. Теперь в парикмахерской ждут двое, и генератор вносим в очередь на время 12 + 30 = 42 минуты.
    5. Теперь минимальное время у кресла, и оно обслуживает ещё одного посетителя, внося себя в очередь на (13 + 8 = 21 минуте.
    6. И снова минимальное время у кресла, оно вносит себя в очередь на 21 + 8 = 29 минут.
    7. Пришла 29 минута, но стричь-то некого! В очереди менеджера остался один генератор.
    8. Выбираем из очереди генератор, и так далее…

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

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

    @Mercury13
    Программист на «си с крестами» и не только
    Вот правила множественного числа разных языков.
    www.unicode.org/cldr/charts/latest/supplemental/la...

    Я с подобным работал, и если есть возможность выдавать один и тот же ответ в двух разных ветках — то так.
    Остаток от деления на 100 от 5 до 20 → «ворон»
    Остаток от деления на 10 равен 1 → «ворона»
    Остаток от деления на 10 от 2 до 4 → «вороны»
    Иначе — «ворон».

    Я эти правила записывал вот в таком виде.
    <pluralRules nDecisions="3" unknownInt="2">    ← вариантов три; если ни одно из правил не подошло, брать последний («ворон»)
    		<rule mod="100" min="10" max="20" decide="2" />  ← остаток на 100 от 10 до 20 — «ворон»
    		<rule mod="10" min="1" max="1" decide="0" />
    		<rule mod="10" min="2" max="4" decide="1" />
    	</pluralRules>
    Ответ написан
    Комментировать
  • Почему рекурсивные алгоритмы работают медленнее своих линейных аналогов?

    @Mercury13
    Программист на «си с крестами» и не только
    Потому что этот алгоритм, будучи рализован в лоб без всяких кэшей и ускорителей, неоптимален! F(n–2) высчитывается дважды, F(n–3) трижды, F(n–4) пять раз, и т.д. по Фибоначчи.

    Что читать, сказать не могу (в том издании Кормена, что у меня, маловато), гугли «динамическое программирование». Хотя поначалу поможет и Кормен.

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

    ЗЗЫ. Программисты не любят рекурсию по многим причинам. 1. Сложно наладить аварийный стоп. 2. Системный стек ограничен и есть риск переполнения.
    Ответ написан
    4 комментария
  • Как реализовать расписание кругового турнира?

    @Mercury13
    Программист на «си с крестами» и не только
    Других методов я просто не вижу. Искать «round-robin tournament algorithm». Но, по-моему, проще поворачивать не кольцо, а схему матчей, поставив одну из команд (лучше последнюю) в центр круга.
    https://commons.wikimedia.org/wiki/File:Round-robi...
    Round-robin-schedule-span-diagram.svg
    Матчам 2-13, 3-12 и т.д. даём фиксированные направления, причём попеременно: 2-13, 12-3, 11-4…
    Направление матча 1-14 постоянно меняется.

    Если команды нумеруются от 0 до N−1, N — чётное, алгоритм получается такой.
    trans := случайная перестановка чисел 0 … N−1
    
    если N чётное
      то M := N−1
      иначе M := N
    
    цикл 2half = false…true
      цикл round = 0…M−1
        цикл shift = 1…[M/2]
          home := (round + shift) % M
          away := (round + M − shift) % M
          если (shift нечётное) xor 2half
            обменять home, away
          вывести trans[home], trans[away]
    
        если N чётное
          home := round
          away := M
          если (round нечётное) xor 2half
            обменять home, away
          вывести trans[home], trans[away]

    Для чётного N одна команда постоянно меняет поле, у остальных — единожды на круге сбивается. Для нечётного N ничего не сбивается.
    Ответ написан
    2 комментария
  • Поддержание максимума в окне?

    @Mercury13
    Программист на «си с крестами» и не только
    Порядок, придумал.

    Создадим список максимальной длины w, в котором содержатся пары (index, value) и value нестрого убывает. Как устроен этот список — разберём позже.

    Сначала список пуст. Сначала w раз проводим операцию «a[i] вошёл». Затем n−w раз — сначала «a[i−w] вышел», затем «а[i] вошёл». Каждый раз list.front — это индекс и значение макс. элемента.

    Операция «a[i] вошёл». С конца списка удаляем все элементы, чей value меньше, чем a[i]. Затем прицепляем пару (i, a[i]) к концу.
    Операция «a[i] вышел». Если в начале списка index=i, удаляем первый элемент. Иначе — ничего не делаем.

    Почему O(n)? Единственное, где сложность неочевидна — удаление в операции «a[i] вошёл». Но удалений будет не больше, чем вставок, а их точно O(n) — так что никаких проблем.

    Ну и на сладкое — как должен быть устроен список.
    — Ограниченная длина (не более w).
    — Удаление из начала.
    — Вставка в конец.
    — Удаление из конца.

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

    @Mercury13
    Программист на «си с крестами» и не только
    Придумал.
    Для каждого узла делаем координату deltaX — смещение по X относительно отца.
    И динамический массив из пар (x1, x2) — границы деревьев на всех уровнях.

    алг рекурсия(узел: Узел)
      длина_мас = 0
    
      // Прогоним алгоритм для сыновей, рассчитаем длину массива
      для всех сыновей (узел) → сын
        рекурсия(сын)  // ыгы, в основе банальный обход в глубину
        длина_мас = макс(длина_мас, сын.границы.длина)
      длина_мас = длина_мас + 1
    
      разложить сыновей на плоскости, руководствуясь их массивами границ
         и вашим чувством прекрасного;  соответственно присвоить ИХ deltaX
         (оставлю это как домашнее задание)
    
      // Рассчитаем границы дли нашего узла
      границы.установить_размер(длина_мас)  
      границы.заполнить(левая=+M, правая=—M)
      полширины = узел.ширина / 2
      границы[0] = (левая = −полширины, правая = узел.ширина − полширины)
      для всех сыновей (узел) → сын
        для i = [0 .. сын.границы.длина)      
          узел.границы[i+1].левая = мин(
               узел.границы[i+1].левая, сын.границы[i].левая + сын.deltaX)
             // если сыновья идут слева направо, этого хватает!
          узел.границы[i+1].правая = сын.границы[i].правая + сын.deltaX
        сын.границы.установить_размер(0)    // массив больше не нужен
    
    // Если же нужны точные координаты каждого узла…
    
    алг уточнитьКоординаты(узел: Узел; Y: целое)
      узел.коорд.Y = Y
      Yсына = Y + высота_яруса
      для всех сыновей (узел) → сын
        сын.коорд.X = узел.коорд.X + сын.deltaX
        уточнитьКоординаты(сын, Yсына)


    +M — запредельно большое число
    −M — отрицательное число с запредельно большим модулем
    Ответ написан
    21 комментарий
  • Почему не работает мой mergesort?

    @Mercury13
    Программист на «си с крестами» и не только
    int left[leftLen]

    Будь осторожен, на «боевых» задачах чревато переполнением стека. Стек вызовов невелик, всего ничего единицы мегабайт.
    Ответ написан
  • Как угадать возраст за наименьшее кол-во попыток?

    @Mercury13
    Программист на «си с крестами» и не только
    Бинарный поиск, однако точка деления — не (a+b)/2, а медиана куска распределения от a до b (т.е. round(F−1([F(a) + F(b)]/2)); F(·) — функция распределения, тупо переделанная из кусочно-постоянного вида в кусочно-линейный, сплайновый или ещё какой-нибудь).
    Немного неоптимально, но крайне просто.

    З.Ы. Эта штука достаточно оптимальна для «гладких» распределений. В общем случае неоптимальна — например, если у нас три возраста с вероятностями 0,45, 0,1 и 0,45, стоит спросить первый, потом третий и уж при полном невезении второй (среднее 1,65 запроса), а не брать среднее, а затем — первое или второе (в среднем 1,9 запросов).

    Если нужен точный оптимум — решать задачу динамического программирования по критерию
    N[a,b] = min{c} (1 + (N[a,c−1]p[a,c−1] + N[c+1,b]p[c+1,b]) / p[a,b]).
    Граничные условия: при a = b N[a,b] = 1; при a>b N[a,b] = 0.
    p[a,b] — суммарная вероятность от a до b включительно; вычисляется как sum[b]−sum[a−1]; sum[i] = sum{x <= i} p(x).
    N[0,M] = ?

    Или, обозначив Q[a,b] как N[a,b]p[a,b],
    Q[a,b] = min{c} (p[c] + Q[a,c−1] + Q[c+1,b]).
    Так как p[0,M] = 1, то Q[0,M] = N[0,M].
    Граничные условия: при a = b Q[a,b] = p[a]; при a>b Q[a,b] = 0.
    Q[0,M] = ?

    Решается задача за O(M²) операций. Если нужно хранить всю информацию, чтобы в нужный момент прокрутить цепь запросов — O(M²) памяти; если только указать оптимум — можно обойтись O(M).
    Ответ написан
    3 комментария
  • Как математически определить уникальное число для любых двух in64?

    @Mercury13
    Программист на «си с крестами» и не только
    Нельзя, простая комбинаторика.
    Одно число — 264 варианта, два числа — 2128 вариантов.
    Принцип Дирихле говорит, что в одной из клеток будут даже не два кролика, а как минимум ceil(2128/264) = 264 кролика — то есть для какого-то ответа будут как минимум 264 пары аргументов, которые его дают.
    Ответ написан
    3 комментария
  • Узко специализированно или широко?

    @Mercury13
    Программист на «си с крестами» и не только
    Это шаблон проектирования «стратегия», и по-хорошему реализуется в виде обработчиков или виртуальных функций.
    Ответ написан
    Комментировать
  • Алгоритмы: Задача о назначениях, как реализовать множественные назначения?

    @Mercury13
    Программист на «си с крестами» и не только
    Непонятно, как вы в такой ситуёвине считаете штраф. Ведь если штраф считается как раньше, а загрузка работника не ограничена, и задачи-то нет — каждую из работ можно дать тому работнику, который выполняет её лучше всех.

    Если же загрузка работника ограничена m работами — тогда решите задачу о максимальном потоке.

    Исток → работник: пропускная m
    работник → работа: пропускная 1
    работа → сток: пропускная 1
    Ответ написан
    Комментировать
  • Как дополнить топологическую сортировку?

    @Mercury13
    Программист на «си с крестами» и не только
    С двузначными метками посещения (bool used[]) — никак.

    Надо перейти на трёхзначные метки (UNVISITED, PARTLY_VISITED, VISITED).

    void dfs (int v) {
      state[v] = PARTLY_VISITED;
        .....
      state[v] = VISITED;
      ans.push_back (v);
    }


    Неплохой пример есть в вашем предыдущем вопросе.
    Ответ написан
    Комментировать
  • Как применить топологическую сортировку?

    @Mercury13
    Программист на «си с крестами» и не только
    Нужно переписать под матрицу смежности вот этот участок кода.
    for(int i = 0;i < Edges[v].size();i ++){
              if(dfs(Edges[v].get(i)))return true;
          }

    Что-то типа этого.
    цикл (i : все вершины)
       если матрица_смежности[v, i]
             если dfs(i)
                 return true;

    Ещё мне не нравятся константы 0, 1 и 2. Объявить бы их как
    UNVISITED = 0;
    PARTLY_VISITED = 1;
    VISITED = 2;

    Или WHITE, GRAY и BLACK, если хотите — в учебниках по алгоритмам их красят в три цвета.
    Ответ написан
    Комментировать
  • Как написать алгоритм спирали?

    @Mercury13
    Программист на «си с крестами» и не только
    688b4461d0a145be8f07116dcdad6612.png
    Ответ написан
    Комментировать
  • Как написать алгоритм спирали?

    @Mercury13
    Программист на «си с крестами» и не только
    И ещё одна штука. Две-семь точек надо располагать по-другому (одна в центре, остальные по кругу на расстоянии dSafe от неё; ну или на худой конец подобрать коэффициент a, чтобы спираль расходилась лишь чуть-чуть). Для восьми-одиннадцати точек надо подбирать индивидуальные a. И только для двенадцати и более подходит a = −0,3·dSafe.
    Ответ написан
    Комментировать
  • Как написать алгоритм спирали?

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

    Чтобы поставить второй кружочек, отойдём от центрального на вектор (dSafe, 0). Для третьего и дальше поступим так.

    Найдём угол касательной к спирали Архимеда. Касательный вектор будет a единиц по радиус-вектору (a — коэффициент спирали r=aф) и r единиц по нормали. Таким образом, если мы в точке (x, y), у нас получается касательный вектор вот такой.
    t = (a·x + r·y; a·y − r·x), r = sqrt(x² + y²)

    Нормируем этот вектор до длины dSafe и добавляем к (x, y).
    Штука номер один. Параметр спирали прямо пропорционален безопасному расстоянию (вы это увидите ниже в коде). И штука номер два — только «бесконечно малые» шаги (при нулевом a, т. е. окружность) будут держать нас на окружности, шаги побольше будут выносить нас наружу, для компенсации сделаем a отрицательным.

    Вот действующий код (на C++ Builder’е)

    namespace {
    
    const double dSafe = 30;
    const int rSafe = dSafe / 2;
    const double a = -0.3 * dSafe;
    
    int toPixel(double x)
    {
        return floor(x + 0.5) + 200;
    }
    
    }
    
    void TfmMain::drawPoint(double x, double y)
    {
        int xx = toPixel(x);
        int yy = toPixel(y);
        Canvas->Ellipse(xx - rSafe, yy - rSafe, xx + rSafe, yy + rSafe);
    }
    
    void __fastcall TfmMain::FormPaint(TObject *Sender)
    {
        Canvas->Brush->Style = bsClear;
        Canvas->Pen->Color = clBlack;
    
        double x = 0, y = 0;
        drawPoint(x, y);
        x += dSafe;
        drawPoint(x, y);
    
        for (int i = 0; i < 120; ++i) {
            double r = sqrt(x*x + y*y);
            double tx = a*x + r*y;
            double ty = a*y - r*x;
            double tLen = sqrt(tx*tx + ty*ty);
            double k = dSafe / tLen;
            x += tx * k;
            y += ty * k;
            drawPoint(x, y);
        }
    }
    Ответ написан
    Комментировать
  • Алгоритм поиска кратчайшего пути в графе

    @Mercury13
    Программист на «си с крестами» и не только

    Если подвижных мостов мало, можно каждую вершину размножить в 2b экземплярах (b — кол-во мостов). Тогда пойдёт даже не Дейкстра, а обычный поиск в ширину.

    Если два моста «сцеплены» друг с другом и наводятся/разводятся одновременно, то в b они идут одной штукой.

    А если мостов много и 2b — непозволительная трата, тогда интересно. Думаю, ничего, кроме эвристик, не поможет. Например, может случиться так, что кратчайший путь от включателя до всех мостов, которыми он управляет, посуху. Или мосты (в смысле управляемые) — это мосты (в смысле теории графов). Или что-нибудь ещё.

    Ответ написан
    1 комментарий
  • Есть ли здесь бывшие олимпиадники? Как олимпиады помогли Вам?

    @Mercury13
    Программист на «си с крестами» и не только
    Скажу сразу, спортивного программирования у меня было не очень много: 10 класс — сборы (подвело незнание стандартной задачи на динамическое программирование, да и в одном месте надо было не Real применить, а Extended: проверяли шесть знаков, а у меня, при формально правильном алгоритме, было три или четыре). 11-й — по своей же глупости вообще не попал в призёры. В университете с командой как-то не заладилось, но это не мешало в одиночку быть второй ACM-«командой» факультета.

    Во-первых, олимпиада позволила мне попасть в университет на собеседование. Как я его проходил — отдельная история, но всё вышло как надо.

    Во-вторых, выработались некоторые приёмы малоглючного программирования. И сейчас посторонние мне часто говорят: «Ты код как на камне высекаешь».

    В третьих, алгоритмы есть алгоритмы. Всегда думаешь: а есть ли способ «наскоком» повысить скорость того или иного метода? Можно ли тут приплести, скажем, std::map?
    Ответ написан
    Комментировать