Página anterior | Voltar ao início do trabalho | Página seguinte |
1. função fatorial (n)
2. se , então
3. retorna
4. senão
5. retorna
6. fim-se
7. fim-função
Novo custo
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... comparado com é uma diferença pequena, mas essa solução ficou bem mais elegante. Poxa, diminuímos o custo do algoritmo em ! Hehehe...
Agora, antes de continuar, só vamos definir a notação assintótica desse nosso novo custo!
A fórmula do é:
Substituindo pela nossa função, temos: . É trivial, que podemos escolher para as duas constantes e para . 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 e uma função quadrática pertence sempre a notação (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 a cada iteração, até que chegue ao valor mínimo aonde a resposta é trivial: .
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 é . Se o cara colocasse , 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 ).
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
1. função fibonacci (n)
2. se , então
3. retorna
4. senão
5. retorna
6. fim-se
7. fim-função
Domínio de fibonacci(n):
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...
1. função imprime_ate (n)
2. imprima
3. se , então
4. imprime_ate(n-1)
5. fim-se
6. fim-função
Domínio de imprime_ate(n):
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
Vamos trabalhando com eles com o tempo...
Este mini-artigo é o sexto da
Série Algoritmos. Se você ainda não leu os artigos anteriores, sugiro que leia antes de continuar:
Neste artigo, iremos começar a estudar a ordenação de vetores. Aí vocês poderiam me perguntar: Por que a ordenação é importante? ou Por que começar com a ordenação? Por isso, resolvi antes de iniciar nos algoritmos de ordenação criar um artigo light para explicar no que consiste um vetor, o que é a ordenação e pra quê ela serve. Se isso tudo é óbvio pra você, espere... O próximo artigo chegará em breve!
O que é um vetor?
Vetor é uma estrutura de dados que serve para substituir várias variáveis. Para um problema pequeno onde desejo armazenar dois inteiros e tirar o MMC deles eu posso usar duas variáveis: n1 e n2. Mas existem casos em que seria um número muito grande de variáveis (e em alguns deles nem sabemos ao certo, porque faremos uma alocação a partir de um número que o usuário pedir), por isso vetores são extremamente úteis.
No que consiste a ordenação?
Os algoritmos de ordenação tem como objetivo permutar uma seqüência de forma que . A ordenação não precisa ser exatamente de um vetor, mas vetor é geralmente a estrutura que usamos para guardar uma lista de números para podermos ordená-los.
Por que ordenar?
Nessa parte, vou plagiar o Cormen...
Algoritmos que iremos estudar
Eu planejo passar três algoritmos nos próximos artigos. São eles:
Acho que é mais interessante não entrarmos no Heap Sort, no Quick Sort e outros algoritmos até mais rápidos, porque suas implementações são um pouco complexas demais para o objetivo dessa série. Mas quem sabe depois de tudo estar pronto, eu mude de idéia. Ou se vocês acharem realmente importante, dêem um toque!
Este artigo é o sétimo da
Série Algoritmos. Se você ainda não leu os artigos anteriores, sugiro que leia antes de continuar:
Também conhecida como Insertion Sort, a Ordenação por Inserção consiste em inserir um elemento num vetor já ordenado de elementos. Neste artigo, apresento-lhes este simples algoritmo para ordenação de vetores.
O pseudocódigo da ordenação por inserção é o seguinte:
1. para j 2 até comprimento do vetor, faça
2. elemento vetor[j]
3. i j - 1
4. enquanto i > 0 e vetor[i] > elemento, faça
5. vetor[i + 1] vetor[i]
6. i i - 1
7. fim-enquanto
8. vetor[i + 1] elemento
9. fim-para
Para explicar eu vou fazer uma coisa que sempre faço para corrigir meus algoritmos, fingir que sou o programa, executando cada passo manualmente (claro que geralmente faço mais rápido, porque não preciso escrever tudo que faço). Vamos iniciar com o seguinte vetor v.
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
5 |
3 |
7 |
8 |
2 |
5 |
Aí o código me manda começar com e iterar até o comprimento do vetor (6). A primeira ordem que ele me dá é para armazenar o elemento () na variável elemento. Para facilitar toda a explicação eu vou sempre pintar de cinza o onde eu estou (no caso, o segundo elemento do vetor, 3) e de preto o vetor ainda não ordenado (elementos ).
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
5 |
3 |
7 |
8 |
2 |
5 |
Então ele me diz que . Portanto, . E agora ele me faz um enquanto (que poderia ser substituído por para) onde meu i deverá ir diminuindo. Vamos entrar no loop...
Bom, meu é maior que 0. é maior que o ? Sim, então vamos entrar no corpo do enquanto... Aqui ele me manda fazer um , que nesse caso é fazer um .
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
5 |
5 |
7 |
8 |
2 |
5 |
E agora subtrae de i um valor. Portanto, . Ele retorna ao enquanto, mas agora não satisfazemos a condição , por isso saímos do enquanto. Então ele pede para (). Portanto, o vetor fica assim:
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
3 |
5 |
7 |
8 |
2 |
5 |
E incrementamos o j, agora .
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
3 |
5 |
7 |
8 |
2 |
5 |
... E ? Não! Portanto, não entramos no enquanto.
(nenhuma mudança)
E lá vamos para e continuando até que vamos ter o vetor ordenado:
v[1] |
v[2] |
v[3] |
v[4] |
v[5] |
v[6] |
2 |
3 |
5 |
5 |
7 |
8 |
Qual a lógica?
Como eu já disse na introdução, mas lá sem grandes explicações, a Ordenação por Inserção divide o vetor em dois. O primeiro (de elementos ) contém todos seus valores ordenados e o segundo (de elementos ) contém os valores que devem ser inseridos no primeiro vetor já ordenado (por isso ele se chama Algoritmo de Inserção).
A chave do algoritmo é o enquanto que acontece para ir deslocando todos os elementos para seu índice enquanto o elemento for menor que eles (para depois o elemento ser colocado onde parou).
A variável elemento só serve para não perdermos o valor de (porque depois fazemos quando )
Acredito que não tenham restado dúvidas. Dê mais uma olhada no algoritmo e tente implementar. Se tiver dificulade, coloque mensagens de debug estratégicas para entender o algoritmo. (ex.: no início do para coloque para imprimir o valor de j e no início de cada enquanto coloque para imprimir os valores elemento, i e v[i])
Custo
Você deve ter percebido que este algoritmo não tem um custo fixo. Se todo o vetor estiver ordenado, ele nunca precisará iterar o e portanto será executado bem mais rápido do que se o vetor estiver inteiro em ordem decrescente (quando ele sempre precisará iterar até o fim - 0). Então, neste artigo, gostaria-lhes de apresentar a análise de algoritmos baseada em casos. Para este programa, dizemos que:
Em alguns programas o caso médio é importante também, mas não é o caso da ordenação por inserção. Vemos que há uma diferença bem grande entre o custo dos dois casos. Por isso, precisamos conhecer onde que nosso algoritmo será implementado e quais as chances de ele ser o melhor ou pior caso. Em geral, o pior caso é o mais comum... Por isso, diremos que o custo deste algoritmo é .
Tiago Madeira - tmadeira[arroba]gmail.com
Página anterior | Voltar ao início do trabalho | Página seguinte |
|
|