• Nenhum resultado encontrado

Introdução à complexidade de algoritmos

No documento UM PEQUENO ESTUDO SOBRE OTIMIZAÇÃO LINEAR (páginas 33-37)

A complexidade de um algoritmo representa o esforço computacional para executá-lo. Esse esforço pode ser medido levando em consideração fatores como tempo e espaço, que se referem, respectivamente, à velocidade e a quantidade de memória requerida para a execução do algoritmo.

Para determinar a complexidade de um algoritmo, poderíamos analisar o esforço requerido para sua execução em um computador específico. Entretanto, características da máquina e/ou da implementação podem influenciar na análise do algoritmo. Assim, uma alternativa é fazer uma análise do algoritmo independente de implementação.

Em geral, a quantidade de trabalho de um algoritmo depende da entrada. Por exemplo, em um algoritmo para multiplicação de matrizes, a quantidade de operações a serem realizadas depende da ordem das matrizes; para a busca de um elemento em uma lista, a quantidade de trabalho depende do tamanho da lista e da forma em que os elementos estão dispostos nela. Sendo assim, vamos nos referir à quantidade de trabalho requerida para executar um algoritmo para uma dada entrada como o desempenho do algoritmo (para essa entrada).

Quando usamos o tempo de execução de um algoritmo como medida de desempenho do mesmo, estamos falando em complexidade de tempo, já quando usamos a quantidade de memória requerida como medida de desempenho do algoritmo trata-se de complexidade de

espaço. Nesse texto, daremos mais atenção à complexidade de tempo.

A quantidade de trabalho requerida para execução de um algoritmo pode ser medida em função da quantidade de vezes que uma determinada operação é executada. Chamaremos essa operação de operação fundamental. Por exemplo, para um algoritmo de ordenação de um vetor, poderemos usar como operação fundamental a comparação entre os elementos quanto à ordem. Em alguns casos, pode-se escolher mais de uma operação fundamental e adotar pesos diferentes para elas.

Vejamos um exemplo para ilustrar essas idéias.

Exemplo 4.1 Segue-se um procedimento para determinar o maior dentre os elementos de um

vetor.

Procedimento 4.1 Máximo de um vetor

Entrada: vet: vetor[1...n].

Saída: max = valor máximo em vet. max vet[1];

para i=2:n se max < vet[i]

então max vet[i]; fim se

fim para retorna(max); fim Procedimento

O Procedimento 4.1 inicia adotando o primeiro elemento do vetor como o máximo, em seguinda itera sobre os outros elementos do vetor, atualizando a variável “max” sempre que encontra algum valor maior do que a estimativa atual. Para esse procedimento, adotaremos como operação fundamental a comparação “<” (em “se max < vet[i]”). Essa comparação é efetuada n-1 vezes, ou seja, o desempenho do Procedimento 4.1 para qualquer entrada é n-1.

A medida de desempenho de um algoritmo é muito parcial, uma vez que é medida em função de uma entrada específica. Há casos, em que até mesmo entradas de um mesmo tamanho, resultam em desempenhos diferentes, como por exemplo, em um algoritmo para busca de um elemento em um vetor, onde o elemento pode ser encontrado em qualquer posição, e o número de comparações efetuadas será o número da posição em que o elemento buscado se encontra.

A fim de fornecer uma avaliação mais genérica acerca de um dado algoritmo, desejamos associar a quantidade de trabalho requerida por um algoritmo à cada tamanho de entrada, e não à uma entrada específica.

Como já dissemos, um algoritmo pode apresentar desempenhos distintos para entradas do mesmo tamanho. Nesse ponto surge uma questão: qual medida de desempenho escolher para avaliação do algoritmo? Para responder a essa pergunta, poderemos adotar alguns critérios, como por exemplo, tomar o valor máximo ou médio dentre os desempenhos.

4.1.1 Complexidade média

A complexidade média ou esperada de um algoritmo é a média dos desempenhos do algoritmo sobre cada entrada de tamanho n usando como peso a probabilidade de ocorrência de cada entrada.

Sejam D o conjunto dos dados de entrada para um algoritmo a e D o conjunto de n

entradas de tamanho n, tal que Dn =

{

dD tam d; ( )=n

}

. A complexidade média de um

algoritmo a (que denotaremos por cM[a]), será

[ ]( ) [

( ). [ ]( )

]

n M d D c a n prob d desemp a d =

,

onde prob(d) é a probabilidade de ocorrência da entrada d no conjunto D e desemp[a](d) é o n

desempenho do algoritmo a quando executado sobre a entrada d.

Consideremos um algoritmo a com 100 entradas de tamanho 10, onde cada uma das entradas tem a mesma probabilidade de ocorrência e cada entrada di apresenta o desempenho ri, i = 1, ..., 100. Nesse caso teremos:

[ ]( ) (

1 100

)

1 10 . ... 100 M c a = r + +r . 4.1.2 Complexidade pessimista

A complexidade pessimista (pior caso) de um algoritmo a corresponde ao valor máximo entre os seus desempenhos sobre as entradas de tamanho n, a saber

[ ]( )

max

{

[ ]( ) ;

}

P n

c a n = desemp a dR d+D .

Para ilustrar esse conceito, segue-se um exemplo de um algoritmo de busca sequencial de um elemento em um vetor.

Exemplo 4.2 Busca Sequencial

O problema consiste em procurar por um valor (chave) ch em um vetor vet com n elementos para determinar se ch encontra-se em vet ou não. Para isso, usaremos uma variável booleana achou que será verdadeira caso ch exista em vet, ou falsa caso contrário. Segue-se o procedimento.

Procedimento 4.2 Busca Sequencial

Entrada: ch: Valor, vet: Vetor[1 ... n] de Valor.

Saída: achou e pos (achou=V caso ch exista em vet, achou=F caso ch não exista em vet e vet[pos]=ch).

i 0; achou F; repita

i i + 1;

se vet[i] = ch então achou V fim se até que achou ou i = n;

se achou então pos i fim se retorna(achou, pos);

fim Procedimento.

Adotaremos como operação fundamental a comparação de igualdade entre a chave e o elemento corrente do vetor (se vet[i] = ch). Essa comparação é executada até encontrar um elemento com valor igual ao de ch ou os elementos de vet se esgotarem sem encontrar a chave. Portanto é fácil verificar que:

se ch encontra-se na primeira posição de vet, será realizada uma comparação; se ch encontra-se na segunda posição de vet, serão realizadas duas comparações;

... se ch encontra-se na k-ésima posição de vet, serão realizadas k comparações; ... se ch encontra-se na n-ésima posição de vet (ou não existe em vet), serão realizadas n comparações.

Portanto, no pior caso, a operação fundamental será executada n vezes, ou seja, a complexidade pessimista para o algoritmo Busca Sequencial é

[ ]( ) .

P

c Busca Sequencial n =n

Considerando que a probabilidade de ter um valor igual ao da chave seja a mesma para todos os elementos do vetor, a complexidade média será:

( )

1 1 [ ]( ) . 1 2 ... . 2 M n c Busca Sequencial n n n + = + + + =

Estudando complexidade, uma questão que surge é como escolher o melhor algoritmo para um determinado problema. Um dos critérios mais utilizados é o comportamento assintótico do algoritmo, ou seja, o comportamento do algoritmo sobre entradas de tamanho grande.

No documento UM PEQUENO ESTUDO SOBRE OTIMIZAÇÃO LINEAR (páginas 33-37)

Documentos relacionados