Introdução à programação/Definições sobre Lógica de Programação
Lógica de Programação é a técnica de desenvolver algoritmos (sequências lógicas) para atingir determinados objetivos dentro de certas regras baseadas na Lógica matemática e em outras teorias básicas da Ciência da Computação e que depois são adaptados para a Linguagem de Programação utilizada pelo programador para construir seu software.
Um algoritmo é uma sequência não ambígua de instruções que é executada até que determinada condição se verifique. Mais especificamente, em matemática, constitui o conjunto de processos (e símbolos que os representam) para efetuar um cálculo.
O conceito de algoritmo é frequentemente ilustrado pelo exemplo de uma receita, embora muitos algoritmos sejam mais complexos. Eles podem repetir passos (fazer iterações) ou necessitar de decisões (tais como comparações ou lógica) até que a tarefa seja completada. Um algoritmo corretamente executado não irá resolver um problema se estiver implementado incorretamente ou se não for apropriado ao problema.
Um algoritmo não representa, necessariamente, um programa de computador, e sim os passos necessários para realizar uma tarefa. Sua implementação pode ser feita por um computador, por outro tipo de autômato ou mesmo por um ser humano. Diferentes algoritmos podem realizar a mesma tarefa usando um conjunto diferenciado de instruções em mais ou menos tempo, espaço ou esforço do que outros. Tal diferença pode ser reflexo da complexidade computacional aplicada, que depende de estruturas de dados adequadas ao algoritmo. Por exemplo, um algoritmo para se vestir pode especificar que você vista primeiro as meias e os sapatos antes de vestir a calça enquanto outro algoritmo especifica que você deve primeiro vestir a calça e depois as meias e os sapatos. Fica claro que o primeiro algoritmo é mais difícil de executar que o segundo apesar de ambos levarem ao mesmo resultado.
O conceito de um algoritmo foi formalizado em 1936 pela Máquina de Turing de Alan Turing e pelo cálculo lambda de Alonzo Church, que formaram as primeiras fundações da Ciência da Computação.
Formalismo
[editar | editar código-fonte]Um programa de computador é essencialmente um algoritmo que diz ao computador os passos específicos e em que ordem eles devem ser executados, como por exemplo, os passos a serem tomados para calcular as notas que serão impressas nos boletins dos alunos de uma escola. Logo, o algoritmo pode ser considerado uma sequência de operações que podem ser simuladas por uma máquina de Turing completa.
Quando os procedimentos de um algoritmo envolvem o processamento de dados, a informação é lida de uma fonte de entrada, processada e retornada sob novo valor após processamento, o que geralmente é realizado com o auxílio de uma ou mais estruturas de dados.
Para qualquer processo computacional teórico, o algoritmo precisa estar rigorosamente definido, especificando a maneira que ele se comportará em todas as circunstâncias. A corretude do algoritmo pode ser provada matematicamente, bem como a quantidade assintótica de tempo e espaço (complexidade) necessários para a sua execução. Estes aspectos dos algoritmos são alvo da análise de algoritmos. As implementações, porém, podem se limitar a casos específicos.
A maneira mais simples de se pensar um algoritmo é por uma lista de procedimentos bem definida, no qual as instruções são executadas passo a passo a partir do começo da lista, uma ideia que pode ser facilmente visualizada através de um fluxograma. Tal formalização adota as premissas da programação imperativa, que é uma forma mecânica para visualizar e desenvolver um algoritmo. Concepções alternativas para algoritmos variam em programação funcional e programação lógica.
Término do algoritmo
[editar | editar código-fonte]Alguns autores restringem a definição de algoritmo para procedimentos que eventualmente terminam. Minksy constatou que se o tamanho de um procedimento não é conhecido de antemão, tentar descobri-lo é problema indecidível já que o procedimento pode ser executado infinitamente, de forma que nunca se terá a resposta. Alan Turing provou em 1936 que não existe máquina de Turing para realizar tal análise para todos os casos, logo não há algoritmo para realizar tal tarefa para todos os casos. Tal condição é conhecida atualmente como problema da parada. Basicamente, isto quer dizer que não existe um programa de computador que possa antever, de forma geral, se um outro programa de computador vai parar algum dia.
Para algoritmos intermináveis o sucesso não pode ser determinado pela interpretação da resposta e sim por condições impostas pelo próprio desenvolvedor do algoritmo durante sua execução. Por exemplo, podemos querer um algoritmo interminável para controlar um sinal de trânsito.
Implementação
[editar | editar código-fonte]A maioria dos algoritmos é desenvolvida para ser implementada em um programa de computador. Apesar disso eles também podem ser implementados por outros modos tais como uma rede neural biológica (tal como no cérebro quando efetuamos operações aritméticas) em circuitos elétricos ou até mesmo em dispositivos mecânicos.
Para programas de computador existe uma grande variedade de linguagens de programação, cada uma com características específicas que podem facilitar a implementação de determinados algoritmos ou atender a propósitos mais gerais.
Análise de algoritmos
[editar | editar código-fonte]A análise de algoritmos é um ramo da ciência da computação que estuda as técnicas de projeto de algoritmos e os algoritmos de forma abstrata, sem estarem implementados em uma linguagem de programação em particular ou implementadas de algum outro modo. Ela preocupa-se com os recursos necessários para a execução do algoritmo tais como o tempo de execução e o espaço de armazenamento de dados. Deve-se perceber que para um dado algoritmo pode-se ter diferentes quantidades de recursos alocados de acordo com os parâmetros passados na entrada. Por exemplo, se definirmos que o fatorial de um número natural é igual ao fatorial de seu antecessor multiplicado pelo próprio número, fica claro que a execução de fatorial(10) consome mais tempo que a execução de fatorial(5).
Um meio de exibir um algoritmo a fim de analisá-lo é através da implementação por pseudocódigo em português estruturado. O exemplo a seguir é um algoritmo em português estruturado que retorna (valor de saída) a soma de dois valores (também conhecidos como parâmetros ou argumentos, valores de entrada) que são introduzidos na chamada da função:
função SomaDeDoisValores (A numérico, B numérico) inicio declare SOMA numérico SOMA <-- A + B retorne (SOMA) fim
Classificação
[editar | editar código-fonte]Classificação por implementação
[editar | editar código-fonte]Pode-se classificar algoritmos pela maneira como foram implementados.
- Recursivo ou iterativo - um algoritmo recursivo possui a característica de invocar a si mesmo repetidamente até que certa condição seja satisfeita e ele é terminado, que é um método comum em programação funcional. Algoritmos iterativos usam estruturas de repetição tais como laços, ou ainda estruturas de dados adicionais tais como pilhas, para resolver problemas. Cada algoritmo recursivo possui um algoritmo iterativo equivalente e vice versa, mas que pode ter mais ou menos complexidade em sua construção. É possível construir algoritmos que sejam ao mesmo tempo iterativo e recursivo, provavelmente para aproveitar alguma otimização de tempo ou espaço que isso permita.
- Lógico - um algoritmo pode ser visto como uma dedução lógica controlada. O componente lógico expressa os axiomas usados na computação e o componente de controle determina a maneira como a dedução é aplicada aos axiomas. Tal conceito é base para a programação lógica.
- Serial ou paralelo - algoritmos são geralmente assumidos por serem executados instrução à instrução individualmente, como uma lista de execução, o que constitui um algoritmo serial. Tal conceito é base para a programação imperativa. Por outro lado existem algoritmos executados paralelamente, que levam em conta arquiteturas de computadores com mais de um processador para executar mais de uma instrução ao mesmo tempo. Tais algoritmos dividem os problemas em sub-problemas e o delegam a quantos processadores estiverem disponíveis, agrupando no final o resultado dos sub-problemas em um resultado final ao algoritmo. Tal conceito é base para a programação paralela. De forma geral, algoritmos iterativos são paralelizáveis; por outro lado existem algoritmos que não são paralelizáveis, chamados então problemas inerentemente seriais.
- Determinístico ou não-determinístico - algoritmos determinísticos resolvem o problema com uma decisão exata a cada passo enquanto algoritmos não-determinísticos resolvem o problema ao deduzir os melhores passos através de estimativas sob forma de heurística.
- Exato ou aproximado - enquanto alguns algoritmos encontram uma resposta exata, algoritmos de aproximação procuram uma resposta próxima a verdadeira solução, seja através de estratégia determinística ou aleatória. Possuem aplicações práticas sobretudo para problemas muito complexos, do qual uma resposta correta é inviável devido à sua complexidade computacional.
Classificação por paradigma
[editar | editar código-fonte]Pode-se classificar algoritmos pela metodologia ou paradigma de seu desenvolvimento, tais como:
- Divisão e conquista - algoritmos de divisão e conquista reduzem repetidamente o problema em sub-problemas, geralmente de forma recursiva, até que o sub-problema é pequeno o suficiente para ser resolvido. Um exemplo prático é o algoritmo de ordenação merge sort. Uma variante dessa metodologia é o decremento e conquista, que resolve um sub-problema e utilização a solução para resolver um problema maior. Um exemplo prático é o algoritmo para pesquisa binária.
- Programação dinâmica - pode-se utilizar a programação dinâmica para evitar o re-cálculo de solução já resolvida anteriormente.
- Algoritmo ganancioso - um algoritmo ganancioso é similar à programação dinâmica, mas difere na medida que as soluções dos sub-problemas não precisam ser conhecidas a cada passo, uma escolha gananciosa pode ser feita a cada momento com o que até então parece ser mais adequado.
Programação linear
- Redução - a redução resolve o problema ao transformá-lo em outro problema. É chamado também transformação e conquista.
- Busca e enumeração - vários problemas podem ser modelados através de grafos. Um algoritmo de exploração de grafo pode ser usado para caminhar pela estrutura e retornar informações úteis para a resolução do problema. Esta categoria inclui algoritmos de busca e backtracking.
- Paradigma heurístico e probabilístico - algoritmos probabilísticos realizam escolhas aleatoriamente. Algoritmos genéticos tentam encontrar a solução através de ciclos de mutações evolucionárias entre gerações de passos, tendendo para a solução exata do problema. Algoritmos heurísticos encontram uma solução aproximada para o problema.
Classificação por campo de estudo
[editar | editar código-fonte]Cada campo da ciência possui seus próprios problemas e respectivos algoritmos, adequados para resolvê-los. Exemplos clássicos são algoritmos de busca, de ordenação, de análise numérica, de teoria de grafos, de manipulação de cadeias de texto, de geometria computacional, de análise combinatória, de aprendizagem de máquina, de criptografia, de compressão de dados e de interpretação de texto.
Classificação por complexidade
[editar | editar código-fonte]Ver artigo principal: Complexidade computacional. Alguns algoritmos são executados em tempo linear, de acordo com a entrada, enquanto outros são executados em tempo exponencial ou até mesmo nunca terminam de ser executados. Alguns problemas possuem múltiplos algoritmos enquanto outros não possuem algoritmos para resolução.