Определение сложности для рекурсивных функций (запись Big O)

Завтра у меня есть компьютерная наука, и мне нужна помощь в определении сложности этих рекурсивных функций. Я знаю, как решать простые случаи, но я все еще пытаюсь научиться решать эти более сложные случаи. Это были лишь некоторые из примеров проблем, которые я не мог понять. Любая помощь была бы высоко оценена и очень помогла бы в моих исследованиях, спасибо!

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

Ответ 1

Временная сложность в нотации Big O для каждой функции в численном порядке:

  • Первая функция вызывается рекурсивно n раз до достижения базового аргумента, поэтому ее O(n), которую часто называют linear.
  • Вторая функция называется n-5 для каждого времени, поэтому мы вычитаем пять из n перед вызовом функции, но n-5 также O(n). (На самом деле называется порядком n/5 раз, и O (n/5) = O (n)).
  • Эта функция представляет собой log (n) base 5, для каждого раз мы делим на 5 перед тем как вызывать функцию так, чтобы ее O(log(n)) (база 5), часто называемая логарифмической, и чаще всего анализ нотации и сложности Big O использует базу 2.
  • В четвертом, это O(2^n) или экспоненциальный, поскольку каждая функция вызывает вызов дважды, если она не была рекурсивно n.
  • Что касается последней функции, цикл for принимает n/2, так как мы возрастаем на 2, а рекурсия принимает n-5 и поскольку цикл for вызывается рекурсивно, поэтому сложность времени находится в (n-5) * (n/2) = (2n-10) * n = 2n ^ 2- 10n, из-за асимптотического поведения и наихудшего сценария или верхней границы, что большой O стремясь, нас интересует только наибольший член, поэтому O(n^2).

    Удачи вам на ваших промежуточных уровнях;)

Ответ 2

В случае, когда n <= 0, T(n) = O(1). Следовательно, временная сложность будет зависеть от того, когда n >= 0.

Мы рассмотрим случай n >= 0 в части ниже.

1.

T(n) = a + T(n - 1)

где a - некоторая константа.

По индукции:

T(n) = n * a + T(0) = n * a + b = O(n)

где a, b - некоторая константа.

2.

T(n) = a + T(n - 5)

где a - некоторая константа

По индукции:

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

где a, b - некоторая константа, k <= 0

3.

T(n) = a + T(n / 5)

где a - некоторая константа

По индукции:

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

где a, b - некоторая константа

4.

T(n) = a + 2 * T(n - 1)

где a - некоторая константа

По индукции:

T(n) = a + 2a + 4a + ... + 2^n * a + T(0) * 2 ^ n 
     = a * 2^(n+1) - a + b * 2 ^ n
     = (2 * a + b) * 2 ^ n - a
     = O(2 ^ n)

где a, b - некоторая константа.

5.

T(n) = n / 2 + T(n - 5)

Мы можем по индукции доказать, что T(5k) >= T(5k - d) где d = 0, 1, 2, 3, 4

Write n = 5m - b (m, b - целое число, b = 0, 1, 2, 3, 4), то m = (n + b) / 5:

T(n) = T(5m - b) <= T(5m)

(На самом деле, чтобы быть более строгим здесь, следует определить новую функцию T'(n) такую, что T'(5r - q) = T(5r) где q = 0, 1, 2, 3, 4. Мы знаем T(n) <= T'(n) как доказано выше. Когда мы знаем, что T'(n) находится в O(f), что означает, что существуют константа a, b, так что T'(n) <= a * f(n) + b, мы можем получить, что T(n) <= a * f(n) + b и, следовательно, T(n) находится в O(f). Этот шаг на самом деле не нужен, но легче думать, когда вы не нужно иметь дело с остатком.)

Расширение T(5m):

T(5m) = 5m / 2 + T(5m - 5) 
      = (5m / 2 + 5 / 2) * m / 2 + T(0) 
      = O(m ^ 2) = O(n ^ 2)

Следовательно, T(n) есть O(n ^ 2).

Ответ 3

Один из лучших способов найти для аппроксимирования сложности рекурсивного алгоритма - это рисование дерева рекурсии. Когда у вас есть рекурсивное дерево:

Complexity = length of tree from root node to leaf node * number of leaf nodes
  • Первая функция будет иметь длину n и количество листов node 1, поэтому сложность будет n*1 = n
  • Вторая функция будет иметь длину n/5 и количество листовых узлов снова 1, поэтому сложность будет n/5 * 1 = n/5. Он должен быть приближен к n

  • Для третьей функции, поскольку n делится на 5 на каждый рекурсивный вызов, длина рекурсивного дерева будет log(n)(base 5), а число листовых узлов снова 1, поэтому сложность будет log(n)(base 5) * 1 = log(n)(base 5)

  • Для четвертой функции, так как каждый node будет иметь два дочерних узла, количество листовых узлов будет равно (2^n), а длина рекурсивного дерева будет log n, поэтому сложность будет (2^n) * log n. Но так как log n не имеет значения перед log n, его можно игнорировать, а сложность можно назвать только (2^n).

  • Для пятой функции есть два элемента, вводящих сложность. Сложность, введенная рекурсивным характером функции и сложности, введенная циклом for в каждой функции. Выполняя приведенный выше расчет, сложность, возникающая при рекурсивном характере функции, будет ~ n и сложностью, обусловленной циклом n. Общая сложность будет n*n.

Примечание. Это быстрый и грязный способ вычисления сложности (ничего официального!). Хотелось бы услышать отзывы об этом. Спасибо.