|
@@ -0,0 +1,634 @@
|
|
|
+<!--
|
|
|
+ Introdução à Programação - 2017 - Prof. Leoônidas de Oliveira Brandão
|
|
|
+ Introdução à eficiência de algoritmos
|
|
|
+ LInE (Laboratory of Informatics in Education) - http://www.usp.br/line
|
|
|
+ IME - USP
|
|
|
+ Material didático
|
|
|
+ Pode usar livrevemente este material para fins não comerciais, devendo sempre fazer referência à autoria.
|
|
|
+ Sugestões/apontamento são bem vindos: leo@ime.usp.br (favor indicar no assunto "material de introducao 'a programacao")
|
|
|
+ Prof. Leônidas de Oliveira Brandão
|
|
|
+ http://www.ime.usp.br/~leo
|
|
|
+ http://line.ime.usp.br
|
|
|
+ http://www.matemtica.br
|
|
|
+-->
|
|
|
+
|
|
|
+ <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
|
|
|
+ <meta name='keywords' content='mac0122, material, professores, leonidas de oliveira brandao'>
|
|
|
+ <link rel='stylesheet' type='text/css' href='css_img_js_conf/all.css'>
|
|
|
+ <link rel='stylesheet' type='text/css' href='css_img_js_conf/line_introducao_programacao.css'>
|
|
|
+ <script src="css_img_js_conf/defineLInE.js"></script> <!-- para referencias 'a documentos internos -->
|
|
|
+<div class="pagina">
|
|
|
+
|
|
|
+
|
|
|
+<p class="secao">Introdução à eficiência de algoritmos</p>
|
|
|
+
|
|
|
+<center><p>[
|
|
|
+ <a href="#precisao" title="Problema de precisao numerica">Precisão</a> |
|
|
|
+ <a href="#tempo" title="Eficiencia em relacao ao tempo de execucao">Velocidade</a> |
|
|
|
+ <a href="#cosseno" title="Eficiencia: examinando a funcao transcendente <i>cosseno</i>">Cosseno</a> |
|
|
|
+ <a href="#muitoingenuo" title="Uma primeira solucao nada eficiente para a funcao <i>cosseno</i>">Solução ineficiente</a> |
|
|
|
+ <a href="#ingenuo" title="Uma segunda solucao para a funcao <i>cosseno</i>: ainda ineficaz">Solução</a> |
|
|
|
+ <a href="#eficiente" title="Uma solucao eficiente para a funcao <i>cosseno</i>">Solução eficiente</a>
|
|
|
+ ]</p>
|
|
|
+</center>
|
|
|
+
|
|
|
+<!--
|
|
|
+<p>
|
|
|
+<b>Alterações</b>: //2017 <i>C</i> e <i>Python</i>)
|
|
|
+</p>
|
|
|
+-->
|
|
|
+
|
|
|
+<p>
|
|
|
+ Nesta seção examinaremos uma questão essencial à computação, se o algoritmo é ou não eficiente para resolver o problema de interesse.
|
|
|
+ Sobre isso existem vários aspectos que podem ser considerados, aqui examinaremos dois: a <b>precisão</b> e a <b>velocidade</b>.
|
|
|
+ <center>
|
|
|
+ <img src="img/img_precisao_x_velocidade.png" title="Gráfico ilustrando que geralmente precisão varia inversamente com a velocidade"/>
|
|
|
+ <br/>
|
|
|
+ <i>Fig. 1. Representação de que maior velocidade de execução geralmente implica em menor precisão numérica.</i>
|
|
|
+</center>
|
|
|
+
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ A <b style="color:#0000aa;">precisão</b> diz respeito à "qualidade" da resposta, é necessário que ela seja
|
|
|
+ <b style="color:#00aa00;">suficientemente próxima da real solução</b> para o problema considerado.
|
|
|
+ Para ilustrar podemos considerar um exemplo <i>numérico</i>, por exemplo, tentar encontrar a raíz para determinada
|
|
|
+ função <i>f(.)</i>, ou seja, encontrar (se existir) algum <i>x</i> para o qual <i>f(x)=0</i>.
|
|
|
+ Nesse caso, se o interesse estiver em pontos dentro do intervalo <i>[0, 1]</i>, seria inútil se o ponto devolvido
|
|
|
+ tivesse um <i style="color:#aa0000;" title="pois se a resposta foi 1/2, na verdade a raíz poderia ser desde o ponto 0 até o ponto 1!">erro
|
|
|
+ maior que <i>0,5</i></i> (i.e., <i>|f(x)| > 0,5</i>).
|
|
|
+ <br/>
|
|
|
+ Outro exemplo numérico é computar uma
|
|
|
+ <a href="https://pt.wikipedia.org/wiki/Fun%C3%A7%C3%A3o_transcendente" target="_blank"
|
|
|
+ title="função que não pode ser escrita com operações algébricas - ver na WikiPedia">função transcendente</a> como o <i>cosseno</i> de valor real.
|
|
|
+ Uma vez as operações que um computador digita consegue efetivamente realizar são aquelas diretamente derivadas da soma, é preciso encontrar um
|
|
|
+ algoritmo que compute uma <i>boa aproximação</i> para o <i>cosseno(x)</i>, qualquer que seja o valor real <i>x</i>.
|
|
|
+ Neste texto examinaremos as implementações para as funções <i>seno</i> e <i>cosseno</i> empregando o conceito matemático
|
|
|
+ <a href="https://pt.wikipedia.org/wiki/S%C3%A9rie_de_Taylor" target="_blank">
|
|
|
+ <b style="color:#00aa00;"
|
|
|
+ title="um somatório com infinitos termos que aproxima a função por meio de um
|
|
|
+ polinômio cuja propriedade é que suas derivadas, de todas as ordens,
|
|
|
+ coincidem com as derivadas da função aproximada no ponto dado">série de Taylor</b></a>. <!-- " -->
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Quanto à <b style="color:#0000aa;">velocidade</b>, ela diz respeito ao <i>tempo que o algoritmo leva para computar a resposta</i>.
|
|
|
+ É preciso que o tempo seja viável para o contexto, quer dizer que ao obter a resposta, essa ainda seja útil.
|
|
|
+ Para ilustrar podemos considerar novamente o exemplo <i>numérico</i> da determinação da raíz da função <i>f(.)</i> (se existir, encontrar
|
|
|
+ algum <i>x</i> para o qual <i>f(x)=0</i>).
|
|
|
+ Digamos que o interessado seja uma industria de produção de tintas e que a raíz representa o melhor momento para interromper a
|
|
|
+ produção de cada um de seus 300 pigmentos distintos.
|
|
|
+ Nesse caso, se a resposta para cada função/pigmento demorar cerca de 10 minutos, então o método empregado é
|
|
|
+ inviável, pois seria <i style="color:#aa0000;" title="pois levaria mais de 2 dias para obter a produção ideal para aquele dia!">necessário
|
|
|
+ cerca de 50 horas para obter as 300 raízes</i> (<i>50 x 10 min = 3000 min</i>).
|
|
|
+ <br/>
|
|
|
+ Um exemplo de outra natureza seria o contexto de planejamentos de pousos e aterrissagens em um grande aeroporto, após a ocorrência
|
|
|
+ de algum evento não esperado, deve-se gerar uma nova escala para as próxima
|
|
|
+ aterrissagens e decolagens muito rapidamente, antes que acabe o combustível de alguma aeronave em sobrevoo.
|
|
|
+
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="precisao">
|
|
|
+<p class="secao">Problema de precisão numérica</p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Como discutido no <a href="#" onclick="trocaPagina('introducao_float.html')" title="examinar o texto sobre ponto flutuante">texto sobre
|
|
|
+ <i>ponto flutuante</i></a>, o problema de precisão numérica inicia-se na impossibilidade de representar valores
|
|
|
+ <i style="color:#00aa00;" title="Impossível representar todos os dígitos de Pi (pois são infinitos)!">irracionais</i>
|
|
|
+ e mesmo
|
|
|
+ <i style="color:#00aa00;" title="Impossível representar todos os dígios de 1/3 (pois são infinitos)!">dizimas periódicas</i> que precisam
|
|
|
+ ser <b style="color:#0000aa;" title="Por exemplo, representar 1/3 por 0,3333 (truncando na quarta casa decimal)">truncados</b>.
|
|
|
+ Mas existe outra questão de eficiência, que é a precisão numérica do algoritmo utilizado para o cômputo.
|
|
|
+ Um exemplo ilustrativo é acumular valores que crescem muito rapidamente (como funções exponenciais ou fatoriais) ou
|
|
|
+ dividir por valores que aproximam-se de zero, ambos podem produzir resultados "ruins":
|
|
|
+ <b style="color:#0000aa;" title="estourar o maior valor que o computador tem capacidade para representar (overflow)">estouro</b>
|
|
|
+ (ou <i>transbordamento</i>).
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Como citado acima, um problema computacional é conseguir implementar funções que não podem ser escritas a partir de operações elementares
|
|
|
+ de soma e multiplicação, com é o caso da função <i>cosseno</i> (que por isso é transcendental).
|
|
|
+ Hoje, este não parece ser um problema, pois qualquer computador de bolso tem uma calculadora implementada que é capaz de computar boas
|
|
|
+ aproximações para a função <i>cosseno</i>.
|
|
|
+ Entretanto, isso só é possível por implementarem algum algoritmo eficiente, a partir de somas e multiplicações.
|
|
|
+ No caso das <i>funções trigonométicas</i>, isso é feito usando a técnica matemática da
|
|
|
+ <i title="Na verdade com um truque para tratar todos os períodos">série de Taylor</i>.
|
|
|
+ Isso será ilustrado nas tabelas <a href="#solucaoruim" title="seguir para implementação ruim">1 (implementação ineficiente)</a>
|
|
|
+ e <a href="#solucaoboa" title="seguir para implementação boa">2 (implementação eficiente)</a>.
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="tempo">
|
|
|
+<p class="secao">Eficiência em relação ao tempo de execução</p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Existem problemas que sabidamente são intratáveis no sentido de sabermos, matematicamente, que é impossível existir
|
|
|
+ um algoritmo que consegue resolvê-lo (como o <i>problema da parada</i>: não existe algoritmo que possa decidir se um algoritmo qualquer pára ou
|
|
|
+ após um tempo finito de execução).
|
|
|
+</p>
|
|
|
+
|
|
|
+<!--p class="sabermais" title="Para quem deseja saber um pouco mais 'como funciona'"-->
|
|
|
+<p><sabermais title="Para quem deseja saber um pouco mais 'como funciona'">
|
|
|
+ Existem outros tipos de problemas que supomos <b style="color:#aa0000;">não</b> ser possível resolvê-los eficientemente, ou seja,
|
|
|
+ todas suas soluções conhecidas são ineficientes
|
|
|
+ (tempo proporcional à uma função exponencial, de base maior que a unidade).
|
|
|
+ Um exemplo nessa categoria, que denominada
|
|
|
+ <a href="https://en.wikipedia.org/wiki/NP-completeness" target="_blank" title="Examinar texto na WikiPedia em Inglês">NP-Completo</a>, é o
|
|
|
+ <a href="https://pt.wikipedia.org/wiki/Problema_de_satisfatibilidade_booliana" target="_blank"
|
|
|
+ title="examinar a explicação na WikiPedia sobre o problema da satisfatibilidade"><i>problema da satisfatibilidade</i></a>:
|
|
|
+ encontrar valores para cada uma de suas variáveis, que são <i>booleanas</i>, de modo a tornar a expressão lógica verdadeira,
|
|
|
+ como em <i>f(b<sub>1</sub>, b<sub>2</sub>, b<sub>3</sub>) = b<sub>1</sub> E (b<sub>2</sub> OU não b<sub>3</sub>)</i>.
|
|
|
+ Para esse caso, por ser uma instância pequena do problema, é fácil perceber que a resposta é <i>sim</i>, basta tomar
|
|
|
+ <i>b<sub>1</sub></i> e <i>b<sub>2</sub></i> como <i>verdadeiro</i> (não importando o valor de <i>b<sub>3</sub></i>
|
|
|
+ pode ser <i>verdadeiro</i> ou <i>falso</i>).
|
|
|
+ A solução conhecida para a <i>satisfatibilidade</i> é tentar todas as <i>2<sup>n<sup></i> possibilidades para as <i>n</i>
|
|
|
+ variáveis! Se o <i>n</i> for grande, é inviável esperar por uma resposta.
|
|
|
+ Exemplo para <i>n=3</i>: testar com
|
|
|
+ <codigo2> <i>f(verdadeiro,verdadeiro,verdadeiro)</i>, <i>f(verdadeiro,verdadeiro,falso)</i>,
|
|
|
+ <i>f(verdadeiro,falso, verdadeiro)</i>, <i>f(verdadeiro,falso, falso)</i>,
|
|
|
+ <i>f(falso, verdadeiro,verdadeiro)</i>, <i>f(falso, verdadeiro,falso)</i>,
|
|
|
+ <i>f(falso, falso, verdadeiro)</i>, <i>f(falso, falso, falso)</i>.</codigo2>
|
|
|
+</sabermais>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Na seção seguinte examinaremos um particular problema, conseguir um valor aproximado para a função trigonométrica <i>cosseno</i>,
|
|
|
+ explorando tanto a questão da eficiência numérica (precisão) quanto a de tempo de execução (velocidade).
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="cosseno">
|
|
|
+<p class="secao"><i>Série de Taylor</i> e a função <i>cosseno</i></p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+ A função <i>cosseno</i>, assim como várias outras, não pode ser computada de modo exato em um computador digital,
|
|
|
+ em razão de ser uma
|
|
|
+ <a href="https://pt.wikipedia.org/wiki/Função_transcendente" title="sobre funcao transcendente">função <i>transcendente</i></a>
|
|
|
+ (basicamente, significa que não pode ser computada a partir de somas).
|
|
|
+ Então usamos uma
|
|
|
+ <a href="https://en.wikipedia.org/wiki/Taylor_series" target="_blank"
|
|
|
+ title="examinar a explicação na WikiPedia sobre séries de Taylor">teoria matemática apresentada em 1715</a>,
|
|
|
+ aproximá-la por uma função polinomial que tem como propriedade ter todas as derivadas
|
|
|
+ coincidindo com as derivadas da função a ser aproximada, considerando determinado ponto fixado.
|
|
|
+ Esse polinômio de infinitos termos, recebeu o nome de
|
|
|
+ <a href="https://pt.wikipedia.org/wiki/S%C3%A9rie_de_Taylor" title="sobre serie de Taylor na WikiPedia"><b>série de Taylor</b></a>,
|
|
|
+ devido seu proponente Brook Taylor.
|
|
|
+</p>
|
|
|
+
|
|
|
+</p>
|
|
|
+ O <i>polinômio de Taylor</i> que aproxima a função cosseno quando aplicada ao ponto <i>x</i> é:
|
|
|
+ <center>
|
|
|
+ <i>cos(x) = 1 - x<sup>2</sup> /2! + x<sup>4</sup> /4! - x<sup>6</sup> /6! + x<sup>8</sup> /8! + ...</i>
|
|
|
+ </center>
|
|
|
+ Se estiver interessado entender a Matemática para se construir a demostração desse polinômio, estude o bloco abaixo.
|
|
|
+<p>
|
|
|
+
|
|
|
+<p>
|
|
|
+<sabermais title="Para quem deseja saber um pouco mais: mas precisa conhecer Cálculo Diferencial">
|
|
|
+ Como a função <i>cosseno</i> é periódica, de período <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">2*Π</i>
|
|
|
+ e além disso os valores em cada quadrante coincidem em módulo, podemos computar apenas para primeiro caso, para <i>x</i> entre
|
|
|
+ <i>0</i> e <i>Π/2</i>.
|
|
|
+ <img class="imgDir" src="img/img_eficiencia_cos_trig.png" title="Imagem (do iGraf) da função trigonométrica cosseno indicando o período 2*Π">
|
|
|
+ Desse modo, podemos usar um caso particular da <b>série de Taylor</b>, a séria aplicada ao ponto <i>x=0</i>, que neste caso é chamada
|
|
|
+ de
|
|
|
+ <a href="https://en.wikipedia.org/wiki/Colin_Maclaurin"
|
|
|
+ title="sobre serie de MacLaurin na WikiPedia"><b style="color:#0000aa">série de MacLaurin</b></a>.
|
|
|
+ Fixado um valor real <i>x</i> entre <i>0</i> e <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">Π/2</i>,
|
|
|
+ a <i>série de MacLaurin</i> que aproxima o cosseno aplicado ao ponto <i>x=0</i>, <i>cos(x)</i>, é o polinômio (de infinitos termos)
|
|
|
+
|
|
|
+ <i>p(x)=a<sub>0</sub>+a<sub>1</sub>x<sup>1</sup>+a<sub>2</sub>x<sup>2</sup>+a<sub>3</sub>x<sup>3</sup>+...</i>
|
|
|
+
|
|
|
+ de tal forma que
|
|
|
+ a <i>k</i>-ésima derivada de <i>p(0)</i> coincide com a <i>k</i>-ésima derivada de <i>cos(0)</i>, ou seja,
|
|
|
+ <i>cos(0)=p(0)</i>, <i>cos'(0)=p'(0)</i>, <i>cos''(0)=p''(0)</i> e assim por diante.
|
|
|
+<br/>
|
|
|
+<br/>
|
|
|
+ Assim, no caso da função cosseno, escrevendo o polinômio candidato
|
|
|
+ <i>p(x) = a<sub>0</sub> x<sup>0</sup> + a<sub>1</sub> x<sup>1</sup> + a<sub>2</sub> x<sup>2</sup> +...</i>
|
|
|
+ e estabelecendo as identidades para cada derivada e usando <i>x=0</i> e sabendo que
|
|
|
+ <i>cos(0)=1</i>, <i>sen(0)=0</i>, que
|
|
|
+ <i>cos'(x)=-sen(x)</i> e que <i>sen'(x)=cos(x)=</i>, obtém-se
|
|
|
+ <br/>
|
|
|
+ <i>p(0)=a<sub>0</sub>
|
|
|
+ ⇒ a<sub>0</sub> = 1<br/>
|
|
|
+ <i>p'(0)=a<sub>1</sub>+2a<sub>2</sub>x<sup>1</sup>+3a<sub>3</sub>x<sup>2</sup>+...</i>|x=0
|
|
|
+ ⇒ p'(0)=a<sub>1</sub>
|
|
|
+ ⇒ a<sub>1</sub> = p'(0)= -sen(0) = 0
|
|
|
+ ⇒ a<sub>1</sub> = 0<br/>
|
|
|
+ <i>p''(0)=2a<sub>2</sub>+6a<sub>3</sub>x<sup>1</sup>+12a<sub>4</sub>x<sup>2</sup>+...</i>|x=0
|
|
|
+ ⇒ (1/2)p''(0)=a<sub>2</sub>
|
|
|
+ ⇒ a<sub>2</sub> = (1/2)(-cos(0)) = -1
|
|
|
+ ⇒ a<sub>2</sub> = -1/2<br/>
|
|
|
+ <i>p'''(0)=6a<sub>3</sub>+24a<sub>4</sub>x<sup>1</sup>+...</i>|x=0
|
|
|
+ ⇒ (1/6)p'''(0)=a<sub>3</sub>
|
|
|
+ ⇒ a<sub>3</sub> = (1/6)(cos'''(0)) = (1/3!)(-sen''(0)) = (1/3!)(-cos'(0)) = (1/3!)sen(0) = (1/3!)0
|
|
|
+ ⇒ a<sub>3</sub> = 0<br/>
|
|
|
+ e assim, por diante, obtendo-se o polinômio aproximador:
|
|
|
+ <br/>
|
|
|
+
|
|
|
+ <i>cos(x) = 1 - x<sup>2</sup> /2! + x<sup>4</sup> /4! - x<sup>6</sup> /6! + x<sup>8</sup> /8! + ...</i>
|
|
|
+</sabermais>
|
|
|
+</p>
|
|
|
+
|
|
|
+<!--
|
|
|
+⇒ = implica
|
|
|
+≃ aproximado
|
|
|
+✓
|
|
|
+ -->
|
|
|
+
|
|
|
+<a name="cosseno">
|
|
|
+<p class="secao">Eficiência: examinando a função transcendente <i>cosseno</i></p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+ A implementação da aproximação da função <i>cosseno</i>, próximo do valor nulo, por <i>série de Taylor</i> produz um bom exemplo
|
|
|
+ para ilustrar <i>ineficiência</i> e <i>eficiência</i> de algoritmos, quanto ao tempo de execução e quanto à precisão numérica.
|
|
|
+ Uma primeira observação é que é necessário truncar a série por duas razões:
|
|
|
+ é impossível computar infinitos termos; além disso a partir de determinado termo, todos os seguintes resultam zero (devido à
|
|
|
+ precisão finita de qualquer computador).
|
|
|
+ Portanto, devemos <i>truncar</i> a <i>série de Taylor</i>.
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Analisando o polinômio de Taylor para o <i>cosseno</i>, talvez um aprendiz na arte da programação perceba
|
|
|
+ que é possível escrever cada termos do somatório com
|
|
|
+ <i>(-1)<sub>k</sub>(1/2k!)x<sup>2k</sup></i>, <i>k=0,1,2,...</i>.
|
|
|
+ Ou seja, cada termo tem no numerador um cálculo de exponencial (<i>x<sup>2k</sup></i>) e no denominador um cálculo de fatorial (<i>2k!</i>).
|
|
|
+ Daí poderia-se desenhar um laço acumulando cada um desses termos.
|
|
|
+ <br/>
|
|
|
+ Entretanto, essa solução tem um claro problema (que aparecerá no primeiro teste):
|
|
|
+ o computo de exponencial (se maior que um) e de fatorial, ambos crescem muito rápido!
|
|
|
+ Isso resultará em valores nada próximos do esperado.
|
|
|
+ Isso é ilustrado abaixo.
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="taylor">
|
|
|
+<p class="secao">Uma primeira solução nada eficiente para a função <i>cosseno</i></p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+A partir da série de Taylor para o cosseno, percebe-se que pode-se elaborar um laço que a cada passo computa um dos termos da série, primeiro
|
|
|
+<i>1=x<sup>0</sup>/0!</i>, depois <i>x<sup>2</sup>/2!</i> e assim por diante, para um passo genérico <i>k</i> seria,
|
|
|
+<i>-1<sup>k</sup>x<sup>2k</sup>/(2k!)</i>.
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+Desse modo uma primeira solução, seria implementar duas funções auxiliares, uma para computar a potência (<i>pot</i>) e uma para o
|
|
|
+fatorial (<i>fat</i>), a cada passo invocando as funções auxiliares.
|
|
|
+Vamos simplificar a condição de parada, fixando a <i>priori</i> o número de termos calculados, digamos <i>NT=10</i>.
|
|
|
+<center>
|
|
|
+<a name="solucaoruim">
|
|
|
+<i>Tab. 1. Uma implementação muito ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
|
|
|
+</a>
|
|
|
+<br/>
|
|
|
+<table class="tbCodeLinCol">
|
|
|
+ <tr class="tbCodeLinColH"><th colspan="2">Algoritmo muito ineficiente para cômputo do cosseno</th></tr>
|
|
|
+ <tr><th>C </th> <th>Python</th></tr>
|
|
|
+ <tr valign="top"><td><table class="tbCode">
|
|
|
+ <tr><td><pre>i = 0;
|
|
|
+while (i<2*NT) { <cyan>// Ineficiente! Nao implemente assim!</cyan>
|
|
|
+ soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
|
|
|
+ i += 2;
|
|
|
+ }</pre></td></tr>
|
|
|
+ </table></td>
|
|
|
+ <td><table class="tbCode"><pre>i = 0;
|
|
|
+while (i<2*NT) : <cyan># Ineficiente! Nao implemente assim!</cyan>
|
|
|
+ soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
|
|
|
+ i += 2;
|
|
|
+
|
|
|
+ return soma;</pre></td></tr>
|
|
|
+ </table></td></tr>
|
|
|
+</table></center>
|
|
|
+<p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Testando-se o algoritmo acima, percebe-se rapidamente que o método não é eficiente. Por exemplo, examinando a tabela 1, na qual listamos
|
|
|
+ os resultados para vários valores de <i>x</i> empregando o método acima, um método eficiente (vistao abaixo) e a função <i>cosseno</i>
|
|
|
+ da biblioteca da linguagem.
|
|
|
+ Nota-se que já na segunda linha, para <i>x=0.05</i>, o método ineficiente gera o valor <i>1.0012</i> enquanto o eficiente e a biblioteca
|
|
|
+ <i>0.9988</i>, uma diferença nada desprezível.
|
|
|
+ Isso vai piorando com o aumento de <i>x</i>, para o último tabelado, <i>x=0.45</i>, a diferença já está em quase <i>0.2</i>!
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+<b>Ineficiência 1</b>.
|
|
|
+ Fazendo um rápido exame da solução notamos um primeiro item de ineficiência:
|
|
|
+ computar <i>pot(-1, 2*i)</i> a cada passo.
|
|
|
+ Isso é completamente desnecessário, pois de um passo ao outro deve-se apenas inverter o sinal, então bastaria usar uma variável
|
|
|
+ <tt>sinal</tt>,
|
|
|
+ que a cada passo fizesse <tt>sinal = -sinal</tt>.
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+<b>Ineficiência 2</b>.
|
|
|
+ Note que a mesma ineficiência do sinal ocorre com <tt>pot</tt> e <tt>fat</tt>, pois a cada passo ignora-se que uma potência e um fatorial
|
|
|
+ parcial já foi computado, calculando tudo desde o primeiro valor.
|
|
|
+ Se ainda não está claro que pode ser melhorado, vamos examinar os passos <tt>k=2</tt> e <tt>k=3</tt>:
|
|
|
+ <br/>
|
|
|
+ - <tt>k=2</tt>: computa-se <tt>pot(x, 2*2)=pot(x, 4)</tt> e <tt>fat(2*2)=fat(4)</tt>; e<br/>
|
|
|
+ - <tt>k=3</tt>: computa-se <tt>pot(x, 2*3)=pot(x, 6)</tt> e <tt>fat(2*3)=fat(6)</tt>.<br/>
|
|
|
+ Mas, se usarmos uma variável <tt>pot1</tt> para manter a potência e <tt>fat1</tt> para manter o fatorial,
|
|
|
+ <!--
|
|
|
+ <i>x<sup>2k</sup></i> (<tt>pot(x, 2*k)</tt>) e
|
|
|
+ <tt>fat1</tt> para manter <i>2k!</i> (<tt>fat(2*k)</tt>),
|
|
|
+ -->
|
|
|
+ então, ao final do passo <tt>k=2</tt>,
|
|
|
+ <tt>pot1</tt> estaria com o valor <i>x<sup>2k</sup>=x<sup>4</sup></i> e <tt>fat1</tt> com o valor <i>2k!=4!=24</i>.
|
|
|
+ <br/>
|
|
|
+ Portanto, no passo <tt>k=3</tt>, bastaria fazer:
|
|
|
+ <center>
|
|
|
+ <tt>pot1 = pot1 * x * x</tt> e <tt>fat1 = fat1 * 5 * 6</tt>,
|
|
|
+ </center>
|
|
|
+ pois dessa forma, conseguiríamos
|
|
|
+ <center><!-- ⇒ = implica -->
|
|
|
+ <tt>pot1 ← pot1 * x * x ⇒ pot1 ← x<sup>4</sup> x<sup>2</sup> = x<sup>6</sup></tt> e
|
|
|
+ <br/>
|
|
|
+ <tt>fat1 ← fat1 * 5 * 6 ⇒ fat1 ← 4! * 5 * 6 = 6!</tt>.
|
|
|
+ </center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Outro problema da solução da tabela 1 é que potência e fatorial crescem muito rápido
|
|
|
+ (se <i>x > 1</i>, mas se <i>x < 1</i>, então decresce muito rápido).
|
|
|
+ Isso também pode resultar ineficiência numérica.
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Portanto, esta solução 1 é muito ineficaz, tanto em termos de velocidade quanto de precisão.
|
|
|
+ A discussão da razão de sua ineficácia já indica um caminho de melhoria, examinado a seguir.
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="ingenuo">
|
|
|
+<p class="secao">Uma segunda solução para a função <i>cosseno</i>: ainda ineficaz numericamente</p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+Uma solução um pouco melhor, mas numericamente ainda ingênua, seria eliminar a grande ineficiência citada acima, a cada passo
|
|
|
+aproveitando a potência e o fatorial anterior.
|
|
|
+Usando como critério de parada que as diferenças entre o cômputo do termo da série seja menor que um dado limiar <i>Lim</i>,
|
|
|
+o código pode ficar como o mostrado abaixo. Suporemos a existëncia de uma função que devolve o módulo de um valor "flutuante", de nome <i>valor_absoluto</i>:
|
|
|
+<center>
|
|
|
+<a name="solucaomedia">
|
|
|
+<i>Tab. 2. Uma implementação não tão ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
|
|
|
+</a>
|
|
|
+<br/>
|
|
|
+<table class="tbCodeLinCol">
|
|
|
+ <tr class="tbCodeLinColH"><th colspan="2">Algoritmo ineficiente para cômputo do cosseno</th></tr>
|
|
|
+ <tr><th>C </th> <th>Python</th></tr>
|
|
|
+ <tr valign="top"><td><table class="tbCode">
|
|
|
+ <tr><td><pre><verm>float</verm> cossInef (<verm>float</verm> x) {
|
|
|
+ <verm>float</verm> pot = 1, soma = 0;
|
|
|
+ <verm>float</verm> termo0 = 2, termo = 1;
|
|
|
+ <verm>int</verm> fat = 1, i = 1, sinal = 1;
|
|
|
+ while (valor_absoluto(termo0-termo)>Lim) {
|
|
|
+ soma = soma + termo;
|
|
|
+ pot *= x * x; <cyan>// compute potencia 2i de x</cyan>
|
|
|
+ fat *= (i+1) * (i+2); <cyan>// compute fatorial de 2i</cyan>
|
|
|
+ termo0 = termo;
|
|
|
+ termo = sinal * pot / fat;
|
|
|
+ i += 2;
|
|
|
+ sinal = -sinal;
|
|
|
+ }
|
|
|
+ return soma;
|
|
|
+ }</pre></td></tr>
|
|
|
+ </table></td>
|
|
|
+ <td><table class="tbCode"><pre><verm>def</verm> cossInef (x) :
|
|
|
+ pot = 1; soma = 0;
|
|
|
+ termo0 = 2; termo = 1;
|
|
|
+ fat = 1; i = 0; sinal = 1;
|
|
|
+ while (valor_absoluto(termo0-termo)>Lim) :
|
|
|
+ soma = soma + termo;
|
|
|
+ pot *= x * x; <cyan># compute potencia 2i de x</cyan>
|
|
|
+ fat *= (i+1) * (i+2); <cyan># compute fatorial de 2i</cyan>
|
|
|
+ termo0 = termo;
|
|
|
+ termo = sinal * pot / fat;
|
|
|
+ i += 2;
|
|
|
+ sinal = -sinal;
|
|
|
+ return soma;</pre></td></tr>
|
|
|
+ </table></td></tr>
|
|
|
+</table></center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+Rodando este algoritmo e comparando-o com a implementação interna das linguagens <b>C</b> e <b>Python</b>, mesmo para valores
|
|
|
+próximos de zero (como <i>0.1</i>) nota-se uma diferença significativa de <i>0.01</i> (veja a tabela abaixo onde comparamos o algoritmo
|
|
|
+acima, sua versão mais eficiente e a implementação interna da linguagem).
|
|
|
+Isso indica que o método acima <b>não</b> é eficiente.
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+Analisando-se o algoritmo, percebe-se que o potencial problema é que a variável <i>termo</i> é resultado da divisão de 2 valores que tem
|
|
|
+tem potencial de crescimento enorme (as variáveis <i>pot</i> e <i>fat</i> - com valor <i>x</i> próximo de zero, <i>pot</i> na verdade
|
|
|
+aproxima-se muito rápido é do zero).
|
|
|
+<p>
|
|
|
+
|
|
|
+</p>
|
|
|
+Outra observação importante: a diferença entre 2 passos no laço para a variável <i>termo</i> é multiplicar por <i>x * x / (i+1)*(i+2)</i>.
|
|
|
+Assim, poderia-se tentar a cada passo multiplicar diretamente por esta "diferença" e não acumular a potência e o fatorial!
|
|
|
+</p>
|
|
|
+
|
|
|
+
|
|
|
+<a name="eficiente">
|
|
|
+<p class="secao">Uma solução eficiente para a função <i>cosseno</i></p>
|
|
|
+</a>
|
|
|
+
|
|
|
+<p>
|
|
|
+Usando esta ideia produz-se o algoritmo seguinte que é bem mais eficiente. Nele aproveitamos para ilustrar o uso
|
|
|
+de medição de tempo de execução tanto em <b>C</b> quanto em <b>Python</b>,
|
|
|
+<center>
|
|
|
+<a name="solucaoboa">
|
|
|
+<i>Tab. 3. Uma implementação bem eficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
|
|
|
+</a>
|
|
|
+<br/>
|
|
|
+<table class="tbCodeLinCol">
|
|
|
+ <tr class="tbCodeLinColH"><th colspan="2">Algoritmo eficiente para cômputo do cosseno - comparação com o ineficiente e o interno</th></tr>
|
|
|
+ <tr><th>C </th> <th>Python</th></tr>
|
|
|
+ <tr valign="top"><td><table class="tbCode">
|
|
|
+ <tr><td><pre><cyan>// Leo^nidas O. Brandao - 2017/06/19</cyan>
|
|
|
+<cyan>// cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
|
|
|
+<cyan>// = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
|
|
|
+<cyan>// gcc -lm -o introducao_eficiencia_algoritmos_cosseno introducao_eficiencia_algoritmos_cosseno.c</cyan>
|
|
|
+
|
|
|
+<incl1>#include</incl1> <stdio.h>
|
|
|
+<incl1>#include</incl1> <math.h> <cyan>// cos(x)</cyan>
|
|
|
+<incl1>#include</incl1> <time.h> <cyan>// clock_t, clock()</cyan>
|
|
|
+
|
|
|
+<def>#define</def> Lim 0.001
|
|
|
+
|
|
|
+<verm>float</verm> valor_absoluto (<verm>float</verm> x) {
|
|
|
+ if (x>=0) return x;
|
|
|
+ return -x;
|
|
|
+ }
|
|
|
+
|
|
|
+<cyan>// Cosseno implementado de modo ineficiente</cyan>
|
|
|
+<verm>float</verm> cossInef (<verm>float</verm> x) { ... } <cyan>// copiar aqui o codigo C do ineficiente acima</cyan>
|
|
|
+
|
|
|
+<cyan>// Cosseno implementado de modo mais eficiente</cyan>
|
|
|
+<verm>float</verm> cossEfic (<verm>float</verm> x) {
|
|
|
+ <verm>float</verm> pot = 1, soma = 0;
|
|
|
+ <verm>float</verm> termo0 = 2, termo = 1;
|
|
|
+ <verm>int</verm> i = 0, sinal = 1;
|
|
|
+ while (valor_absoluto(termo0-termo)>Lim) {
|
|
|
+ soma = soma + termo;
|
|
|
+ termo0 = termo;
|
|
|
+ termo *= -1 * x * x / ((i+1) * (i+2)); <cyan>// compute potencia 2i de x e fatorial de 2i</cyan>
|
|
|
+ i += 2;
|
|
|
+ }
|
|
|
+ return soma;
|
|
|
+ }
|
|
|
+
|
|
|
+<verm>int</verm> main (void) {
|
|
|
+ <verm>float</verm> x, cosx, auxcI, auxcE;
|
|
|
+ <verm>int</verm> i;
|
|
|
+ clock_t tempo_inicial = clock(); <cyan>// "dispara o cronometro"...</cyan>
|
|
|
+ <verd>printf</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.\n");
|
|
|
+ x = 0.0;
|
|
|
+ for (i=0; i<10; i++) {
|
|
|
+ cosx = cos(x);
|
|
|
+ auxcI = cossInef(x);
|
|
|
+ auxcE = cossEfic(x);
|
|
|
+ <verd>printf</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f\n",
|
|
|
+ x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx));
|
|
|
+ x += 0.05;
|
|
|
+ }
|
|
|
+ clock_t tempo_final = clock();
|
|
|
+ <verd>printf</verd>("Tempo em segundos: %f\n", (double)(tempo_final - tempo_inicial) / CLOCKS_PER_SEC);
|
|
|
+
|
|
|
+ return 1;
|
|
|
+ }</pre></td></tr>
|
|
|
+ </table></td>
|
|
|
+ <td><table class="tbCode"><pre><cyan># Leo^nidas O. Brandao - 2017/06/19</cyan>
|
|
|
+<cyan># cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
|
|
|
+<cyan># = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
|
|
|
+
|
|
|
+<cyan># Invocar execucao com linha de comando: python introducao_eficiencia_algoritmos_cosseno.py</cyan>
|
|
|
+
|
|
|
+<incl1>import</incl1> math; <cyan># para cos(x)</cyan>
|
|
|
+<incl1>import</incl1> time; <cyan># para tempo 'time.time()'</cyan>
|
|
|
+
|
|
|
+Lim = 0.001
|
|
|
+
|
|
|
+<verm>def</verm> valor_absoluto (x) :
|
|
|
+ if (x>=0) : return x;
|
|
|
+ return -x;
|
|
|
+
|
|
|
+<cyan># Cosseno implementado de modo mais eficiente</cyan>
|
|
|
+<verm>def</verm> cossInef (x) : <cyan># Copiar aqui a implementacao de cosseno ineficiente</cyan>
|
|
|
+ ...
|
|
|
+
|
|
|
+<cyan># Cosseno implementado de modo mais eficiente</cyan>
|
|
|
+<verm>def</verm> cossEfic (x) :
|
|
|
+ pot = 1; soma = 0;
|
|
|
+ termo0 = 2; termo = 1;
|
|
|
+ i = 0; sinal = 1;
|
|
|
+ while (valor_absoluto(termo0-termo)>Lim) :
|
|
|
+ soma = soma + termo;
|
|
|
+ termo0 = termo;
|
|
|
+ termo *= -1 * x * x / ((i+1) * (i+2)); <cyan># compute potencia 2i de x e fatorial de 2i</cyan>
|
|
|
+ i += 2;
|
|
|
+ return soma;
|
|
|
+
|
|
|
+<verm>def</verm> main () :
|
|
|
+ tempo_inicial = time.time(); <cyan># "dispara o cronometro"...</cyan>
|
|
|
+ <verd>print</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.");
|
|
|
+ x = 0.0;
|
|
|
+ for i in range(0, 10) :
|
|
|
+ cosx = math.cos(x);
|
|
|
+ auxcI = cossInef(x);
|
|
|
+ auxcE = cossEfic(x);
|
|
|
+ <verd>print</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f" %
|
|
|
+ ( x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx) ));
|
|
|
+ x += 0.05;
|
|
|
+
|
|
|
+ tempo_final = time.time();
|
|
|
+ <verd>print</verd>("Tempo em segundos: %f\n" % (tempo_final - tempo_inicial));
|
|
|
+
|
|
|
+main();</pre></td></tr>
|
|
|
+ </table></td></tr>
|
|
|
+</table></center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Experimente rodar em sua máquina o código acima (completanto com o código da função <i>cossInef</i>, implementada na tab. 1).
|
|
|
+ Na máquina usada em 2017 para o primeiro teste desse código, tanto rodando <i>C</i> quanto <i>Python</i>, produzem como resultado a tabela abaixo.
|
|
|
+ Note que a medida
|
|
|
+ que o <i>x</i> se afasta da origem, aparece mais erro, entretanto os erros são consistentemente maiores ao usar
|
|
|
+ a implementação ingênua.
|
|
|
+
|
|
|
+<center>
|
|
|
+ <i>Tab. 4. As saídas geradas pelos códigos ineficientes e eficientes (da tabela 3) para aproximar <i>cosseno</i>.</i>
|
|
|
+ <pre> x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.
|
|
|
+ 0.00 | 1.0000 | 1.0000 | 1.0000 | 0.0000 | 0.0000
|
|
|
+ 0.05 | 1.0012 | 0.9988 | 0.9988 | 0.0025 | 0.0000
|
|
|
+ 0.10 | 1.0050 | 0.9950 | 0.9950 | 0.0100 | 0.0000
|
|
|
+ 0.15 | 1.0112 | 0.9888 | 0.9888 | 0.0225 | 0.0000
|
|
|
+ 0.20 | 1.0199 | 0.9801 | 0.9801 | 0.0399 | 0.0000
|
|
|
+ 0.25 | 1.0311 | 0.9689 | 0.9689 | 0.0622 | 0.0000
|
|
|
+ 0.30 | 1.0447 | 0.9553 | 0.9553 | 0.0893 | 0.0000
|
|
|
+ 0.35 | 1.0606 | 0.9394 | 0.9394 | 0.1213 | 0.0000
|
|
|
+ 0.40 | 1.0789 | 0.9211 | 0.9211 | 0.1579 | 0.0000
|
|
|
+ 0.45 | 1.0996 | 0.9004 | 0.9004 | 0.1991 | 0.0000</pre></center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Os resultados numéricos são os mesmos comparando o executável a partir do código <i>C</i> ou a interpretação do <i>Python</i>,
|
|
|
+ Entretanto, os tempos de execução diferem,
|
|
|
+ sendo significativamente menores utilizando a versão compilada (compilador <b>Gnu GCC</b>).
|
|
|
+ Usando a mesma máquina (um PC com 2 processadores AMD Phenom II rodando Linux/Debian 8.8),
|
|
|
+ os menores resultados de tempo de execução (dentre 100 testes) para o executável via <i>C</i> e o interpretado
|
|
|
+ <i>Python</i>: <center><pre>
|
|
|
+ C Python
|
|
|
+Tempo em segundos: 0.000079 0.000220</pre></center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<p>
|
|
|
+ Em novos testes, desta vez usando um computador de dois núcleos com processador <i>Intel Core i5-6400 CPU @ 2.70GHz</i>,
|
|
|
+ com sistema <i>Debian GNU/Linux 9.12</i>, rodando 400 vezes, eliminando-se as linhas com comando de impressão
|
|
|
+ (pois as saídas poderiam uniformizar os resultados por serem significativamente mais lentas),
|
|
|
+ os resultados numéricos do executável (compilação <i>Gnu GCCC</i>) mostra um bastante superior ao interpretador <i>Python</i>.
|
|
|
+ Os tempos médios das 400 rodadas, do menor tempo de execução e do maior tempo, para a versão compilada e interpretada foram
|
|
|
+ (em segundos):
|
|
|
+ <center><pre>
|
|
|
+ C Python
|
|
|
+Tempo medio: 0.000520 0.008276
|
|
|
+Tempo minimo: 0.000016 0.000076
|
|
|
+Tempo maximo: 0.000945 0.016031</pre></center>
|
|
|
+</p>
|
|
|
+
|
|
|
+<!--
|
|
|
+2020/08/13 - /proc/cpuinfo = 2 x Intel(R) Core(TM) i5-6400 CPU @ 2.70GHz
|
|
|
+C
|
|
|
+-->
|
|
|
+
|
|
|
+
|
|
|
+<p>
|
|
|
+Este é apenas um resumo para apresentar o assunto de eficiência de algoritmos, seu aprofundamente demanda muito mais tempo.
|
|
|
+Por exemplo, no IME-USP existe um curso (usualmente de segundo ano) cujo objeto de estudo é precisamente
|
|
|
+discutir efeciência de algoritmos, o curso
|
|
|
+<a href="http://uspdigital.usp.br/jupiterweb/obterDisciplina?sgldis=MAC0122&verdis=2"
|
|
|
+ title="ver ementa">MAC0122 - Princípios de Desenvolvimento de Algoritmos</a>.
|
|
|
+No programa de Verão do IME-USP temos o curso
|
|
|
+<a href="http://www.ime.usp.br/verao/index.php/turmas/apresentacao.php"
|
|
|
+ title="ver lista de cursos do ultimo Verao">Tópicos de Programação</a>.
|
|
|
+</p>
|
|
|
+
|
|
|
+ <p class="autoria">
|
|
|
+ <a href="https://www.ime.usp.br/~leo" target="_blank" title="seguir para a pagina do prof. Leônidas">Leônidas de Oliveira Brandão</a><br/>
|
|
|
+ <a href="http://www.ime.usp.br/~leo" target="_blank" title="seguir para a página do LInE">http://line.ime.usp.br</a>
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <p class="rodape">
|
|
|
+ <b>Alterações</b>:<br/>
|
|
|
+ 2020/08/15: novo formato, pequenas revisões<br/>
|
|
|
+ 2020/08/13: ampla revisão (introdução nova, com imagem), novo formato;<br/>
|
|
|
+ 2019/04/24: primeira versão
|
|
|
+ </p>
|
|
|
+
|
|
|
+
|
|
|
+</div>
|