algo

Олимпиадные алгоритмы

Оценка сложности алгоритмов

Чтобы выполнить какой-либо алгоритм, компьютеру необходимо проделать определенное количество операций, и это количество ограничено. Оптимальное время работы алгоритма для любых входных данных на олимпиадах – $1$ секунда. Компьютер способен выполнить ограниченное количество операций за секунду (Python позволяет произвести примерно $10^6$ операций в секунду), это значит, что необходимо понимать, впишется ли ваше решение в ограничение по времени или нет.

Для того, чтобы оценивать сложность алгоритма, нам нужно познакомиться с таким понятием, как асимптотика. Асимптотика пришла в информатику из математики, где с помощью нее описывается поведение функций. В информатике так оценивается асимптотическая сложность алгоритмов, так как алгоритмы - это все те же функции.

Введем такое понятие, как $O$-большое. Говорят, что функция $f(n)$ является $O(g(n))$ (читается: $O$-большое от $g(n)$), если существует такая константа (постоянное значение) $C$ и $n_0$, что для всякого $n > n_0$ $f(n) \leq Cg(n)$.

В случае с алгоритмами мы рассматриваем в качестве возрастающей функции $g(n)$ входные данные, стремящиеся к бесконечности. Соответственно, $f(n)$ – асимтотическая сложность нашего алгоритма.

Рассмотрим программу:

    int N;
    cin >> N;
    cout << N + 1 << '\n';

Очевидно, что такая программа будет работать быстро. Вне зависимости от входных данных она будет выполняться за одно и то же количество операций. В таких случаях говорят, что программа работает за константу, а ее асимптотическая сложность – $O(1)$.

Но чаще всего сложность алгоритма напрямую зависит от входных данных. Например, в данной программе:

    int N;
    cin >> N;
    for (int i = 0; i < N; ++i) {
        someActions.here();
    }

цикл повторяется $N$ раз, соответственно, сложность ее работы – $O(N)$. Алгоритмы, сложность которых составляет $O(N)$, называются линейными.

Если мы встречаем двойной цикл:

    int N;
    cin >> N;
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) {
            someActions.here();
        }
    }

то внутренний цикл повторяется $N$ раз по $N$. Таким образом, алгоритм работает за $O(N^2)$, его сложность квадратичная.

Ниже показан график зависимости времени выполнения от размера входных данных:

BFS

Вот примеры известных алгоритмов, имеющих такую асимптотику:

Каждый раз при написании решения какой-либо задачи стоит задумываться над его асимптотической сложностью и обращать внимание на ограничения входных данных. Например, если входные данные могут возрастать до $10^9$, скорее всего, задача решается математически или применяются алгоритмы с логарифмической сложностью. А если входные данные могут быть не больше, чем $10^6$, то, скорее всего, стоит применить линейный алгоритм и т.д.