1 TEORIA DA COMPLEXIDADE APLICADA À ANÁLISE DE ALGORÍTIMOS
1.3 O CONCEITO DE COMPLEXIDADE E SUAS COTAS
Complexidade (em Tempo) de um Algoritmo:
É uma função que relaciona o tamanho da instância do problema ao tempo que o algoritmo gasta para resolvê-lo.
Tamanho da Instância de um Problema:
Em geral, é um ou mais números inteiros positivos, que expressam uma medida da quantidade de dados de entrada do problema.
Exemplos:
a) Para os problema de ordenação, o tamanho da instância é o número de objetos a serem ordenados;
b) Para problemas de grafos, o tamanho da instância pode ser o número de vértices, ou o número de vértices juntamente com o número de arestas, do grafo.
Cota Superior de Complexidade de um Problema:
É a menor das complexidades dos algoritmos conhecidos para resolver o problema.
Representação: CS(n), onde n é o tamanho da instância.
Cota Inferior de Complexidade (ou Complexidade Intrínseca) de um Problema:
É um limite inferior para as complexidades em tempo de todos os possíveis algoritmos (conhecidos ou não) que resolvem o problema.
Representação: CI(n), onde n é o tamanho da instância.
Por exemplo, pode-se demonstrar que qualquer algoritmo para resolver o problema da ordenação de n números inteiros terá necessariamente complexidade em tempo maior ou igual a n.lg(n) (no caso pessimista). Daí, podemos afirmar que a função CI(n) = n.lg(n) é uma cota inferior para esse problema.
O objetivo final é chegar a CS(n) = CI(n), o que significa que conhecemos um algoritmo
"ótimo" (ou seja, o melhor algoritmo conhecido tem a menor complexidade possível para o problema em questão).
1.4 MODELOS DE COMPUTAÇÃO
Aqui vamos definir como determinar o tempo de execução de um algoritmo, a partir do tamanho
da instância do problema.
COMPLEXIDADE APLICADA À ANÁLISE DE ALGORÍTMOS
______________________________________________________________________________________________________________________________________________________ ________________ _____________________________________________________________________________________________________________________________ ____________________
A execução de um algoritmo envolve a execução das diversas operações incluídas nas instruções ou comandos que compõem o algoritmo. Outro aspecto importante na definição do tempo de execução é o ambiente computacional (hardware e software) no qual sua execução se dará.
Então, o tempo de execução será o somatório dos tempos gastos na execução de cada uma das instruções.
Visando evitar cálculos muito complexos, e buscando independência da determinação do tempo em relação ao ambiente computacional, vamos introduzir várias suposições simplificadoras, ou seja um modelo (modelo = abstração) simplificado de computação. As simplificações envolvidas nesse modelo são:
Algumas instruções são preponderantes em relação às demais, no que diz respeito a
determinação do tempo de execução. Apenas elas serão consideradas para a obtenção desse tempo. Denominaremos essas operações de “elementares”.
Consideraremos que todas as operações elementares gastam apenas uma unidade de tempo para executar (critério do custo uniforme), o que faz com que a determinação do tempo de execução de uma operação elementar se reduza à determinação da freqüência de execução da mesma.
Resumindo: Para efeito do nosso estudo, o tempo de execução de um algoritmo, para uma dada instância do problema que ele resolve, será igual ao somatório da freqüência de execução das suas operações elementares.
1.5 CASO PESSIMISTA E CASO MÉDIO
O tamanho da instância não é a única variável que pode afetar a determinação do tempo de execução de um algoritmo (mesmo com o modelo de computação simplificado adotado). Outras características da instância também interferem nessa determinação. Por exemplo, no caso do problema de ordenação de uma lista de números inteiros, pode ser relevante para a performance do algorítmo, a situação inicial da lista, ou seja, se ela está totalmente desordenada ou
parcialmente ordenada.
Para eliminar mais essa variável, podemos adotar o caso pessimista ou o caso médio para a escolha da instância, para um dado tamanho n.
Caso Pessimista:
A instância considerada (dentre todas aquelas de tamanho n), é a que exige um “esforço máximo” do algoritmo.
Menor interesse prático, embora seja importante para sistemas críticos de tempo real (p.ex.: controle de tráfego metroviário).
Mais utilizado porque simplifica os cálculos necessários à determinação das complexidades.
Caso Médio:
Considera todas as instâncias (de tamanho n) associadas às respectivas probabilidades de
ocorrência, buscando com isso, caracterizar o "esforço médio" do algoritmo.
Maior interesse na prática, pois pelo menos em princípio, leva a uma medida mais “justa”
da complexidade de um algoritmo.
Menos utilizado devido à dificuldade dos cálculos envolvidos.
No nosso estudo, adotaremos, na maioria das vezes, o caso pessimista.
Exemplo 1a: Análise de um Algorítmo:
Considere o problema de ordenação de n números inteiros, e o algoritmo abaixo que se propõe a resolvê-lo.
Algorítmo A: Ordenar uma lista em ordem não-decrescente.
Entrada: n (n 2), lista (M1, M2, ..., Mn) de números inteiros.
Saída: Os números da lista, ordenados em ordem não-decrescente.
para j n-1 até 1 faça
para i 1 até j faça (* supor que M1, ..., Mj+1 não estão em ordem *) se Mi > Mi+1
então "troque os valores de Mi+1 e de Mi, entre sí"
fim_se fim_para fim_para
pare-com-saída (M1, M2, ..., Mn) Análise:
Operação elementar: A comparação que ocorre na linha 03 (Mi > Mi+1)
Caso pessimista: O maior número de comparações ocorrerá quando a lista de entrada estiver em ordem decrescente (esforço máximo)
Cálculo do tempo de execução deste algoritmo:
A operação elementar será executada, no caso pessimista, um número de vezes dado pela fórmula: (n-1) + (n-2) + ... + 1 = 1/2 (n2 - n)
Adotando a hipótese do custo uniforme (uma unidade de tempo) para o tempo de cada execução da operação elementar, chegamos à complexidade do algoritmo em questão: f(n) = 1/2 (n2 - n)
1.6 ORDENS DE GRANDEZA NA COMPLEXIDADE DE ALGORÍTMOS
Na determinação da complexidade de um algorítmo, ou seja, da função que relaciona o tamanho da instância do problema ao tempo que o algorítmo gasta para resolvê-lo, utilizaremos além das simplificações acima adotadas, também a noção (simplificadora) de ordem de grandeza.
Todos nós estamos familializados com a noção de ordem de grandeza, no nosso dia a dia. Por exemplo, andar a pé, de bicicleta, de carro, ou de avião representam ordens de grandeza crescentes com respeito à distância que uma pessoa pode percorrer por hora.
Na Matemática existe notações apropriadas para expressar a ordem de grandeza de uma função.
Definição: f(n) = O(g(n)) (lê-se: f de n é de ordem máxima g de n), se e somente se,
existem duas constantes positivas c e n
otal que |f(n)| c.|g(n)|, para todo n n
o.
COMPLEXIDADE APLICADA À ANÁLISE DE ALGORÍTMOS
______________________________________________________________________________________________________________________________________________________ ________________ _____________________________________________________________________________________________________________________________ ____________________
Figura 1a:
Definição: f(n) = (g(n)) (lê-se: f de n é de ordem mínima g de n), se e somente se,
existem duas constantes positivas c e n
otal que |f(n)| c.|g(n)|, para todo n > n
o. Figura 1b:
Definição: f(n) = (g(n)) (lê-se: f de n é da ordem exata g de n), se e somente se,
existem constantes positivas c1 , c2 e no tal que c1.|g(n)| |f(n)| c2.|g(n)|, para todo n no.
Figura 1c:
Vimos que o algorítmo de ordenação apresentado acima tem complexidade f(n) = 1/2 (n2 - n).
Como f(n) = 1/2 (n2 - n)
1/2 n2 , para n > 1
Então f(n) = O(n2) , ou seja, a complexidade do algoritmo é de ordem máxima n2.
Exercícios:
1) Mostre que se f(n) = (g(n)), então f(n) = O(g(n)) e f(n) = (g(n)).
2) Seja f(n) = a
m. n
m+ … + a
1.n + a
0um polinômio de gráu m. Mostre que f(n) = O(n
m).
Resolução:
Assim, quando dizemos que a complexidade de um algorítmo é igual a O(n
2) (ou seja, é de ordem máxima n
2), isto significa que se executarmos o algorítmo repetidas vezes num mesmo computador, com o mesmo tipo de dados de entrada, mas para valores crescentes de n, os tempos de execução resultantes serão sempre inferiores c.n
2, sendo c uma constante.
Nesta disciplina, o nosso objetivo será obter uma ordem máxima g(n) para a (função f(n) de) complexidade de um algorítmo, para uma instância de tamanho n, no caso pessimista.
1.7 ORDENS DE GRANDEZA TÍPICAS
As seguintes ordens máximas são comumente utilizadas para expressar a ordem de grandeza da complexidade de algorítmos:
O(1) < O(lg n) < O(n) < O(n . lg n) < O(n
2) < O(n
3) < O(2
n)
O(1) significa que o número de execuções das operações elementares é fixo, e portanto, o tempo total é limitado por uma constante. As seis primeiras ordens máximas acima têm uma importante propriedade em comum: são limitadas por um polinômio. O(n), O(n
2) e O(n
3) já são polinômios de gráu linear, quadrático e cúbico, respectivamente. Entretanto, não existe um número inteiro m tal que n
mlimite 2
n, ou seja, 2
n= O(n
n), para qualquer inteiro m.
Diz-se que m algorítmo cuja complexidade tem 2
npor ordem mínima (ou seja, f(n) = (2
n) )
requer tempo exponencial. Se alguém descobre um novo algorítmo capaz de resolver o mesmo
problema requerendo apenas tempo polinomial, isso é uma grande conquista, pois a medida que
n cresce, surge uma tremenda diferença entre os algorítmos que requerem tempo exponencial e
os que requerem tempo polinomial, conforme fica claro nas tabelas 1a e 1b, abaixo.
COMPLEXIDADE APLICADA À ANÁLISE DE ALGORÍTMOS
______________________________________________________________________________________________________________________________________________________ ________________ _____________________________________________________________________________________________________________________________ ____________________
Função de Complexidade
Valor de n
20 40 60
n 0,0002 s 0,0004 s 0,0006 s n . lg n 0,0009 s 0,021 s 0,0035 s n
20,004 s 0,016 s 0,036 s
n
30,08 s 0,64 s 2,16 s
2
n10 s 127 s 3.660 s
3
n580 s 38.550 s 1,310
14s
Função de Complexidade
Tamanho da maior instância solucionável em uma hora Computador atual Computador 100 x
mais rápido
Computador 1.000 x mais rápido
n
N100 N 1.000 N
n . lg n N
122,5 N
1140,2 N
1n
2 N210 N
231,6 N
2n
3N
34,6 N
310 N
32
nN
4N
4+ 6 N
4+ 10
3
nN
5N
5+ 4 N
5+6
1.8 EFEITO DA CONSTANTE MULTIPLICATIVA C
É importante notar que um algorítmo de complexidade rapidamente crescente pode ter uma constante multiplicativa menor do que um outro de complexidade lentamente crescente. Neste caso, o primeiro algorítmo pode ter um desempenho superior para instâncias pequenas; é até possível que ele resolva rapidamente todas as instâncias que ocorram na prática.
A figura 1d explicita as constantes multiplicativas das funções de complexidade e apresenta os intervalos dos valores de n, para os quais os algorítmos correspondentes são os mais rápidos.
Note que o algorítmo de complexidade 20000 n é o melhor apneas para n > 1024.
20.000 n 2.000 n lg n 200 n2 10 n3 10 (2 n) 3 n
n > 1.024 59 n 1.024 21 n 58 10 n 20 6 n 9 1 n 9