Mathematics">
Unidade II
Unidade II
Unidade II
e Complexidade
de Algoritmos
Complexidade de Algoritmos
Revisão Textual:
Prof.ª Esp. Kelciane da Rocha Campos
Complexidade de Algoritmos
OBJETIVO DE APRENDIZADO
• Apresentar ao aluno a forma de classificação de algoritmos de acordo com o respectivo
tempo de execução, considerando o tamanho da instância, bem como a utilização da no-
tação assintótica a partir da definição de limites inferiores e superiores e as respectivas
técnicas de análise.
UNIDADE Complexidade de Algoritmos
8
que cada passo é executado. Desse modo, cada passo individual consiste de uma instru-
ção que será executada pelo computador. Considere, por exemplo, uma instrução que
estabelece que um determinado valor seja atribuído a uma variável específica. Veja que
o tempo necessário para executar essa operação de atribuição é constante e dependente
do hardware. Todas as vezes que esse tipo de operação for executado em um mesmo
computador, ela gastará o mesmo tempo, por isso tempo constante. Por outro lado, se
essa operação for executada em computadores com características diferentes, o tempo
gasto em cada operação, também, será diferente, por isso a dependência do hardware.
O tamanho da instância de um problema impacta diretamente na eficiência dos algo-
ritmos. Como descrito anteriormente, a eficiência é medida em função do custo (tempo
de processamento) de uma instrução (passo do algoritmo) e da quantidade de vezes que
a instrução é executada. Nesse sentido, o que determina quantas vezes a instrução é
executada é o tamanho da instância. Assim, instâncias maiores levarão o algoritmo a
executar as instruções mais vezes; consequentemente, o tempo total gasto pelo algoritmo
também será maior.
Um exemplo clássico, sempre utilizado em cursos de computação, é o conjunto de
algoritmos de ordenação. O problema de ordenação é definido formalmente por Cormen
(2009) da seguinte forma:
• Entrada: uma sequência de n valores [x1, x2,…, xn];
Saída: uma permutação éëêx 1 , x 2 ,..., x n ùûú da sequência original de entrada, de modo
' ' '
•
å (tj - 1)
n -1
5.V éêëi + 1ùúû ¬ V éêëi ùúû c5 j =1
å (tj - 1)
n -1
6. i ¬ i - 1 c6 j =1
9
9
UNIDADE Complexidade de Algoritmos
Para cada linha de instrução, temos o respectivo tempo de execução (ci) ( e o número
de vezes que é executada (CORMEN, 2009).
De forma similar, o número de vezes que o laço da linha 4 será executado é dado
pela somatória do número de execuções para cada tj. Assim, t1 é o número de vezes
que a instrução da linha 4 foi executada para j = 1, t2 se refere às execuções para j = 2
e assim sucessivamente. Veja que as instruções das linhas 5 e 6 irão ser executas, em
cada iteração, uma vez menos que a quantidade de vezes do laço (linha 4). A lógica aqui
é a mesma descrita anteriormente para o laço da linha 1.
O tempo de execução do algoritmo Insertion Sort (T(n)) será a somatória dos pro-
dutos entre os custos das instruções e a quantidade de vezes que a respectiva instrução
será executada. Assim, temos o seguinte:
n -1 n -1 n -1
T (n ) = c1n + c2 (n - 1) + c3 (n - 1) + c4 å t j + c5 å (t j - 1) + c6 å (t j - 1) + c7 (n - 1)
j =1 j =1 j =1
10
A classificação de algoritmos, em função de sua respectiva eficiência, deve levar em
conta qual entrada está sendo considerada e não somente seu tamanho. Por exemplo, um
algoritmo de ordenação pode ser mais ou menos eficiente de acordo com a organização
prévia dos elementos a serem ordenados. Se considerarmos duas instâncias de mesmo
tamanho, sendo uma já ordenada e outra na qual os elementos estão em ordem inversa,
podemos ter tempos diferentes de execução do algoritmo sobre essas duas entradas.
Veja que, para que seja possível descrever T(n) de uma maneira mais efetiva, devemos
levar em consideração a organização prévia da instância de entrada. Essa organização
terá um impacto importante na definição da complexidade do algoritmo em função do
tamanho da entrada.
11
11
UNIDADE Complexidade de Algoritmos
Analisamos o tempo de execução do algoritmo Insertion Sort quando ele recebe uma
instância de entrada previamente ordenada, ou seja, o melhor caso. Vimos que nesse
caso não há nenhuma troca a ser realizada, visto que todos os elementos do arranjo
estão em seus devidos lugares. Por outro lado, podemos ter uma instância de entrada
onde seus elementos estejam posicionados na ordem inversa daquela que desejamos, ou
seja, os elementos estão organizados em ordem decrescente. Para essa configuração es-
pecífica do arranjo, damos o nome de pior caso. Assim, no pior caso todos os elementos
do arranjo deverão ser reposicionados.
Vimos que no melhor caso o controle do laço que contém as instruções de movimenta-
ção dos elementos é executado uma única vez; assim, para todo j temos que t j = 1. No
pior caso, temos a situação oposta, ou seja, teremos de executar o reposicionamento de
todos os elementos; assim, para todo j temos que t j = j , para j = 1, 2, …, n − 1. Assim,
cada elemento V [ j ] será comparado com todos os elementos do subarranjo ordenado
à direita V [ 0, …, j − 1].
O próximo passo para reduzir a função T ( n ) a uma função que caracterize o tempo
de execução do algoritmo é resolver as somatórias. Veja que, conforme descrito ante-
riormente, no pior caso t j = j , assim podemos utilizar essa igualdade substituindo na
n −1
£ jnj=111t+j2,+…+
primeira somatória ∑
j =1
a qual
n − 1. fica da seguinte forma:
n −1
∑j =
j =1
1 + 2 +…+ n − 1.
Veja que essa somatória consiste da soma dos valores de j , os quais variam entre
1 e n − 1 . Podemos resolver essa somatória utilizando uma técnica que consiste em so-
mar a sequência de soma com a mesma sequência invertida, da seguinte maneira:
12
1 + 2 +…+ n − 1
n − 1 + n − 2 +…+ 1.
∑ ( j − 1)=
j =1
0 + 1 +…+ n − 2.
( n − 2 )( n − 1) ,
2
n 2 − n − 2n + 2
,
2
n 2 − 3n + 2
,
2
n ( n − 3)
+ 1.
2
Desse modo, podemos observar que a somatória original pode ser reescrita como segue:
n −1
n ( n − 3)
∑( =
j − 1)
j =1 2
+ 1.
Após a resolução das somatórias, teremos alguns passos a serem seguidos para a
simplificação da função de tempo de execução T ( n ) .
13
13
UNIDADE Complexidade de Algoritmos
n ( n − 1) n ( n − 3) n ( n − 3)
T ( n ) = c1n + c2 ( n − 1) + c3 ( n − 1) + c4 + c5 + 1 + c6 + 1 + c7 ( n − 1) ,
2 2 2
c4 n ( n − 1) c5 n ( n − 3) c6 n ( n − 3)
T ( n ) = c1n + c2 n − c2 + c3 n − c3 + + + c5 + + c6 + c7 n − c7,
2 2 2
c4 n 2 c4 c5 n 2 3c5 n c n 2 3c n
T ( n ) = c1n + c2 n − c2 + c3 n − c3 + − + − + c5 + 6 − 6 + c6 + c7 n − c7,
2 2 2 2 2 2
c4 n 2 c5 n 2 c6 n 2 c n 3c n 3c n
T (n) = + + + c1n + c2 n + c3 n − 4 − 5 − 6 + c7 n − c2 − c3 + c5 + c6 − c7,
2 2 2 2 2 2
• Quinto passo: colocar a variável n em evidência, de acordo com o valor dos ex-
poentes, separando, assim, os coeficientes das variáveis:
c c c c 3c 3c
T ( n )= 4 + 5 + 6 n 2 + c1 + c2 + c3 − 4 − 5 − 6 + c7 n − ( c2 + c3 − c5 − c6 + c7 ) ,
2 2 2 2 2 2
• Sexto passo: substituir os grupos de valores que multiplicam a variável n por novas
constantes ( a , b e c ) que representem os coeficientes:
c c c
a = 4 + 5 + 6 ,
2 2 2
c 3c 3c
b = c1 + c2 + c3 − 4 − 5 − 6 + c7 ,
2 2 2
c= ( c2 + c3 − c5 − c6 + c7 ) ,
• Último passo: obter a função T ( n ) que representa a complexidade computacional
do algoritmo Insertion Sort:
T ( n ) = an 2 + bn − c.
14
A obtenção de uma função que represente o tempo de execução no pior caso nos
indica o estabelecimento de um limite superior. Essa informação é importante, pois ela
garante que o algoritmo nunca demorará mais do que o tempo estabelecido pela função.
Figura 1
Diagrama com a representação do crescimento das curvas que descrevem
as funções quadrática (an2 + bn + c) e linear (an + b), as quais são, respecti-
vamente, os limites superior e inferior do tempo de execução do algoritmo
Insertion Sort.
As instâncias que não correspondem ao melhor ou pior caso são consideradas como
caso médio e, em teoria, a curva traçada pela função será intermediária entre as duas
anteriores. Porém, comumente o caso médio tem quase o mesmo comportamento do
pior caso. Outra questão sobre o caso médio é a dificuldade em identificar uma instância
que corresponda a esse caso. Por vezes, o caso médio é identificado por meio de análise
estatística, considerando-se um conjunto aleatório de instâncias do problema.
15
15
UNIDADE Complexidade de Algoritmos
Notação Assintótica
O estudo do tempo de execução de algoritmos, que realizamos até o presente mo-
mento, nos possibilita entender que o tempo gasto por um algoritmo pode crescer de
forma mais ou menos rápida, de acordo com o tamanho da instância de entrada e, tam-
bém, da configuração inicial dessa instância. Algo que é importante de ser acrescentado
é que esse tipo de análise faz sentido para instâncias grandes, não sendo efetivo para a
caracterização quando tratamos pequenas entradas.
Considere o exemplo anterior sobre o algoritmo Insertion Sort, para o pior caso; vi-
mos que a função que representa o crescimento do tempo de execução sobre o tamanho
da entrada n é uma função quadrática cuja forma geral é T ( n ) = an 2 + bn + c . Para ins-
tâncias de entrada grandes, o crescimento da função no limite é dominado pelo primeiro
termo da função. Assim, podemos dizer que a função que descreve o crescimento do
referido algoritmo é T ( n ) = n 2 . Note que desconsideramos as constantes multiplicado-
1 0
ras: a , b e c , bem como os termos de menor grau: n e n . Esses elementos, des-
considerados na representação da complexidade computacional, não são determinantes
para o aumento do tempo de execução de um algoritmo quando o tamanho da entrada
é suficientemente grande.
16
da instância de entrada. Assim, a notação assintótica fornece um conjunto de “catego-
rias”, a partir das quais é possível caracterizar os algoritmos, de acordo com as respecti-
vas eficiências computacionais, seja qual for a entrada considerada.
A notação Θ (Theta) será a primeira que iremos estudar. Quando nos referimos à
complexidade de tempo de um algoritmo, dizendo que seu tempo de execução é da ordem
de T ( n ) = Θ ( g ( n ) ) , na verdade estamos afirmando que a função g ( n ) é um limite as-
sintoticamente restrito para f ( n ) . Formalmente, Cormen (2009) define Θ ( g ( n ) ) como
um conjunto de funções obtido da seguinte forma:
( )
A notação anterior define que o conjunto Θ g ( n ) é composto por funções ( f ( x ) )
para as quais existem as constantes positivas c1 , c2 e n0 , de modo que a taxa de cresci-
mento das funções sejam maiores ou iguais à taxa de crescimento da função resultante do
produto da constante c1 pela função g ( n ) e menores ou iguais à taxa de crescimento da
função resultante do produto da constante c2 pela função g ( n ) , para quaisquer valores
de n maiores ou iguais à constante n0 .
Considerando a Figura 2a, veja que a taxa de crescimento da função f ( n ) é menor
que a taxa de c2 g ( n ) e maior que a taxa de c1 g ( n ) para quaisquer valores de n que
sejam maiores que n0 . Existem valores à esquerda de n0 que são menores que c1 g ( n ) ;
assim, quando se diz que o tamanho da instância de entrada deve ser grande o suficiente,
isso significa que as restrições descritas só são válidas a partir de n0 .
Quando afirmamos que uma determinada função, que representa o crescimento do
tempo de execução de um algoritmo, é Θ de alguma outra função, estamos estabele-
cendo limites superior e inferior para a execução do algoritmo. Desse modo, sabemos
quais são os tempos mínimo e máximo de execução para aquele algoritmo.
1
Considere um exemplo prático1, suponha que queremos mostrar que 2 n − 3n = Θ ( n );
2 2
1
( n ) n2 − 3n e g ( n ) = n 2 . Ainda de acordo
para alinharmos com o estabelecido, temos que f =
2
com o estabelecido anteriormente, temos que encontrar as constantes positivas c1 , c2 e
n0 que satisfaçam a inequação:
1 2
c1n 2 ≤ n − 3n ≤ c2 n 2
2
1 2
2 n − 3n
c1n 2 c2 n 2
≤ ≤ 2
n2 n2 n
1 3
c1 ≤ − ≤ c2
2 n
1
Exemplo elaborado pelo professor Moacir Ponti Jr. (ICMC – USP).
17
17
UNIDADE Complexidade de Algoritmos
1
c2 ≥
Deste modo, a desigualdade do lado direito é válida para n ≥ 1 selecionando 2.
Para consideramos a desigualdade do lado esquerdo válida, fazemos n ≥ 7 com c ≥ .1
1
Assim, podemos afirmar que para c2 = 1 , n0 = 7 e c1 = 1 , temos que: 14
2 14
1 2
2
Θ n2 .
n − 3n = ( )
Conforme descrito anteriormente, a notação assintótica Θ define limites superior e
inferior para uma função que define o tempo de execução de um algoritmo. Nesse mes-
mo contexto, a notação Ο (pode ser lida como “big oh”) é utilizada para expressar um
limite assintótico superior para uma função.
( )
Formalmente, a notação assintótica Ο g ( n ) é um conjunto de funções f ( n ) defi-
nido da seguinte forma:
Ο ( g (=
n )) { f ( n ) |∃ c , n
0 > 0 sendo 0 ≤ f ( n ) ≤ cg ( n ) ∀n ≥ n0 } .
Considere a Figura 2b, veja que para todos os valores de n à direita de n0 a taxa
de crescimento da função f ( n ) é, no máximo, igual à taxa de crescimento da função
g ( n ) . Assim, podemos afirmar que f ( n ) = Ο g ( n ) . ( )
A forma como foi escrita a afirmação anterior, apesar de comum, é um abuso da no-
( )
tação de igualdade, visto que f ( n ) não é igual a Ο g ( n ) , formalmente correto seria
grafar como f ( n ) ∈ Ο ( g ( n ) ) .
2n + 10 ≤ cn
cn − 2n ≥ 10
( c − 2 ) n ≥ 10
10
n≥ .
c−2
2
Exemplo elaborado pelo professor Moacir Ponti Jr. (ICMC – USP).
18
A notação Ο é a mais comumente utilizada, pois, na maioria dos casos, estamos
buscando a complexidade de tempo no pior caso, ou seja, queremos saber qual o limite
superior para o tempo de execução do algoritmo. Porém, pode haver situações nas quais
seja necessário saber qual é o limite inferior, nesses casos queremos saber o comporta-
mento mínimo de uma dada função.
Para referenciar uma função que tenha um determinado limite assintótico inferior,
utilizamos a notação Ω (Ômega). O conjunto de funções Ω g ( n ) é definido da se-( )
guinte forma:
Ω ( g (=
n )) { f ( n ) |∃ c , n
0 > 0 sendo 0 ≤ cg ( n ) ≤ f ( n ) ∀n ≥ n0 } .
Analisando a Figura 2c, é possível observar que a função f ( n ) tem uma taxa de
crescimento maior ou igual à taxa de g ( n ) , para todo o intervalo à direita de n0 .
Vamos considerar um exemplo3 para a notação Ω , queremos verificar se a função
f (=
x ) 3n 2 + n é, no mínimo, ômega de g ( n ) = n , para n ≥ n0 .
3n 2 + n ∈ Ω ( n )
3n 2 + n ≥ cn
3n 2 + n cn
≥
n n
3n + 1 ≥ c
c −1
n≥ ,
3
ο ( g (=
n )) { f ( n ) |∀ c > 0 ∃ n 0 > 0 sendo 0 ≤ f ( n ) < cg ( n ) ∀n ≥ n0 } .
3
Exemplo elaborado pelo professor Moacir Ponti Jr. (ICMC – USP).
19
19
UNIDADE Complexidade de Algoritmos
Importante!
Veja que a diferença entre Ο(g(n)) e ο(g(n)) está nas seguintes desigualdades: f(n)≤cg(n)
para Ο e f(n)<cg(n) para ο.
ω ( g (=
n )) { f ( n ) |∀c > 0 ∃ n 0 > 0 sendo 0 ≤ cg ( n ) < f ( n ) ∀n ≥ n0 } .
Podemos considerar uma analogia para que o entendimento sobre as notações as-
sintóticas apresentadas seja mais consistente. A analogia consiste entre a comparação
assintótica de duas funções f e g e dois números reais a e b . Dessa forma, podemos
considerar o seguinte:
f (n) =Ο ( g ( n ) ) ⇒ a ≤ b,
f (n) =Ω ( g ( n ) ) ⇒ a ≥ b,
f (n) =Θ ( g ( n )) ⇒ a =b,
f ( n ) ο ( g ( n ) ) ⇒ a < b,
=
f ( n ) ω ( g ( n ) ) ⇒ a > b.
=
4
A estratégia de Divisão e Conquista será abordada nas em outras unidades.
20
• Função quadrática (f(n) = n2): algoritmos cujo tempo de execução é representa-
do por uma função quadrática normalmente apresentam dois laços de repetição
aninhados. Nesses casos, sempre que o tamanho da entrada dobra, o tempo de
execução é multiplicado por quatro;
• Função cúbica (f(n) = n3): também conhecida como função do terceiro grau, de-
fine o tempo de execução de algoritmos que, comumente, apresentam três laços
aninhados, como, por exemplo, na multiplicação de matrizes. Nesse caso, sempre
que o tamanho da entrada dobra o tempo de execução é multiplicado por oito;
• Função exponencial (f(n) = an): algoritmos com o tempo de execução repre-
sentado por esse tipo de função são pouco úteis, pois sua complexidade cresce
muito rapidamente;
• Função fatorial (f(n) = n!): tem um comportamento pior que as funções exponen-
ciais; portanto, algoritmos com essa complexidade são, igualmente, pouco úteis do
ponto de vista prático.
21
21
UNIDADE Complexidade de Algoritmos
Material Complementar
Indicações para saber mais sobre os assuntos abordados nesta Unidade:
Vídeos
Dica rápida de como saber a complexidade de um algoritmo
https://youtu.be/f--A1FK6KK4
Fundamentos para notação assintótica e contagem de instruções | Análise de Algoritmos
https://youtu.be/uxtO9pBTnzw
O Grande – Big O & Classes de Algoritmos | Análise de Algoritmos
https://youtu.be/Qdf3SN2ucVw
Leitura
Notação assintótica
https://bit.ly/2JOoX2o
22
Referências
CORMEN, T. H. et al. Introduction to algorithms. MIT press, 2009.
23
23