Рубрики
Без рубрики

Сложность алгоритма по отношению к памяти

Недавно ProfBrunoLopes сделал live morgiovanelli говоря о сложности algor… С тегами performance, code style, java, braziliandevs.

Недавно ProfBrunoLopes сделал live morgiovanelli говоря о сложности алгоритмов, где это обсуждается, так математике, как определить, какой алгоритм был более эффективным, учитывая рост их исполнения по сравнению с размером входного сигнала. В этом тексте я хочу расширить этот анализ на другие аспекты, такие как использование памяти.

Сложность функции фибоначчи

Как краткий обзор, будет сделан анализ сложности времени выполнения алгоритмов для вычисления значения последовательности Фибоначчи , это-последовательность, где первые два значения 1, а остальные-это сумма двух предыдущих значений. Пример алгоритма, который делает этот расчет с помощью рекурсии:

public class FibonacciRecursivo {
    public static int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

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

Таким образом, можно сказать, что сложность этого алгоритма, является экспоненциальной ( (2^n) | ( | ), видно, что рекурсивный вызов не изменяет величины размера проблема должна быть решена для каждого вызова. И из-за его сложности геометрической прогрессии, этот алгоритм считается неэффективным для больших значений.

Однако, поскольку значение из последовательности Фибоначчи был рассчитан, он может быть сохранен в памяти и повторно, вместо того, чтобы вычислить его каждый раз. И необходимо знать только последние два значения последовательности для того, чтобы рассчитать следующий. Таким образом, можно реализовать итерационный алгоритм (который перебирает, использует цикл повторить), как:

public class FibonacciIterativo {
    public static int fibonacci(int n) {
        if (n == 0) {
            return 0;
        }

        int a = 0;
        int b = 1;

        for (int i = 1; i < n; ++i) {
            int aux = a + b;
            a = b;
            b = aux;
        }

        return b;
    }
}

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

Сложность факториала

Другая проблема, которая может быть проанализирована расчета серия номер один. Факториал числа это произведение его за все его предшественники до 1, или просто умножение числа на факториал от своего предшественника, в том, что факториал 1 равен 1. Пример:

4! = 4 * 3!
4! = 4 * 3 * 2!
4! = 4 * 3 * 2 * 1!
4! = 4 * 3 * 2 * 1

Снова можно реализовать алгоритм, который решает эту проблему с помощью рекурсии:

public class FatorialRecursivo {
    public static int fatorial(int n) {
        if (n <= 1) {
            return 1;
        }
        return n * fatorial(n - 1);
    }
}

Где рекурсивных вызовов генерируют что-то вроде диаграммы низкой:

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

Также можно сделать итерационный алгоритм для решения этой проблемы, как:

public class FatorialIterativo {
    public static int fatorial(int n) {
        int fatorial = 1;

        for (int i = 2; i <= n; ++i) {
            fatorial *= i;
        }

        return fatorial;
    }
}

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

Несмотря на то, что эта проблема, как рекурсивный алгоритм, как и алгоритм итеративной имеют одинаковую сложность времени выполнения двух алгоритмов имеют различия в отношении использования памяти. Хотя алгоритм итеративной использует только три переменных (аргументов функции, и две в коде), версия рекурсивно использует только (аргумент функции), но это повторяется для каждого вызова функции, и как это находится в памяти до тех пор, пока последняя функция будет вызвана, эта переменная достигает существовать n раз в памяти, в то же время, в каждом контексте функции, как в примере выполнения, сделать Онлайн-отладчик GDB :

Таким образом, можно сказать, что есть разница в сложности этих кода в отношении использования памяти, рекурсивный алгоритм, используя память, линейный размер входа, и версия итеративный использование памяти стабильно. И этот анализ имеет тот же результат, если сделан на алгоритмы для вычисления последовательности Фибоначчи.

Сложность для по дереву

Можно хранить данные в структуру, которая получает название бинарное дерево , который состоит в организации всех значений, меньших определенного значения слева, и все значения, большие справа, образуя что-то, что напоминает дерево с ветвями. Пример:

Можно использовать рекурсию, чтобы следовать в порядке возрастания этого дерева и печатать на экране их значения. Пример:

class ArvoreValor {
    ArvoreValor esquerda;
    int valor;
    ArvoreValor direita;

    public ArvoreValor(ArvoreValor esquerda, int valor, ArvoreValor direita) {
        this.esquerda = esquerda;
        this.valor = valor;
        this.direita = direita;
    }
}

public class ArvoreRecursiva {
    private ArvoreValor raiz;

    public ArvoreRecursiva() {
        raiz = new ArvoreValor(
            new ArvoreValor(
                new ArvoreValor(null, 1, null),
                2,
                new ArvoreValor(null, 3, null)
            ),
            4,
            new ArvoreValor(
                new ArvoreValor(null, 5, null),
                6,
                new ArvoreValor(null, 7, null)
            )
        );
    }

    private void printAux(ArvoreValor valor) {
        if (valor == null) {
            return;
        }

        printAux(valor.esquerda);
        System.out.println(valor.valor);
        printAux(valor.direita);
    }

    public void print() {
        printAux(raiz);
    }
}

В этом случае были созданы две функции: printAux , который проходит через дерево рекурсивно, и печать , который предоставляет эту функцию, можно назвать внешне более простой способ. В отношении времени выполнения, этот алгоритм имеет сложность линейной, так как она проходит все значения в дереве, и в отношении использования памяти, как только это необходимо, чтобы сохранить переменные, с ветки дерева за раз в памяти, он растет в форме логарифмической относительно количества значений, дерево ( O(log2 n) ), равна высоте дерева, считая, что оно балансировкой (с ветви, приблизительно одинакового размера).

Можно думать о итерационный алгоритм для этой проблемы. Однако, как следует проводить один раз значениями слева и другой значениями справа от каждого значения дерева, чтобы отслеживать состояние и знать, в какую сторону следует идти, создает структуру с такой информацией. Пример:

import java.util.ArrayList;
import java.util.List;

class ArvoreValor {
    ArvoreValor esquerda;
    int valor;
    ArvoreValor direita;

    public ArvoreValor(ArvoreValor esquerda, int valor, ArvoreValor direita) {
        this.esquerda = esquerda;
        this.valor = valor;
        this.direita = direita;
    }
}

enum ArvoreLado {
    Esquerda,
    Direita;
}

class Estado {
    ArvoreValor valor;
    ArvoreLado lado;

    public Estado(ArvoreValor valor, ArvoreLado lado) {
        this.valor = valor;
        this.lado = lado;
    }
}

public class ArvoreIterativa {
    private ArvoreValor raiz;

    public ArvoreIterativa() {
        raiz = new ArvoreValor(
            new ArvoreValor(
                new ArvoreValor(null, 1, null),
                2,
                new ArvoreValor(null, 3, null)
            ),
            4,
            new ArvoreValor(
                new ArvoreValor(null, 5, null),
                6,
                new ArvoreValor(null, 7, null)
            )
        );
    }

    public void print() {
        List estados = new ArrayList();
        estados.add(new Estado(raiz, ArvoreLado.Esquerda));

        while (!estados.isEmpty()) {
            Estado estado = estados.remove(estados.size() - 1);
            ArvoreValor valor = estado.valor;
            if (valor == null) {
                continue;
            }
            switch (estado.lado) {
                case Esquerda:
                    estado.lado = ArvoreLado.Direita;
                    estados.add(estado);
                    estados.add(new Estado(valor.esquerda, ArvoreLado.Esquerda));
                    break;
                case Direita:
                    System.out.println(valor.valor);
                    estados.add(new Estado(valor.direita, ArvoreLado.Esquerda));
                    break;
            }
        }
    }
}

В отношении времени выполнения, этот алгоритм имеет сложность линейной, а по отношению к памяти, как это необходимо для поддержания положения ветви дерева, сложности является логарифмической, так же, как сложность алгоритма с помощью recurção. В этом случае список государств, созданных работает аналогично стек выполнения, используется рекурсивный алгоритм. Это лучшее решение, что поддерживать состояние все значения в памяти, что бы сложности линейный использования памяти, в зависимости от количества значений в дерево. И даже в отношении реализации стека также просто занимая меньше памяти, поскольку не нужно поддерживать состояние всех значений для отрасли, тех, кто еще они имеют какую-то сторону (левую или правую), которые будут обрабатываться. Но это решение, хотя и использует немного меньше памяти, из-за того, та же сложность в отношении использования памяти, будучи код более трудным для чтения и обслуживания, чтобы добиться этого.

Соображение

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

Примеры представлены также считают, что все значения помещаются в int . При работе с большими значениями, например, с помощью нескольких int для представления числа, где каждый int представляет часть стоимости, операций сумма перестают иметь сложность времени постоянно иметь сложность линейной количество int которые представляют собой число, поскольку необходимо выполнить операции сложения для каждого int , что может изменить сложность алгоритма.

Оригинал: “https://dev.to/acaverna/complexidade-do-algoritmo-em-relacao-a-memoria-332b”