Recursão

A recursão é uma das técnicas mais simples e úteis que existem para usarmos em nossos algoritmos. Consiste em uma função (denominada recursiva) chamar a si mesmo, até que o retorno seja trivial. Resolvi abordá-la aqui porque alguns algoritmos que estudaremos mais para frente usam funções recursivas.

Gostaria de, antes de falar sobre o assunto, contar um pouco da minha história com recursão, porque foi meu início no mundo dos algoritmos.

Junho de 2004, tinha eu 13 anos e fui premiado com medalha de ouro na modalidade iniciação da Olimpíada de Informática. Fui convidado para o curso de introdução a programação na UNICAMP e, extremamente geek como eu era (e ainda sou), falei para os professores que já programava em C e então eles sugeriram que eu fosse para o curso de programação avançada.

No entanto, eu ainda achava muito complicado os algoritmos da modalidade de programação da OBI, por isso pedi pra ficar num “meio-termo”. Eles toparam numa boa e com isso passei a ter aulas com um monitor excelente (aluno da UNICAMP) chamado Ribamar. Esse cara foi extremamente importante para minha iniciação na programação de verdade.

Na primeira tarde que tive aula com ele, ele perguntou se eu sabia o que era recursão. Respondi que não e, a partir daquele dia e depois de ele me ensinar também de grafos e programação dinâmica, além de me apresentar o Slackware, me tornei um amante de algoritmos. A recursão, portanto, mesmo sendo algo simples, é um assunto especial pra mim porque representa a mudança de “nível”.

Então, mãos à obra!


Em matemática, o número fatorial de nn é igual a: n×n1×n2×2×1n \times{} n-1 \times{} n-2 \times{} \ldots{} 2 \times{} 1.

Logo, por exemplo, 5!5! (cinco fatorial) seria igual a: 5×4×3×2×1=1205 \times{} 4 \times{} 3 \times{} 2 \times{} 1 = 120.


Um exemplo bom e simples de recursão é um algoritmo para determinar números fatoriais:

função fatorial (n)
se n=1n = 1, então
  retorna 11
senão
  retorna n×fatorial(n1)n \times{} fatorial(n-1)
fim-se
fim-função

Domínio de nossa função: nN/n1n \in{} \mathbf{N} / n \geq{} 1.

Qual o custo desse algoritmo?

Vamos abrir um grande parênteses aqui até a próxima linha horizontal para descobrir qual o custo do nosso algoritmo antes de continuar com a conversa sobre recursão e relembrar/reforçar o post sobre Análise de Algoritmos. Vou colocar o número de vezes que cada instrução é executada, usando o esquema que será o padrão para as próximas vezes que veremos custos:

Número da linha: Número de vezes que é executada..

  1. nn
  2. n1n-1
  3. 11
  4. n1n-1
  5. n1n-1

T(n)=(n)+(n1)+(1)+(n1)+(n1)=4n2T(n) = (n) + (n-1) + (1) + (n-1) + (n-1) = 4n - 2

Uma função linear, o tipo de algoritmo mais simples que podemos encontrar, com excessão dos que são uma função constante. Mas o parênteses na verdade não serviu só pra isso. Eu queria aproveitar pra escovar uns bits de nosso código. Você percebeu que o primeiro condicional é executado n1n-1 vezes, mas só entramos nele uma vez? Então vamos inverter nosso condicional.

função fatorial (n)
se n2n \geq{} 2, então
  retorna n×fatorial(n1)n \times{} fatorial(n-1)
senão
  retorna 11
fim-se
fim-função

Novo custo

  1. nn
  2. n1n-1
  3. n1n-1
  4. 11
  5. 11

T(n)=(n)+(n1)+(n1)+(1)+(1)=3nT(n) = (n) + (n-1) + (n-1) + (1) + (1) = 3n

Claro que continua uma função linear, não houve nenhuma grande mudança. Os dois continuam com a mesma ordem de crescimento e tal… 4n+24n+2 comparado com 3n3n é uma diferença pequena, mas essa solução ficou bem mais elegante. ;) Poxa, diminuímos o custo do algoritmo em 14\frac{1}{4}! Hehehe…

Agora, antes de continuar, só vamos definir a notação assintótica desse nosso novo custo!

A fórmula do Θ\Theta{} é: 0c_1g(n)f(n)c_2g(n)0 \leq{} c\_{1} g(n) \leq{} f(n) \leq{} c\_{2} g(n)

Substituindo pela nossa função, temos: c_1n3nc_2nc\_{1}n \leq{} 3n \leq{} c\_{2}n. É trivial, que podemos escolher para as duas constantes c_1=c_2=3c\_{1}=c\_{2}=3 e para n_0=0n\_{0}=0. Com isso pretendi mostrar-lhes uma conclusão óbvia que no outro artigo não tinha mostrado para não complicar muito: uma função reta (linear) pertence sempre a notação Θ(n)\Theta{}(n) e uma função quadrática pertence sempre a notação Θ(n2)\Theta{}(n^{2}) (ora, façam um gráfico das funções e vejam se isso não é óbvio!). Mas vamos aprendendo mais sobre análise de algoritmos com o tempo…


Bom… Continuando com a recursão… Nossa função cria um loop consigo mesma, ao invés de usar um para (for) ou enquanto (while). Ela se repete diminuindo um de nn a cada iteração, até que chegue ao valor mínimo 11 aonde a resposta é trivial: 11.

O que é necessário para criarmos uma recursão? Apenas um ponto de parada! Temos que tomar cuidado para não criarmos loops infinitos cuidando sempre com cada entrada que o usuário pode colocar. Nesse caso, eu determinei que o domínio da função é nN/n1n \in{} \mathbf{N} / n \geq{} 1. Se o cara colocasse n=0n=0, minha função iria diminuindo infinitamente… e nunca iria retornar nada!

Para fazer a recursão portanto, precisamos analisar o domínio de nossa função e mais: precisamos conhecer um valor (que vai ser o limite; no caso do fatorial, o valor que sabíamos é que 1!=11! = 1).

Acredito que vocês tenham achado tudo simples e que não tenham problema com isso. Funções recursivas vão ser extremamente úteis para nós nos próximos artigos. Vou finalizar mostrando-lhes alguns casos básicos de algoritmos em que podemos usar a recursão:

Números de Fibonacci

função fibonacci (n)
se n3n \geq{} 3, então
  retorna fibonacci(n1)+fibonacci(n2)fibonacci(n-1) + fibonacci(n-2)
senão
  retorna 11
fim-se
fim-função

Domínio de fibonacci(n): nN/n1n \in{} N / n \geq{} 1

Depois descobriremos como calcular os números de Fibonacci mais rápido, mas por enquanto nosso objetivo é a recursão!

Substituir um loop

Vamos supor que você quer imprimir os números de n a 1 e esqueceu a sintaxe do para… :D

função imprimeate_ (n)
imprima nn
se n>1n>1, então
  imprime_ate(n-1)
fim-se
fim-função

Domínio de imprime_ate(n): nN/n1n \in{} N / n \geq{} 1

Todo loop pode ser uma recursão e tem alguns que ficam bem mais fáceis se forem! Nesse caso, é claro que seria mais simples usarmos um para!

Outros exemplos, para concluir

  • Ordenação por Intercalação (Merge Sort)
  • Busca em Profundidade (Depth-First Search) em Grafos
  • Union/Find
  • … entre vários outros casos!

Análise de Algoritmos

Analisar um algoritmo é prever o que o algoritmo irá precisar. Às vezes o hardware é importante, mas acho que o que acontece com mais freqüência, ao menos em olimpíadas, maratonas e problemas em casa, é precisarmos medir o tempo que ele irá demorar.

Eu expliquei em algum dos artigos anteriores que o tempo de um algoritmo depende geralmente do tamanho de sua entrada. Com este artigo, pretendo explicar como analisamos um algoritmo baseado nesse tamanho de sua entrada para compará-lo com outros algoritmos e ter uma noção de quanto tempo ele vai demorar.

Para o entendimento ficar mais fácil, vamos partir do seguinte algoritmo (que vamos chamar de Algoritmo 1):

para i \leftarrow{} 1 até n, faça
para j \leftarrow{} 1 até i, faça
  imprima i ×\times{} j ×\times{} n
fim-para
fim-para

O que este algoritmo faz é, depois de receber a entrada nn do usuário, imprimir o produto de nn com todos dois números ii e jj, tal que jinj \leq{} i \leq{} n.

Para medir o custo do algoritmo, nossa análise consistirá em ver quantas vezes cada passo é executado. Mediremos o custo de cada linha (cada passo a ser executado), sempre em função de n, que para este algoritmo é a variável mais importante (aliás, a única variável). Por isso o pseudocódigo do Algoritmo 1 está com suas linhas numeradas. Vamos analisar…

  • Linha 1: Será executada n+1n + 1 vezes.
  • Linha 2: Será executada n×_i=1n+nn \times{} \sum\_{i=1}^{n} + n vezes.
  • Linha 3: Será executada n×_i=1nn \times{} \sum\_{i=1}^{n} vezes.
  • Linhas 4 e 5: Não tem custo. :)

Por que estes números de execução?

Se você já entendeu por que cada passo é executado este número de vezes, pode pular essa parte (continuar a ler a partir da linha horizontal).

Linha 1

O loop para voltará para si mesmo nn vezes, isso é, testará novamente sua condicional e incrementará um. Por sempre testar um condicional, no final ele terá que testar novamente para dizer que já passou de nn. Por isso, ele será executado n+1n+1 vezes, ao invés de simplesmente nn.

Linha 2

Este loop para será executado um número de vezes variável (ii), que irá de 11 a nn. Portanto, ele será executado duas vezes (1 mais “o último condicional”) no primeiro loop de ii, três (2 mais “o último condicional”) no segundo, e por aí vai. Com isso, ele será executado o número de vezes que equivale a soma de 11 a nn, mais nn que são “os últimos condicionais”.

Linha 3

Exatamente o mesmo número que a Linha 2, mas sem “os últimos condicionais” (n-n).


Imprimir algo na tela pode demorar mais do que fazer uma operação, mas a análise de algoritmos é uma coisa bem rústica. Desprezamos todas as constantes, com isso só levando a sério a informação importante: neste caso, apenas nn. Então agora, vamos escrever o tempo de execução do algoritmo, que é a soma dos tempos de execução para cada instrução executada.

T(n)=(n+1)+(_i=1n+n)+(_i=1n)T(n) = (n + 1) + (\sum\_{i=1}^{n} + n) + (\sum\_{i=1}^{n})

Os parênteses não são necessários, mas coloquei para ajudar na visualização separando o custo de cada instrução.

Simplificando esta operação, teremos:

T(n)=n2+3nT(n) = n^{2} + 3n, uma função quadrática.

Ordem de Crescimento

Como eu já disse antes, descobrir o custo de um algoritmo é uma coisa feita sem precisão, porque para entradas realmente grandes (que são casos onde precisamos do computador!) as constantes não importam. Agora vamos determinar a ordem de crescimento de um algoritmo resgatando do nosso algoritmo apenas o valor mais importante, o maior expoente de nn nele, neste caso, n2n^{2}. Se tivéssemos 2n22 n^{2}, por exemplo, também usaríamos apenas n2n^{2} porque o 22 que multiplica também é desprezível!

Vamos agora aprender como representar o custo desse algoritmo usando notações assintóticas com a ordem de crescimento do algoritmo.

Se você não entendeu alguma coisa aí em cima, sugiro reler antes de continuar…

Notações Assintóticas

Sugestão

Principalmente para pessoas pouco habituadas com matemática, essa parte é difícil e cansativa. Quando eu comecei a aprender isto, talvez por causa da matemática tão básica que é ensinada na escola, eu não entendia nada… Mas só quero dar uma dica: se você não entender direito ou achar muito complicado, pule para a próxima linha horizontal ao invés de desistir e dizer que “algoritmos são muito difíceis”. Tentei fazer o artigo para você poder pular essa parte e mesmo assim não parar no estudo dos algoritmos… Depois, com o tempo, você vai aprendendo isso.

As notações que usamos para descrever o tempo de execução de um algoritmo são cinco:

  • Θ\Theta{}
  • OO
  • Ω\Omega{}
  • oo
  • ω\omega{}

Embora essas notações sejam conjuntos, usamos o sinal de igualdade (=) para expressar que f(n)f(n) pertence a algum deles, ao invés de usar o sinal de pertinência (\in{}).

Vou explicá-las, omitindo alguns fatos para tentar facilitar o entendimento, porque eu acho que analisar algoritmos é meio complicado e nessa parte é extremamente difícil ser didático. Mas se você realmente se interessar, você pode me enviar um comentário pedindo mais um artigo sobre isso (e eu terei o prazer de até pesquisar para informar-lhes mais) ou então leia o Capítulo 3 do livro Algoritmos: Teoria e Prática, que acredito que seja bem completo. Gostaria de enfatizar aqui que meu objetivo com essa série é tornar uma introdução a algoritmos simples e não ser uma referência, como é o objetivo, por exemplo, do livro do Cormen [et al].

A notação Θ\Theta{}

Lê-se “theta de gê de ene”.

Θ(g(n))=f(n)\Theta{}(g(n)) = f(n), se existem constantes positivas c_1c\_{1}, c_2c\_{2} e n_0n\_{0} tais que 0c_1g(n)f(n)c_2g(n)0 \leq{} c\_{1} g(n) \leq{} f(n) \leq{} c\_{2} g(n) para todo nn_0n \geq{} n\_{0}.

A notação OO

Lê-se “ó maiúsculo de gê de ene”. Para quando há apenas um limite assintótico superior.

O(g(n))=f(n)O(g(n)) = f(n), se existem constantes positivas cc e n_0n\_{0} tais que 0f(n)cg(n)0 \leq{} f(n) \leq{} cg(n) para todo nn_0n \geq{} n\_{0}.

A notação Ω\Omega{}

Lê-se “omega maiúsculo de gê de ene”. Para quando há apenas um limite assintótico inferior.

Ω(g(n))=f(n)\Omega{}(g(n)) = f(n), se existem constantes positivas cc e n_0n\_{0} tais que 0cg(n)f(n)0 \leq{} cg(n) \leq{} f(n) para todo nn_0n \geq{} n\_{0}.

A notação oo

Lê-se “ó minúsculo de gê de ene”. Para quando há apenas um limite assintótico superior, sem permitir que f(n)=cg(n)f(n) = cg(n). Utiliza-se a notação oo para denotar um limite superior que não é assintoticamente restrito.

o(g(n))=f(n)o(g(n)) = f(n), se para qualquer constante c>0c > 0, existe uma constante n_0>0n\_{0} > 0 tal que 0f(n)cg(n)0 \leq{} f(n) \leq{} cg(n) para todo nn_0n \geq{} n\_{0}.

A notação ω\omega{}

Lê-se “omega minúsculo de gê de ene”. Para quando há apenas um limite assintótico inferior, sem permitir que cg(n)=f(n)cg(n) = f(n). Utiliza-se a notação ω\omega{} para denotar um limite inferior que não é assintoticamente restrito.

ω(g(n))=f(n)\omega{}(g(n)) = f(n), se para qualquer constante c>0c > 0, existe uma constante n_0>0n\_{0} > 0 tal que 0cg(n)f(n)0 \leq{} cg(n) \leq{} f(n) para todo nn_0n \geq{} n\_{0}.

Para analisar problemas mais complexos como, por exemplo, recorrências, existem métodos bastante interessantes, como o Teorema Mestre que o Cormen apresenta no Capítulo 4. É uma boa leitura pra quem se interessou.


Podemos criar várias comparações entre estas funções, mas isto não vem ao caso. O importante é saber em que notação a nossa função se encontra. Com o tempo vamos compreendendo melhor essas fórmulas.

Vamos relembrar o custo de nosso algoritmo… T(n)=n2+3nT(n) = n^{2} + 3n.

Vamos ver em que notação ele pode se encaixar, sabendo que g(n)g(n) seria a ordem de crescimento (parte importante) do nosso custo; no caso, n2n^{2}.

Testamos primeiro se ele encaixa na função Θ(n2)\Theta{}(n^{2}). Vamos substituir f(n)f(n) e g(n)g(n) (naquela função ali em cima, onde diz A notação Θ\Theta{}) pelos valores que conhecemos.

c_1n2n2+3nc_2n2c\_{1}n^{2} \leq{} n^{2} + 3 n \leq{} c\_{2} n^{2}

Se dividirmos tudo por n2n^{2}, obteremos:

c_11+3nc_2c\_{1} \leq{} 1 + \frac{3}{n} \leq{} c\_{2}

Agora separaremos as inequações.

Inequação 1: c_11+3nc\_{1} \leq{} 1 + \frac{3}{n}

Inequação 2: 1+3nc_21 + \frac{3}{n} \leq{} c\_{2}

Para satisfazer a Inequação 1, podemos quase automaticamente perceber que para qualquer n1n \geq{} 1, é válido c_1=1c\_{1} = 1 (ora, por mais que 3n\frac{3}{n} chegue perto de 0, sempre ainda vamos ter a constante 1 adicionada a ele). Para satisfazer a Inequação 2, podemos perceber facilmente que para qualquer n1n \geq{} 1, é válido c_2=4c\_{2} = 4 (a função só tende a diminuir a partir que nn vai aumentando e com n=1n=1, c_2=4c\_{2}=4). Com isso, agora chegamos as três constantes que precisávamos.

n_0n\_{0} (o menor valor de nn) =1= 1; c_1=1c\_{1} = 1; c_2=4c\_{2} = 4.

Logo, concluímos que f(n)=n2+3n=Θ(n2)f(n) = n^{2} + 3n = \Theta{}(n^{2}). Uma função que pertence a Θ\Theta{}, tem um limite assintótico superior e inferior e, portanto, pertenceria também a O(n2)O(n^{2}) e Ω(n2)\Omega{}(n^{2}), mas nem é necessário testar os outros valores porque já identificamos nossa função como “theta de ene ao quadrado”, que é a função mais “retinha” que podemos esperar.

Bom… Nos próximos artigos, veremos que um mesmo problema pode ter várias soluções diferentes com custos ainda mais diferentes! Por isso, é crucial sabermos analisar, mesmo que por cima, qual o algoritmo que é mais eficiente. Vou ficando por aqui…

Sobre as mudanças

Estou em Florianópolis e passarei aqui mais uma semana, o que é excelente. Agora estou na casa da minha tia, que, além de ficar do lado da praia, tem ADSL (e uma placa de rede sobrando no computador para o laptop aproveitar a internet… :))

Eu planejava postar um artigo por dia da Série Algoritmos, mas o artigo que estou escrevendo agora é a parte de algoritmos que eu considero a mais complicada (muito mais complicada que os algoritmos em si que vamos estudar depois): análise de algoritmos. E estou fazendo um esforço para deixar a parte matemática bem leve e introduzir essa matéria de maneira bem simples, por isso a demora.

Aí eu resolvi escrever um artigo inteiro só pra comentar sobre as mudanças no blog.

Leitores do meu feed, me desculpem pelas várias atualizações de ontem. Além de mudar a estrutura da página (tirando, por exemplo, os comentários da página inicial), eu estou reorganizando os artigos a partir de agora, separando-os em categorias (cada um com só uma categoria) e usando o plugin Simple Tags para a tagsonomia.

Se vocês observarem, incluí links para a validação de WAI e da Section 508 na minha sidebar. Meu site tá bem acessível com esse novo design e estrutura. :) Além disso, enviei-o para algumas galerias e por isso as visitas estão mais em alta do que já estavam antes nessa semana.

Hmmm… Para os que ainda não perceberam, troquei as antigas páginas Biografia e Portifólio por apenas uma seção, denominada Currículo. Essa mudança foi para que eu não precisasse de grandes atualizações em textos (o que é uma coisa muito chata de fazer!) e ter minhas informações profissionais separadas em um só lugar. Embora eu não tenha quase experiência alguma (quer dizer, experiência até eu tenho, mas não tenho cursos), espero que o currículo também me ajude com os negócios.

Adicionei um link para meus feeds no Bloglines também e dei uma arrumada na seção de problemas lógicos.

Espero que estejam gostando das mudanças… Agora estou me preparando para mudanças mais radicais, como transformar o site inteiro em Ajax (o que não é difícil no WordPress, mas vou esperar no mínimo esperar a série de algoritmos pra não ficar atrasado com ela)

Gecko vs. Opera

Opera Is Faster, More Secure, More Compliant Than Firefox, afirma o site OS News. Let’s get the facts… Não que eu seja um expert nisso, mas aqui está minha análise sobre o caso Mozilla (Gecko) vs. Opera, sem querer ofender ninguém, nem difamar a imagem do Opera. Mas se eu estiver errado, comentem o contrário! ;)

Vantagens do Opera

Na minha opinião, a única vantagem do Opera é que ele gasta menos memória. Isso é incontestável. Já fiz vários testes e ele é realmente muito bom nesse aspecto. Sobre ser mais rápido, eu não tenho tanta certeza. Se você compilar o seu Firefox para a sua plataforma e configurar usando o about:config eu acho que eles têm uma velocidade semelhante. E além de não ter uma internet estável o suficiente, não sei como fazer o teste de velocidade, de qualquer maneira…

Vantagens do Gecko

Tem bem mais compatibilidade com os padrões web e com DOM do que o Opera. Não vem com um monte de recursos que eu não quero. Existem centenas (ou milhares?) de extensões para eu baixar e que personalizam o navegador para mim. É um software livre. Já baixei várias vezes o código do CVS dele e compilei usando configurações para o meu processador. Eu tenho como criar extensões para ele com o que eu quiser. Eu tenho como ver o código dele (e alterar o que eu quiser); Como é possível ainda assim dizerem que o Opera é mais seguro?


Eu gostaria de acessar o link que o OSNews diz de onde veio a notícia, mas não está funcionando aqui… Sinceramente, pelo menos pra mim, o Firefox vale bem mais a pena. E acho que para os leigos também, que tem um navegador semelhante ao IE (semelhança que eu me refiro é só a posição das coisas!), mais recursos e mais segurança.

De qualquer maneira, cada um usa o que quiser!

Qual a utilidade do algoritmo?

No primeiro artigo desta série, o Hélio comentou: ‘Algoritmos’ é um tema pouco valorizado por muitos programadores iniciantes, que querem logo botar a mão na massa.

É a mais pura verdade. Quando eu tive a minha primeira noção do que são algoritmos (no dia 12 de julho de 2004, no Curso de Programação da Olimpíada Brasileira de Informática na UNICAMP), confesso que fiquei decepcionado. Qual a utilidade de algo tão formal pra algo que já sabemos fazer? Eu já programava em C e achei um saco o professor escrevendo no quadro aqueles pseudocódigos de problemas super simples, perdendo tempo com isso ao invés de programar. Porém, hoje percebo que algoritmos são muito mais legais (e importantes) do que eu pensava. Claro que para somar dois inteiros não há necessidade de escrever um pseudocódigo, mas algoritmos são representações essenciais para problemas mais complexos e grandes aplicações.

Acredito que você que já leu os dois primeiros artigos já deve saber, mas não custa lembrar: algoritmo é a relação entre entrada e saída do programa, é o rascunho do programa, o projeto. E um projeto antes de colocar a mão na massa é indispensável. Enquanto a implementação é a função dos pedreiros, o algoritmo é a função dos engenheiros. Se os engenheiros não existissem, acho que os pedreiros não iam conseguir fazer as casas e os prédios que eles constroem hoje em dia!

Não querendo desmerecer alunos de sistemas de informação, mas a maioria deles não passa de pedreiros.

O algoritmo sempre existe, mesmo que apenas no seu pensamento. A primeira coisa que você pensa quando quer fazer uma aplicação (a não ser que você seja louco) é: o que é a aplicação? O que ela vai fazer? E se você sair implementando uma coisa complexa, você vai se decepcionar depois demorando mais do que o tempo de fazer a implementação só para limpar o código! Por isso, representar algoritmos complexos é essencial.

E mais… Mesmo se você tivesse tempo infinito, memória infinita, etc. e tal (não tivesse necessidade de limpar o código), você vai precisar de um algoritmo pra provar que o seu programa funciona, para se organizar e para estudar a sua lógica. Se você não planejar um algoritmo para o caso acima, qualquer funcionalidade que você queira adicionar no meio, por falta de projeto e organização, vai demorar bem mais tempo. Por isso, algoritmo não é uma perda de tempo antes da programação, mas a programação é que se torna uma perda de tempo quando não teve um algoritmo (ou, um projeto). O livro Algoritmos: Teoria e Prática reforça essa interessante idéia na página 7:

Suponha que os computadores fossem infinitamente rápidos e que a memória do computador fosse livre. Você teria alguma razão para estudar algoritmos? A resposta é sim, se não por outra razão, pelo menos porque você ainda gostaria de demonstrar que o método da sua solução termina, e o faz com a resposta certa.

Outra utilidade do algoritmo é compartilhar idéias de problemas complexos. Todo programador entende um pseudocódigo e por isso convém muitas vezes apresentar sua idéia de um programa em forma de um algoritmo, num pseudocódigo, ao invés de passar um programa implementado em C para uma dúzia de programadores de VBScript (e sem dúvidas é muito melhor do que ter que aprender uma linguagem da Microsoft!!!).

Existe uma série de algoritmos comuns que todo programador deve conhecer (projetos prontos para muitas coisas complicadas que precisamos implementar no dia-a-dia) e isso é o que vamos começar a estudar no próximo artigo. :)

© 2005–2020 Tiago Madeira