Você está na página 1de 37

Noes de complexidade de algoritmos

358

Tempo de Execuo
A avaliao de desempenho de um algoritmo quanto executado por um computador pode ser feita a posteriori ou a priori. Uma avaliao a posteriori envolve a execuo propriamente dita do algoritmo, medindo-se o tempo de execuo. S pode ser exata se se conhecer detalhes da arquitetura da mquina, da linguagem de programao usada, do cdigo gerado pelo compilador, etc. De fato, o tempo deve ser medido fisicamente para um certo algoritmo, compilador e computador. Obtm-se assim medidas at certo pondo empricas, ainda que guiadas por conjuntos de testes preparados para tal, chamados benchmarks.
359

Tempo de Execuo
J uma avaliao a priori de um algoritmo, feita sem sua execuo, de forma analtica, possvel se considerarmos dois itens: a entrada (os dados fornecidos) e o nmero de instrues executadas pelo algoritmo. Em geral, o aspecto importante da entrada seu tamanho, que pode ser dado como nmero de valores num vetor, o nmero de registros num arquivo, enfim, um certo nmero de elementos que constituem a entrada de dados para o algoritmo. De modo que o tempo de execuo de um algoritmo pode ser dado como uma funo T(n) do tamanho n da sua entrada.
360

Tempo de Execuo
Por exemplo, um programa pode ter tempo de execuo T(n) = n2 + n + 1. A unidade de T(n) em principio instruo executada. Uma instruo neste contexto uma seqncia de operaes cujo tempo de execuo pode ser considerado constante (de certa forma, cada passo do algoritmo). Por exemplo, o algoritmo a abaixo: void somavet (int v[n], int *k) { int i; *k=0; for (i=0; i<n; i++) *k = (*k) + v[i]; }
361

Tempo de Execuo
Sendo v a entrada, de tamanho n, pode-se ver facilmente que a soma k+v[i] ser efetuada n vezes. Conseqentemente, T(n) = n+1, incluindo o passo de inicializao. O tempo de execuo (em instrues) variar conforme variar n, numa proporo linear. No entanto, como j se frisou, o tempo de execuo vai depender de outros fatores, ligados mquina, linguagens usadas, etc., e mesmo, por vezes, funo de aspectos adicionais de uma particular entrada e no apenas do seu tamanho. Como no exemplo a seguir.
362

Tempo de Execuo
int localiza (int v[n], int x) { int i; for (i=0; i<n; i++) if (x==v[i]) return i; return -1; }

O teste pode ser executado apenas uma vez, se o valor procurado estiver no primeiro elemento do vetor. E executado no mximo n vezes. Pode-se cogitar, ento, de tempos mnimos, mdios e mximos. 363

Tempo de Execuo
Acontece que, em geral, tempos mnimos so de pouca utilidade e tempos mdios so difceis de calcular (dependem de se conhecer a probabilidade de ocorrncia das diferentes entradas para o algoritmo). Considera-se assim T(n) como uma medida assinttica mxima, ou seja, uma medida do pior caso de desempenho, que ocorre com a entrada mais desfavorvel possvel. No caso anterior, T(n) = n + 1, incluindo o passo de retorno, que sempre acontece uma vez.
364

Complexidade
A avaliao analtica de uma algoritmo pode ser feita com vistas a se obter uma estimativa do esforo de computao, no em termos de unidade de tempo propriamente, mais em termos de uma taxa de crescimento do tempo de execuo em funo do tamanho do problema, i.e., do tamanho da entrada. Um exemplo tpico desse relacionamento entre tamanho da entrada e tempo de processamento o caso dos algoritmos de ordenao: nestes, os dados so vistos sempre como seqncias de valores com um certo comprimento, e natural concluir que quanto maior esse seqncia, mais tempo consumir a ordenao.
365

Complexidade
Mas quanto mais tempo? Pode se considerar o comportamento de dois algoritmos, A1 e A2, que realizam a mesma tarefa em tempos TA1 e TA2, para uma entrada de tamanho n. observemos os tempos de execuo para diferentes tamanhos da entrada:

366

Complexidade
Concluso: ao se multiplicar o tamanho da entrada por k, o tempo de A1 cresceu segundo k e o de A2 segundo k. Ou seja, a taxa de crescimento do tempo de execuo de A1 proporcional a n, e a de A2, proporcional a n. Essa taxa de crescimento proporcional chamada complexidade do algoritmo. Ela permite uma classificao dos algoritmos segundo sua categoria de complexidade e permite tambm comparar qualitativamente algoritmos diferentes que realizam a mesma tarefa. Pode ser considerada em termos de tempo de execuo (complexidade de tempo) ou termos de espao de memria utilizado (complexidade de espao).
367

Complexidade
Como j se discutiu para o tempo de execuo, pode-se ter complexidade de melhor, mdio e pior caso. Em nosso estudo, a necessidade de espao em memria ser considerada constante e o termo complexidade designar complexidade de tempo de pior caso. A expresso da complexidade de um algoritmo busca refletir suas condies intrnsecas, abstraindo aspectos ligados aos ambientes especficos de execuo. Assim, no so consideradas constantes de soma ou multiplicao. Uma expresso como kn + c deve ser simplificada para n.
368

Complexidade
Outra condio que se assume de que se est considerando o comportamento assinttico do algoritmo, ou seja: a complexidade expressa uma tendncia a um limite medida que cresce o tamanho do problema. Supe-se que a quantidade de dados a ser processada suficientemente grande para essa tendncia se evidenciar. Isto leva a outra simplificao: ao se ter uma expresso polinomial P(n), como os termos de menor grau podem ser desprezados (quando n grande) diante do termo de maior grau, este que ser adotado como aproximao. Por exemplo: an + bn cn + d ser reduzido a n.
369

Cabe aqui ressaltar a importncia dessa anlise. Apressadamente poder-se-ia supor que, com a incrvel melhoria em desempenho dos equipamentos recentes frente aos de algum tempo atrs, e mesmo com a expectativa de ainda melhores performances no futuro, seria desnecessria uma preocupao com a qualidades de uma soluo em termos de eficincia. No entanto, o que se observa um crescimento tambm acelerado na dificuldade dos problemas submetidos ao computador, alm de um aumento significativo na quantidade de dados (tamanho de entradas). Ou seja: as mquinas melhoram mas os problemas pioram. E at numericamente pode-se verificar que uma soluo de baixa qualidade torna-se economicamente imune ao aumento da capacidade de processamento das mquinas. Finalmente, certamente h um limite para o aumento de desempenho das mquinas. Ento, a escolha da melhor soluo se tornar crtica.
370

A notao O

Diz-se que uma funo g(n) O(f(n)), notando-se g = O(f(n)) se existir alguma constante c > 0 e um inteiro n0, tal que n > n0 implica g(n) <= c* f(n).

371

A notao O
Diz-se tambm que g(n) tem taxa de crescimento proporcional a f(n), de ordem mxima f(n), de magnitude f(n), de complexidade f(n) ou simplesmente que O de f(n). Isto interpretado como uma constatao de que f expressa um limite superior para valores assintticos de g. Ou que O(f(n)) tem como valor uma quantidade no conhecida explicitamente, sabendo-se que no excede c*f(n), se n for suficientemente grande (n>n0). Este n0 ento o ponto a partir do qual g(n) seguramente menor que (ou ultrapassada por) f(n).

372

A notao O
Exemplo: se g(n)= n + 1, ento g(n)=O(n); tambm: g(n)=n+5n-3 O(n); g(n)=517 O(1); g(n)=k2n O(2n). bom ressaltar que g(n)=n+1 tambm O(n) e O(n4)... pois estas expresses satisfazem a relao acima. Ao se tomar o tempo de execuo T(n) de um algoritmo com uma funo g, na implicao acima, pode-se naturalmente considerar sobre sua magnitude, ou sua complexidade f.
373

A notao O
No trabalho com estruturas de dados so as seguintes as complexidades costumeiras (em ordem crescente):
O(1) ou constante; O(log n) ou logaritmica; O(n) ou linear; O(n log n) ou n log de n; O(n) ou quadrtica; O(n) ou cbica.

Embora no se apresente aqui um mtodo formal para determinao da magnitude de uma funo, um conjunto de regras prticas pode ajudar. As consideraes anteriores sobre simplificaes da expresso da taxa de crescimento do tempo de 374

A notao O
execuo valem para a determinao de O(f(n)) de algoritmos, pois atendem propriedade descrita. Por exemplo: a. regra da complexidade polinomial: se P(n) um polinmio de grau k, ento P(n) = O(nk). Isto certo devido a que, para valores grandes de n, os termos adicionais do polinmio podem ser desprezados, e se ter constantes c e n0 tais que, para todo n > n0, P(n)<=c*nk. Alm disso, certo que: b. f(n) = O(f(n));
375

A notao O
c. regra da constante: O(c*f(n)) = c* O(f(n)) = O(f(n)); d. O(f(n)) + O(f(n)) = O(f(n)) ; e. regra da soma de tempos: se T1(n) = O(f(n)) e T2(n) = O(g(n)) ento T1(n) + T2(n) = O(max(f(n),g(n))) Isto significa que a complexidade de um algoritmo com dois trechos em sequncia com tempos de execuo diferentes dada como a complexidade do trecho de maior complexidade.
376

A notao O
f. regra do produto de tempos: se T1(n) = O(f(n)) e T2(n) = O(g(n)) ento T1(n) * T2(n) = O(f(n) * g(n)) Isto significa que a complexidade de um algoritmo com dois trechos aninhados, em que o segundo repetidamente executado pelo primeiro, dada como o produto da complexidade do trecho mais interno pela complexidade do trecho mais externo.
377

Complexidade de algumas estruturas de controle

Regras rgidas sobre o clculo da complexidade de qualquer algoritmo no existem, cada caso deve ser estudado em suas condies. No entanto, as estruturas de controle clssicas da programao estruturada permitem uma estimativa tpica de cada uma. A partir disso, algoritmos construdos com combinaes delas podem ter sua complexidade mais facilmente estabelecida.

378

a. comando simples tem um execuo constante, O(c) = O(1).

tempo

de

b. seqncia tem um tempo igual soma dos tempos de cada comando da seqncia; se cada comando O(1), assim, tambm ser a seqncia; seno, pela regra da soma, a seqncia ter a complexidade do comando de maior complexidade. c. alternativa qualquer um dos ramos pode ter complexidade arbitrria; a complexidade resultante a maior delas; isto vale para alternativa dupla (if-else) ou mltipla (switch).
379

d. repeties i. repetio contada: aquela em que cada iterao (ou volta) atualiza o controle mediante uma adio (geralmente, quando se usa uma estrutura do tipo for, que especifica incremento/decremento automtico de uma varivel inteira). Se o nmero de iteraes independente do tamanho do problema, a complexidade de toda a repetio a complexidade do corpo da mesma, pela regra da constante (ou pela regra da soma de tempos). for (i=0; i<k ; i++) // se k no f(n) ento trecho com O(g(n)) // o trecho O(g(n))
380

for (i=0; i<10 ; i++) // isto O(1), logo toda // a repetio O(1) { x = x+v; printf (%d, x); }

Se o nmero de iteraes funo de n, pela regra do produto teremos a complexidade da repetio como a complexidade do corpo multiplicada pela funo que descreve o nmero de iteraes. Isto :
for (i=0; i<n ; i++) // como o nmero de iteraes f(n)=n trecho com O(g(n)) // ento o trecho O(n*g(n))

Exemplo:
for (i=0; i<k*n ; i++) // o trecho O(f(n)*g(n)), no caso trecho com O(log n) // O(k*n*log n), ou seja: O(n log n)
381

Uma aplicao comum da regra do produto a determinao da complexidade de repeties aninhadas. Exemplo:
// o trecho O(f(n)*g(n)), no caso for (i=0; i<n ; i++) for (j=0; j<n ; j++) // g(n)=n*1 (lao interno); logo, trecho com O(1) // O(n*n), ou seja: O(n)

Exemplo:
for (i=1; i<=n ; i++) // o lao interno executado 1+2+3 for (j=1; j<=i ; j++) // +...n-1 +n=n*(n+1)/2 vezes, logo, trecho com O(1) // O(n*(n+1)/2), ou seja: // O(0.5(n+n)) ou seja O(n)

382

Exemplo:
for (i=1; i<=n ; i++) // o lao interno executado n+n-1 for (j=n; i<=j ; j--) // +n-2+...+2+1=n*(n+1)/2 vezes, ou trecho com O(1) // seja: O(n) como no caso anterior

Os dois ltimos exemplos podem ser generalizados para quaisquer aninhamentos de repeties contadas em k nveis, desde que todos os ndices dependam do tamanho do problema. Nesse caso, a complexidade da estrutura aninhada ser da ordem de nk. Exemplo:
for (IndExt=1; IndExt<=n ; IndExt++) // o lao mediano for (IndMed=IndExt; IndMed<=n ; IndMed++) // executado for (IndInt=1; IndInt<=IndMed; IndInt++) // n+n-1+n-2+... trecho com O(1) // +2+1=(n+n)/2 vezes; o lao mais // interno ser executado no mximo n vezes; logo, // tem-se O((n+n)*n), ou seja: O(n)
383

ii. repetio multiplicativa: aquela em que cada iterao atualiza o controle mediante uma multiplicao ou diviso. Exemplo:
limite=1; // o nmero de iteraes depende while (limite<=n) // de n; limite vai dobrando a cada { // iterao; depois de k iteraes, limite = 2k e trecho com O(1) // k = log2 limite; como o valor limite = limite*2; // mximo de limite n, ento } // o trecho O(log2n) = O(log n)

OBS: Na verdade O(log n) independe da base do logaritmo, pois logan = logab*logbn = c*logbn.
384

Os dois exemplos anteriores tambm podem ser generalizados, adotando-se um fator genrico de multiplicao fator. Nesse caso, o nmero de iteraes ser dado por k = logfator limite = O(log f(n)), se o limite funo de n.
Exemplo: int limite=n; while (limite!=0)

Exemplo: /* o nmero de iteraes depende de n; limite vai-se subdividindo int limite; for (limite=n; limite!=0; limite /=2) a cada iterao; depois de k=log2n iteraes, encerra; trecho com O(1) ento o trecho O(log n)*/

/* o nmero de iteraes depende { de n; limite vai-se subdividindo for (i=1; i<=n; i++) a cada iterao; o lao interno trecho com O(1) O(n), o externo O (log n); logo, o trecho O (n log n)*/ limite = limite/2;
385

e.

Chamada de Procedimento Pode ser resolvida considerando-se que o procedimento tambm tem um algoritmo com sua prpria complexidade. Esta usada como base para clculo da complexidade do algoritmo invocador. Por exemplo: se a invocao estiver num ramo de uma alternativa, sua complexidade ser usada na determinao da mxima complexidade entre os dois ramos; se estiver no interior de um lao, ser considerada no clculo da complexidade da seqncia repetida, etc.

386

A questo se complica ao se tratar de uma chamada recursiva. Embora no haja um mtodo nico para esta avaliao, em geral a complexidade de um algoritmo recursivo ser funo de componentes como: a complexidade da base e do ncleo da soluo e a profundidade da recurso. Por este termo entende-se o nmero de vezes que o procedimento invocado recursivamente. Este numero, usualmente, depende do tamanho do problema e da taxa de reduo do tamanho do problema a cada invocao. E na sua determinao que reside a dificuldade da anlise de algoritmos recursivos.
387

Como exemplo, considere o algoritmo do clculo fatorial. int fatorial (int n) { if (n==0) return 1; // Base else return n*fatorial(n 1); //Ncleo } A reduo do problema se faz de uma em uma unidade, a cada reinvocao do procedimento, a partir de n, at alcanar n = 0. Logo, a profundidade da recurso igual a n. O ncleo da soluo (que repetido a cada reinvocao) tem complexidade O(1), pois se resume a uma multiplicao. A base tem complexidade O(1), pois envolve apenas uma atribuio simples. Nesse caso, conclui-se que o algoritmo tem um tempo T(n) = n*1+1 = O(n).
388

f.

Desvio Incondicional A discusso anterior subentendeu que os algoritmos sejam construdos com as estruturas da programao disciplinada. O uso indiscriminado de desvios incondicionais h de incrementar a dificuldade do clculo da complexidade. No entanto o go to no causar problemas em casos de uso restrito: - quando for usado como desvio para a frente, associado a uma condio de parada, apenas para sair de um lao de repetio, diretamente para o comando subseqente ao corpo da repetio;

389

nesse caso, o tempo do pior caso no afetado, pois se assume que o pior caso quando tal desvio nunca executado e a repetio ocorre um nmero mximo de vezes; - quando for um desvio para trs, estabelecendo uma repetio adequadamente estruturada (ou seja: desde que os laos estejam separados ou completamente aninhados); nesse caso, a anlise pode ser feita sobre a estrutura de repetio resultante.

390

Comparao Quantitativa
Pode-se ter uma idia do que alguns autores chamam de tirania da taxa de crescimento observando-se o comportamento de diversos algoritmos de complexidade diferente, todos dedicados soluo do mesmo problema, sob as mesmas condies de processamento. Nos valores a seguir, assume-se que uma operao elementar executvel em um dcimo de microssegundo (0,1 * 10-6s).

391

digna de nota a taxa de crescimento dos algoritmos de ordem exponencial. Em geral, seu desempenho torna-se de custo proibitivo, devendo ser usados apenas quando no se conhea soluo de menor complexidade. Complementarmente, pode-se considerar o efeito do aumento da capacidade de processamento sobre
392

o tamanho do maior problema solucionvel em um certo tempo. A tabela a seguir apresenta resultados para os mesmos algoritmos, executados na mquina original e em mquinas 100 e 1000 vezes mais rpidas, tomando-se como bsico o tamanho do problema solucionvel em 1 hora de processamento.

393

Como j se citou anteriormente, algoritmos de alta complexidade tm um ganho pouco significativo em funo da capacidade da mquina. Os algoritmos exponenciais, principalmente, so quase imunes: no exemplo da tabela, o algoritmo com O(2n) tem um ganho de apenas 10 unidades no tamanho do problema solucionvel com uma mquina 1000 vezes mais rpidas! Na prtica, so consideradas aceitveis complexidades no mximo polinomiais na ordem de n.
394

Você também pode gostar