Complexidade de Algoritmos
Complexidade de Algoritmos
Complexidade de Algoritmos
_______________________________________________________________________________________________
COMPLEXIDADE DE ALGORITMOS
O mesmo problema pode freqüentemente ser resolvido com algoritmos que diferem
em eficiência. As diferenças entre os algoritmos podem ser irrelevantes para processar
um pequeno número de itens de dados, mas cresce proporcionalmente com a
quantidade de dados. Com a finalidade de comparar a eficiência de algoritmos, uma
medida do grau de dificuldade de um algoritmo, chamada de “complexidade
computacional” foi desenvolvida por Juris Hartmanis e Richard E. Stearns.
Muitas vezes um programa correto se torna inútil para uma entrada de dados
moderadamente grande, pois o tempo de execução é inviável. Sendo assim, é
importante considerar a eficiência de nossos algoritmos. Essas questões são tratadas
pela Teoria da Complexidade de Algoritmos, área da computação que visa a
determinar a complexidade (custo) de um algoritmo, o que torna possível:
• Comparar algoritmos
Como existem muitos algoritmos que resolvem uma mesma classe de problemas,
através dessa medida de complexidade podemos compará-los entre si.
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
2
_______________________________________________________________________________________________
O exemplo a seguir mostra como comparar algoritmos entre si, quanto ao seu custo de
tempo. Por exemplo, suponha uma corda de tamanho T e que deve-se “organizá-la”
de tal forma que ocupe o menor espaço possível. Vamos analisar três métodos:
1. Enrolar no braço
Segurando uma das extremidades com a mão, e dando a volta sob o cotovelo,
enrolamos a corda várias vezes, sempre mantendo o mesmo comprimento do laço;
2. Enrolar sobre si
Usando algum apoio fixo (por exemplo, uma maçaneta), enrolamos a corda sobre
esse ponto fixo, aumentando gradativamente o tamanho do laço;
3. Dobrar sucessivamente
Dobre a corda ao meio, unindo as duas extremidades. Repita esse processo até
que não seja mais possível dobrar.
Uma função que expressa a relação entre n e t usualmente é muito mais complexa, e
o cálculo dessa função é importante somente em relação a grandes quantidades de
dados; quaisquer termos que não modifiquem substancialmente a grandeza da função
devem ser eliminados da função. A função resultante fornece somente uma medida
aproximada da eficiência da função original. No entanto essa aproximação é
suficientemente próxima do original, especialmente para uma função que processa
grandes quantidades de dados. Essa medida de eficiência é chamada de
complexidade assintótica e é usada quando se desprezam certos tempos de uma
função para expressar a eficiência de um algoritmo, ou quando calcular uma função é
difícil ou impossível e somente aproximações podem ser encontradas.
Para pequenos valores de n, o último termo, 1000, é o maior. Quando n se iguala a 10,
o segundo (100n) e o último (1000) termos estão em igual condição com os outros,
dando uma pequena contribuição ao valor da função. Quando n atinge o valor de 100,
o primeiro e o segundo termos dão a mesma contribuição ao resultado. Mas quando n
se torna maior que 100, a contribuição do segundo termo se torna menos significativa.
Por isso, para valores maiores de n, devido ao crescimento quadrático do primeiro
termo (n2), o valor da função f depende principalmente do valor de seu primeiro termo,
como demonstra a Tabela 1. Os outros valores podem ser desprezados.
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
3
_______________________________________________________________________________________________
2
Tabela 1 - Taxa de crescimento de todos os termos da função f(n) = n + 100n + log10n + 1000.
2
N f(n) n 100n log10n 1000
1 1.101 1 100 0 1000
10 2.101 100 1.000 1 1000
100 21.002 10.000 10.000 2 1000
1.000 1.101.003 1.000.000 100.000 3 1000
10.000 101.001.004 100.000.000 1.000.000 4 1000
100.000 10.010.001.005 10.000.000.000 10.000.000 5 1000
• Tempo de Execução
A complexidade mais estudada em algoritmos é a complexidade de tempo. O
modelo para determinação desse custo é baseado no custo de cada operação
realizada, em função do tamanho da entrada (n). Em geral, não são
consideradas todas as operações, levando-se em consideração apenas as de
maior custo. Por exemplo, algoritmos de ordenação têm a sua complexidade de
tempo determinada pelo número de comparações realizadas, em função de n;
• Espaço em Memória
Determina o espaço utilizado pelo algoritmo. Em geral, quanto menor a
complexidade de tempo de um algoritmo, maior, a sua complexidade de espaço.
A complexidade não é uma medida exata, pois na verdade ela representa uma “ordem
de grandeza”.
A notação mais usada para especificar a complexidade assintótica, isto é, para estimar
a taxa de crescimento da função, é a notação O-Grande, introduzida em 1894 por Paul
Bachmann.
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
4
_______________________________________________________________________________________________
Para entender o que vem a ser ordem de magnitude, suponha-se que num dado
programa exista a seguinte linha de comando: x++.
// exemplo 1
int main (){
int x;
x++;
}
// exemplo 2
int main (){
int x, n=10;
for (int i= 1; i<=n; i++)
x++;
}
// exemplo 3
int main (){
int x, n=10;
for (int i= 1; i<=n; i++)
for (int j= 1; j<=n; j++)
x++;
}
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
5
_______________________________________________________________________________________________
Comportamento
• Polinomial
O(n) é polinomial, pois à medida que n aumenta, o fator que estiver sendo
analisado (tempo ou espaço) aumenta linearmente.
• Exponencial
Qualquer comportamento não-polinomial é exponencial, pois à medida que n
aumenta, o fator que estiver sendo analisado aumenta exponencialmente.
Problemas tratáveis são aqueles que tem n como um fator polinomial em seus limites
superior e inferior. Exemplos: O(log n), O(n), O(n log n), O(log nk). Problemas tratáveis
são ditos computáveis pois podem ser resolvidos computacionalmente.
Problemas intratáveis são aqueles que tem n como um fator exponencial em seus
limites superior e inferior. Exemplos: O(2n), O(n!), O(nn). No ponto de vista prático
esses problemas são considerados insolúveis via computação, já que os recursos
necessários para resolver esse problema têm um custo altíssimo.
Comparação de Programas
Exemplo: Um programa leva 100n unidades de tempo para ser executado e outro leva
2n2. Qual dos dois programas é melhor?
Depende do tamanho do problema. Para n < 50, o programa com tempo 2n2 é melhor
do que o que possui tempo 100n. Para problemas com entrada de dados pequena é
preferível usar o programa cujo tempo de execução é O(n2). Entretanto, quando n
cresce, o programa com tempo de execução O(n2) leva muito mais tempo que o
programa O(n).
f(n) = O(1)
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
6
_______________________________________________________________________________________________
f(n) = O(log n)
• Um algoritmo de complexidade O(log n) é dito ter complexidade logarítmica.
• Típico em algoritmos que transformam um problema em outros menores.
• Pode-se considerar o tempo de execução como menor que uma constante
grande.
f(n) = O(n)
• Um algoritmo de complexidade O(n) é dito ter complexidade linear.
• Em geral, um pequeno trabalho é realizado sobre cada elemento de entrada.
• É a melhor situação possível para um algoritmo que tem de processar/produzir
n elementos de entrada/saída.
• Cada vez que n dobra de tamanho, o tempo de execução dobra.
f(n) = O(n2)
• Um algoritmo de complexidade O(n2) é dito ter complexidade quadrática.
• Ocorrem quando os itens de dados são processados aos pares, muitas vezes
em um anel dentro de outro.
• Quando n é mil, o número de operações é da ordem de 1 milhão.
• Sempre que n dobra, o tempo de execução é multiplicado por 4.
• Úteis para resolver problemas de tamanhos relativamente pequenos.
f(n) = O(n3)
• Um algoritmo de complexidade O(n3) é dito ter complexidade cúbica.
• Úteis apenas para resolver pequenos problemas.
• Quando n é 100, o número de operações é da ordem de 1 milhão.
• Sempre que n dobra, o tempo de execução fica multiplicado por 8.
f(n) = O(2n)
• Um algoritmo de complexidade O(2n) é dito ter complexidade exponencial.
• Geralmente não são úteis sob o ponto de vista prático.
• Ocorrem na solução de problemas quando se usa força bruta para resolvê-los.
• Quando n é 20, o tempo de execução é cerca de 1 milhão. Quando n dobra, o
tempo fica elevado ao quadrado.
f(n) = O(n!)
• Um algoritmo de complexidade O(n!) é dito ter complexidade exponencial,
apesar de O(n!) ter comportamento muito pior do que O(2n).
• Geralmente ocorrem quando se usa força bruta para na solução do problema.
• n = 20 -> 20! = 2432902008176640000, um número com 19 dígitos.
• n = 40 -> um número com 48 dígitos.
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
7
_______________________________________________________________________________________________
Exemplos de Complexidade
Alguns algoritmos mal projetados, ou algoritmos cuja complexidade não pode ser
melhorada, não têm aplicações práticas nos computadores disponíveis. Para
processar um milhão de itens com um algoritmo quadrático, mais de 11 dias são
necessários, e, para um algoritmo cúbico, milhares de anos. Mesmo se um
computador pudesse realizar uma operação por nanossegundos (um bilhão de
operações por segundo), o algoritmo quadrático terminaria em 16,7 segundos, mas o
algoritmo cúbico exigiria mais de 31 anos. Mesmo uma melhoria de 1.000 vezes na
velocidade de execução teria muito pouco sentido prático para esse algoritmo. A
Tabela 3 mostra o comportamento do tempo em função da dimensão da entrada e
ordem de complexidade. A velocidade impressionante dos computadores é de
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
8
_______________________________________________________________________________________________
Conclusão
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna
9
_______________________________________________________________________________________________
0, se n = 0
Fn = 1, se n = 1
Fn-1 + Fn-2, se n > 1
Algoritmo 1
int fibo1(int n) {
if (n == 0)
return 0;
else
if (n == 1)
return 1;
else
return fibo1(n - 1) + fibo1(n - 2);
}
Algoritmo 2
____________________________________________________________________________________
Algoritmos II – 2023
Profa. Andréa Carla Gonçalves Vianna