1

Algoritmos: por que devemos nos importar?

#Lógica de Programação
P
Paulo Filho

Quando uma pessoa decide entrar de cabeça no mundo do desenvolvimento de software, ela com certeza estará sujeita a um mar de novos conceitos, cada um representado por uma palavra que, talvez, essa pessoa nunca tenha ouvido antes.

Dentro deste “vocabulário próprio do desenvolvimento”, uma palavra de grande importância, que normalmente é ignorada por novas pessoas desenvolvedoras, é "Algoritmo".

Quando começamos a aprender uma habilidade nova, normalmente estamos tão ansiosos em progredir dentro de uma linguagem de programação, que nos esquecemos deste conceito tão fundamental. Mas quem pode culpar o iniciante certo? Quando comparado com a versatilidade do Python, a liberdade do JavaScript ou à performance do C#, parece que o “tempo perdido” aprendendo algoritmo pode ser adiado, talvez até indefinidamente.

Contudo, na verdade, é justamente o contrário: algoritmos são a base fundamental não apenas para as linguagens citadas, mas para programação como um todo. É simplesmente impossível (ou, no máximo, extremamente inviável) que alguém aprenda a desenvolver sem aprender antes algoritmo.

Note que, na frase acima, não foi especificado que estou falando sobre “desenvolver software”, porque na verdade não estou. Estou sim me referindo a qualquer tipo de desenvolvimento, seja ele atrelado ou não a Tecnologia. Isso porque o conceito de algoritmo transcende a Programação, e é muito anterior à ela. Na verdade tudo que fazemos nas nossas vidas é baseado em algoritmos, e veremos neste artigo o motivo.

Logo após a definição de algoritmos, veremos um exemplo simples de algoritmo no nosso dia-a-dia. Depois, faremos um exemplo de um algoritmo em pseudocódigo, apresentando o racional de sua construção, passo a passo.


Afinal de contas, o que é um algoritmo?

Embora exista uma definição formal e acadêmica do que um algoritmo é, vamos tentar abstraí-la por um momento e definir este conceito da maneira mais simples possível:

Algoritmo é uma sequência de passos para se resolver um problema.

E, de fato, parece que ficou bem simples, não é mesmo?

Novamente enfatizo a ausência de qualquer viés tecnológico nessa definição. Isso quer dizer que algoritmos podem ser usados por qualquer pessoa para resolver qualquer problema no seu cotidiano. Vamos exemplificar: suponha que você precise atravessar uma rua movimentada. Pare por um momento e pense em algumas vezes que você tenha realizado essa ação. Provavelmente, ela tenha seguido passos parecidos com os abaixo:

1 - Localizar a faixa de pedestres mais próxima;

2 - Se mover até a faixa;

3 - Olhar para a direita e verificar se está vindo alguma veículo;

4 - Olhar para a esquerda e fazer a mesma verificação;

5 - Em caso de não haver nenhum veículo à caminho, finalmente atravessar a rua com segurança.

Notaram a sequência lógica dos procedimentos para atravessar uma rua? Identificaram algo que vocês já tenham feito? Provavelmente sim, pois este é o algoritmo usado para atravessar a rua.

Poderia aqui listar mais centenas de atividades do cotidiano de uma pessoa que envolvam os algoritmos, mas acho que já deu para pegar a ideia, não é mesmo?

Vamos então falar sobre os elementos principais em um algoritmo.


Elementos Principais de um algoritmo

Novamente esse é um debate teórico bem interessante: não existe um consenso sobre o que é fundamental em um algoritmo e o que deixa de ser. Entretanto, vou falar aqui sobre três elementos principais, um essencial e dois opcionais, mas que praticamente todos os algoritmos possuem.

O primeiro e essencial é a ordenação ou fluxo. Notem que ela vem da própria definição de algoritmo que fizemos, onde falamos sobre uma sequência de passos. Para exemplificar de maneira mais visual, vamos pensar no algoritmo de atravessar a rua. O que acontece se invertermos os passos? Digamos que vamos colocar o passo 4 antes do passo 1, deixando a ordem como 4 - 1 - 2 - 3 - 5.

O que vai acontecer nesse caso é que vamos olhar para a esquerda antes de nos deslocarmos até a faixa de pedestres. Sendo assim, quando chegarmos até a faixa, não sabemos dizer se existe algum veículo vindo da esquerda ou não, pois a informação que temos é do passado.

Outro exemplo bem claro de que a ordenação é relevante está no algoritmo de escovar os dentes. Imagine alguém tentando colocar a pasta de dentes na escova antes de pegar a própria escova. Me parece bem mais difícil de se realizar.

O segundo elemento bastante recorrente nos algoritmos são as estruturas condicionais ou estruturas de decisão. Elas estão fundamentalmente baseadas em condições que devem ser satisfeitas para se executar (ou não) uma determinada ação. No nosso exemplo de atravessar a rua, apenas realizamos essa ação SE nenhum veículo estiver cruzando em direção alguma. SE nós vermos algum veículo vindo em direção à faixa, não atravessaremos a rua.

Assim sendo, vemos que o ato “atravessar a rua” é resultado da condicional “tem algum carro?”. As estruturas condicionais não são obrigatórias dentro de um algoritmo, mas estão presentes na grande maioria deles. Na maioria das linguagens de programação, as condicionais são tratadas pelo comando IF (em português, SE), com estruturas do tipo:


If (condição) {ação}

else {outra ação}


Nesta situação, o algoritmo pode tomar rumos bem diferentes, dependendo do caminho pelo qual ele seguiu. Isso torna as estruturas de decisão extremamente poderosas nos algoritmos.

O terceiro elemento, que também é opcional, mas geralmente está presente, são as estruturas recursivas ou estruturas de repetição. Estas são estruturas que se repetem até uma condição específica ser atingida. Suponha por exemplo que você quer aprender a somar frações e deseja tirar 10 em uma prova neste tópico. Sendo assim, em algum ponto do seu algoritmo de estudo deve haver a linha “Fazer exercícios até acertar todos eles”. Note que a condição que você gostaria de atingir é acertar todos os exercícios e a ação que está sendo repetida é o ato de fazer exercícios.

Estruturas recursivas são muito interessantes para usar em algoritmos de programação, visto que os computadores, em geral, não reclamam de realizar ações repetitivas (a não ser que alguém se esqueça de incrementar o contador...). Todas as linguagens de programação trazem comandos prontos para lidar com este elemento de algoritmo chamados de laços de repetição. Exemplos comuns são o for e o while, que quando usados com sabedoria, evitam complexidade de código e repetição do mesmo.


Exemplo de algoritmo em pseudocódigo

Para dar um exemplo completo dos três principais elementos contidos em um algoritmo, vamos montar, em pseudocódigo, um programa que faça o seguinte:

  • Leia 100 entradas de dois números, um de cada vez;
  • Faça a divisão do primeiro pelo segundo número;
  • Apresente os 100 resultados em tela.

Para tornar o exemplo mais didático e evidenciar a importância do fluxo, vamos realizar primeiro apenas uma operação, e depois as outras 99.

Ok, o primeiro passo então é declarar as variáveis e receber os números:

1 - VariávelFloat numerador;

2 - VaríávelFloat denominador;

3 - numerador = converteParaFloat(EntradaDeTexto())

4 - denominador = converteParaFloat(EntradaDeTexto())


O termo “VariávelFloat” nas linhas 1 e 2 simplesmente diz que as variáveis numerador e denominador vão ser do tipo Float. Se você não tem familiaridade com os tipos ainda, não se preocupe. Por hora basta saber que o tipo Float é capaz de armazenar números com casas decimais. Nas linhas 3 e 4 estamos atribuindo valores às variáveis numerador e denominador. As variáveis recebem uma entrada do usuário, que normalmente vem como texto e por isso deve ser convertida para float.

Antes de seguirmos com o desenvolvimento do algoritmo, preste atenção à ordem das linhas 1, 2, 3 e 4. Notem que as variáveis estão sendo declaradas antes de serem atribuídas. Se invertermos as linhas 1 e 3, por exemplo, uma grande quantidade de linguagens de programação iria reclamar e apresentar erros (principalmente as fortemente tipadas, como C# e Java). Estes erros ocorreriam pois o programa estaria tentando atribuir um valor para algo que ele não sabe o que é. Isso nos mostra como o fluxo do algoritmo é algo essencial para sua perfeita funcionalidade.

Depois de termos as entradas atribuídas, devemos realizar a divisão dos números e escrever o resultado na tela. Mas podemos ter um problema grave nessa divisão: a entrada do denominador pode ser zero e, como sabemos, não podemos dividir por zero. Sendo assim, o caminho que nosso algoritmo deve seguir depende fortemente desta segunda entrada. Se ela for zero, devemos tratar o problema, senão, podemos prosseguir. Este é o momento perfeito para incluirmos uma estrutura de decisão SE no nosso programa:


1 - VariávelFloat numerador;

2 - VaríávelFloat denominador;

3 - numerador = converteParaFloat(EntradaDeTexto());

4 - denominador = converteParaFloat(EntradaDeTexto());

5 - VariávelFloat resultado;

6 - SE (denominador == 0)

7 - EscrevaNaTela(“Não é possível dividir por zero!”);

8 - SENÃO

9 - EscrevaNaTela(“Valor da divisão = ”, resultado);


A primeira coisa que mudamos foi adicionar a declaração de uma nova variável do tipo float, a resultado. Notem que, embora ela esteja declarada abaixo das entradas numéricas, não há problema, pois ela não está sendo usada antes da linha 5. Embora não haja uma convenção forte sobre isso, é muito comum termos as variáveis declaradas no começo do código. Sendo assim, vamos colocar a declaração do resultado acima no código:


1 - VariávelFloat numerador;

2 - VaríávelFloat denominador;

3 - VariávelFloat resultado;

4 - numerador = converteParaFloat(EntradaDeTexto());

5 - denominador = converteParaFloat(EntradaDeTexto());

6 - SE (denominador == 0)

7 - EscrevaNaTela(“Não é possível dividir por zero!”);

8 - SENÃO

9 - resultado = numerador/denominador;

10 - EscrevaNaTela(“Valor da divisão = ”, resultado);


Agora que a coisa está mais organizada, vamos nos concentrar na linha 6 e nas subsequentes. A linha 6 tem a instrução “SE” e a condição “denominador == 0”. A primeira coisa a se notar é a diferença entre “=” e “==”. Enquanto “=” é uma instrução de atribuição, “==” é uma instrução de comparação. Ela verifica se a variável denominador tem valor 0, e isso retorna o resultado “verdadeiro” ou “falso”. O bloco de ações representado pela linha 7 só vai ser executado quando a condição retornar “verdadeiro”. Se a validação for falsa, isso é, em qualquer outra situação onde o denominador não for zero, o bloco de ações do “SENÃO” (linhas 9 e 10) é executado.

No nosso exemplo, a linha 9 não tem nenhum problema de divisão, e pode ser executada normalmente se o denominador não for zero.

No futuro, quando estudamos o tópico de tratamento de erros, vemos maneiras bem mais elegantes de resolver esse problemas matemáticos. Entretanto, para nosso escopo atual, o tratamento com a diretiva ‘SE’ fica bem aplicado.

Finalmente, devemos repetir a ação 100 vezes, o que é bem fácil. Basta copiar e colar as instruções mais 99 vezes!

Ok, é brincadeira. Não seria nem um pouco produtivo apertar control+V 99 vezes e modificar o nome das variáveis. Felizmente, fazer o procedimento da maneira certa é ainda mais fácil que isso, usando as diretivas FOR e WHILE. Vamos, por motivos didáticos, usar a diretiva WHILE para resolver esse problema. Para estruturar bem o código, vamos antes pensar no que deve se repetir e no que não precisa necessariamente acontecer em todos os loops.

Primeiro temos a declaração de variáveis, que é basicamente dizer para o computador “reconheça o valor X da memória como Y”, onde Y é o nome da variável e X é totalmente abstraído do programador. Esse é um processo que não precisa estar dentro do loop, pois uma vez que temos a variável declarada, podemos mudar o valor dela indefinidamente sem “re-declarar” a mesma.

A entrada, contudo, deve ocorrer dentro da estrutura de repetição, caso contrário, os valores seriam sempre os mesmos. Também precisamos que a divisão, bem como a validação do denominador, aconteçam todas as vezes. Logo, essas também devem ficar dentro do WHILE. Finalmente, vejamos a estrutura do pseudocódigo:

1 - VariávelFloat numerador;

2 - VaríávelFloat denominador;

3 - VariávelFloat resultado;

4 - WHILE (n <= 100)

5 - numerador = converteParaFloat(EntradaDeTexto());

6 - denominador = converteParaFloat(EntradaDeTexto());

7 - SE (denominador == 0)

8 - EscrevaNaTela(“Não é possível dividir por zero!”);

9 - SENÃO

10 - resultado = numerador/denominador;

11 - EscrevaNaTela(“Valor da divisão = ”, resultado);


Notem as linhas 1 a 3 antes do WHILE na linha 4 (elas são executadas apenas uma vez), enquanto as linhas de 5 à 11 são executadas, a priori, 100 vezes. Contudo o código desta forma ainda não funciona por dois motivos:

i) Não declaramos a variável n. Ela é o nosso CONTADOR, um número inteiro, começando de 1 que vai ser incrementado para cada vez que o laço for executado.

ii) Um incremento do contador. Ele é extremamente importante para não cairmos em loop infinito, como veremos logo logo, mas antes vamos ao pseudocódigo completo:


1 - VariávelFloat numerador;

2 - VaríávelFloat denominador;

3 - VariávelFloat resultado;

4 - VariávelInteira n = 1;

5 - WHILE (n <= 100)

6 - numerador = converteParaFloat(EntradaDeTexto());

7 - denominador = converteParaFloat(EntradaDeTexto());

8 - SE (denominador == 0)

9 - EscrevaNaTela(“Não é possível dividir por zero!”);

10 - SENÃO

11 - resultado = numerador/denominador;

12 - EscrevaNaTela(“Valor da divisão = ”, resultado);

13 - n = n + 1;

14 - EscrevaNaTela(“Programa Finalizado!”)


Agora sim! O que temos de novo no código são as linhas 4, 13 e 14. A linha 4 é uma simples inicialização da variável n, que é um inteiro e inicia com valor 1. Na linha 13, o valor de n assume o valor de n + 1, ou seja, quando n vale 1, n + 1 vale 2 e n passa a valer 2. Este é o incremento do contador, enquanto o valor de n for menor que 100, o processo se repete, devido à condição da diretiva WHILE. Quando n finalmente atinge o valor 100, na linha 13 ele recebe o valor 101, falseando a condição no WHILE, e encerrando o loop. Quando o loop é encerrado, o programa segue seu fluxo de execução normal, passando para a linha 14, que escreve na tela “Programa Finalizado!”

Um erro que todo desenvolvedor, independente de sua experiência, comete (eu cometo 75% das vezes) é esquecer-se da linha 13. Desta maneira notem que o valor de n nunca aumenta e o WHILE nunca termina, criando o famoso erro do loop infinito, que grande parte das linguagens de programação chamam de ‘Stack Overflow’, que pode ser traduzido como ‘Estouro de Pilha”, onde o computador basicamente precisa fazer tantas execuções de tarefas que ele para de saber lidar com isso.

Conclusão


Vimos então que algoritmos são nada mais nada menos que uma sequência de comandos executados em sequência e com estruturas básicas de decisão e repetição. Dado sua simplicidade, são fáceis de serem atribuídos para computadores, através de diversas linguagens de programação.

Apresentamos um exemplo do dia-a-dia: o algoritmo para se atravessar uma rua movimentada. Também evidenciamos quais são os elementos básicos que compõem a estrutura de um algoritmo e como esses elementos são aplicados pelas linguagens de programação.

Finalmente, construímos um algoritmo em pseudocódigo, apresentando o racional para sua construção.

Agora que você conhece o básico do básico dos algoritmos, pode aplicar seus elementos na resolução de problemas dos dia-a-dia, tanto dentro da programação como fora dela.



3
74

Comentários (3)

0
Tiago Silva

Tiago Silva

17/03/2021 10:39

Extremamente didático.

Conteúdo excelente para assimilar o conceito de algoritmo.

Parabéns pelo trabalho!

0
Luciana Colombo

Luciana Colombo

17/03/2021 10:11

Ótimo texto! Obrigada Paulo!

0
Ilan Ribeiro

Ilan Ribeiro

17/03/2021 09:28

Valeu demais Paulo!

Bem estruturado e bem explicado.

Acredito que tenha se dedicado bem para trazer esse conteúdo.

Parabéns e obrigado.

None

Brasil