![]() | ![]() ![]() | Página seguinte ![]() |
Este algoritmo não tem um melhor/pior caso, porque todos os elementos são varridos, sempre. Medir seu custo é simples. Custo de linha por linha...
n = tamanho do vetor
Você pode estar se perguntando porque eu coloquei este custo para a linha 5. Afinal, a linha 5 diria que este programa tem um melhor/pior caso, porque ela não seria executada se o se retornar falso. Mas o caso é que ela é desprezível. Uma soma como estas para o custo geral do nosso algoritmo não vai influenciar em nada. Quer ver? Vamos somar os custos com esta linha valendo (como se nenhum se entrasse) e depois com ela valendo
.
Primeiro cálculo
Segundo cálculo
Conclusão
Como vocês puderam ver, não faz diferença alguma o que aquela somatória nos proporciona. Já que todo o cálculo de algoritmos é baseado apenas no maior expoente de n e desprezamos todas as constantes (inclusive as que multiplicam o n de maior expoente, muitos passos são desprezíveis.
Este artigo é o nono da Série Algoritmos. Se você ainda não leu os artigos anteriores, sugiro que leia antes de continuar:
Resolvi fazer uma pausa nos algoritmos de ordenação para mostrar como podemos usar os conhecimentos já adquiridos de maneira prática. Vamos neste artigo resolver o problema Mini-Poker, que caiu na prova da Programação Nível 2 (categoria para pessoas até 19 anos ou primeiro ano da faculdade) da Olimpíada Brasileira de Informática de 2005.
Esse post ficou gigante, mas é muito simples. Leia com atenção e acho que você não terá problemas...
Com esta resolução de problema, espero treinar com vocês o conceito de:
Acho que será legal para pôrmos em prática o que já estudamos sobre algoritmos.
O problema é bem simples, mas é só pra iniciar. Depois vamos resolvendo problemas cada vez mais difíceis...
Mini-Poker é o nome do jogo de cartas que é uma simplificação de Poker, um dos mais famosos jogos de cartas do mundo. Mini-Poker é jogado com um baralho normal de 52 cartas, com quatro naipes (copas, paus, espadas e ouro), cada naipe compreendendo treze cartas (Ás, 2, 3, 4, 5, 6, 7, 8, 9, 10, Valete, Dama, Rei).
No início do jogo, cada jogador recebe cinco cartas. O conjunto de cinco cartas vale um certo número de pontos, de acordo com as regras descritas abaixo. Diferentemente do jogo de Poker normal, em Mini-poker o naipe das cartas é desconsiderado. Assim, para simplificar a descrição do jogo, vamos utilizar os números de 1 a 13 para identificar as cartas do baralho, na ordem dada acima. Uma outra diferença é que pode ocorrer empate entre mais de um vencedor; nesse caso os vencedores dividem o prêmio.
As regras para pontuação em Mini-Poker são as seguintes:
Tarefa
Escreva um programa que, fornecidas as cartas dadas a um jogador, calcule a pontuação do jogador naquela jogada.
Entrada
A entrada é composta por vários casos de teste, cada um correspondendo a uma jogada. A primeira linha da entrada contém um número inteiro que indica o número de casos de teste (
). Cada uma das
linhas seguintes contém cinco números inteiros
,
,
,
e
, representando as cinco cartas recebidas por um jogador (
).
A entrada deve ser lida do dispositivo de entrada padrão (normalmente o teclado).
Saída
Para cada caso de teste da entrada, seu programa deve produzir três linhas na saída. A primeira linha deve conter um identificador do caso de teste, no formato "Teste n", onde n é numerado seqüencialmente a partir de 1. A segunda linha deve conter a pontuação do jogador considerando as cinco cartas recebidas. A terceira linha deve ser deixada em branco. A grafia mostrada no Exemplo de Saída, abaixo, deve ser seguida rigorosamente.
A saída deve ser escrita no dispositivo de saída padrã (normalmente a tela).
Restrições
Exemplo de Entrada
2
12 3 10 3 12
1 2 3 5 4
Saída para o Exemplo de Entrada
Teste 1
62
Teste 2
201
2.3. Comentários sobre os problemas de olimpíadas
Todos os problemas passados em competições de programação tem um enunciado parecido com o desse. São especificados todos os limites (restrições), é dito exatamente como será a entrada e como deve ser a saída e geralmente tem uma historinha no começo...
Bom... Todos esses dados são fundamentais. Alguns limites nem vamos usar, não tem importância para a nossa solução, mas pode ter importância para outra pessoa que queira implementar um algoritmo diferente. A sintaxe da entrada e da saída são extremamente importantes. Na prova da Seletiva IOI do ano passado, eu quase perdi 60 pontos (6 casos de teste) na solução de um problema simples porque meu programa desprezava um espaço no início de uma frase quando imprimia uma saída. E mesmo a historinha do começo é fundamental. Ela sempre dá boas dicas e algumas vezes até ilustra o problema (às vezes a gente nem lê o enunciado e já sabe que é um problema de grafos!)
Mas vamos a solução deste problema...
Com o tempo você pode decidir fazer um caminho diferente, mas eu sugiro começar sempre pelo recebimento da entrada. Aliás, acho que isto é atípico, porque a maioria das pessoas prefere ler bastante o problema e desenvolver todo o algoritmo a mão antes de botar a mão na massa. Eu acho que depois que a gente recebe a entrada, fica bem mais fácil fazer o resto e a gente pode ir pensando enquanto a gente recebe a entrada! Então, depois que lemos o problema e já entendemos tudo o que ele quer, vamos fazer a entrada!
O problema fala que começa nos dando um número N que será o número de casos de teste que teremos que receber depois. Sem dificuldade podemos escrever o pseudocódigo a seguir:
recebe N
para nteste 1 até N, faça
fim-para
Já chamo a variável que loopa como nteste, porque já li a saída do problema e sei que vou precisar imprimir o número de caad caso de teste...
Aí o enunciado diz que Cada uma das linhas seguintes contém cinco números inteiros
,
,
,
e
, representando as cinco cartas recebidas por um jogador (
). Então, vamos receber os cinco números em cada iteração e colocá-los num vetor, é claro!
recebe N
para nteste 1 até N, faça
recebe
fim-para
E a entrada está pronta.
O programa se baseia em encontrarmos valores iguais nos elementos do vetor. O que podemos fazer para facilitar essa tarefa?
Isso mesmo: A ordenação! Se os elementos estiverem ordenados, ficará bem mais fácil para procurarmos quatro números iguais, porque eles não poderão ser qualquer uma das possibilidades, mas somente ou
.
Aí que algoritmos devemos implementar para ordenar? Isso é uma conclusão que vamos chegar no final de nossa série, mas para este algoritmo não tem solução melhor que a Ordenação por Inserção. É um caso pequeno (n=5) e a Ordenação por Inserção é mais rápida que a por Seleção, porque o seu melhor caso é uma função linear. Então, vamos implementar o Insertion Sort no nosso algoritmo:
recebe N
para nteste 1 até N, faça
recebe
início da ordenação por inserção
para j 2 até 5
elemento
i j-1
enquanto i > 0 e > elemento, faça
fim-enquanto
elemento
fim-para
fim da ordenação por inserção
fim-para
O bom desses algoritmos de ordenação é que sua lógica é muito simples e por isso é fácil decorá-los... Ao menos o Insertion Sort e o Selection Sort são algoritmos básicos que todo programador deve conhecer bem. Bom... Acredito que vocês não tenham tido dificuldade pra entender até aqui. A cor vermelha no pseudocódigo eu vou usar daqui pra frente para um comentário, que aliás, é uma excelente prática de boa programação.
O resto do problema precisa calcular quantos pontos o cara fez, baseado em suas cartas, agora já ordenadas. Para isto vamos criar uma função para testar vários se e retornar o resultado.
Eu poderia tirar os se aninhados, mas assim fica mais fácil a compreensão.
Como vamos ver com os pseudocódigos a seguir, é fácil testar cada uma das regras com o vetor ordenado:
Primeira Regra - Seqüência
Se as cinco cartas estão em seqüência a partir da carta (ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos. Por exemplo, se as cartas recebidas são 10, 9, 8, 11 e 12, a pontuação é 208 pontos.
se e
e
e
, então
retorna
fim-se
Segunda Regra - Quadra
Se há quatro cartas iguais (uma quadra, ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos. Por exemplo, se as cartas recebidas são 1, 1, 1, 10 e 1, a pontuação é 181 pontos.
se ou
, então
retorna
fim-se
Aqui retornamos porque ele será sempre parte da quadra (ela começando em
ou
).
Terceira e Quarta Regra - Trinca
Se há três cartas iguais e outras duas cartas iguais
(uma trinca e um par, ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos. Por exemplo, se as cartas recebidas são 10, 4, 4, 10 e 4, a pontuação é 164 pontos.
se ou
ou
, então
se ( e
) ou (
e
), então
retorna
Se há três cartas iguais e duas outras cartas diferentes
e
(uma trinca, ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos. Por exemplo, se as cartas recebidas são 2, 3, 2, 2 e 13, a pontuação é 142 pontos.
senão
retorna
fim-se
fim-se
Note que aqui retornamos porque ele será sempre parte da trinca (o mesmo motivo que retornarmos
para a quadra).
Quinta Regra - Duas Duplas
Se há duas cartas iguais , duas outras cartas iguais
(
) e uma outra carta distinta
(dois pares, ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos, em que
. Por exemplo, se as cartas recebidas são 12, 7, 12, 8 e 7, a pontuação é 70 pontos.
se ou
, então
se ou
, então
retorna
fim-se
fim-se
será sempre elemento da menor dupla e
será sempre elemento da maior dupla. Por isso usamos eles como
e
, respectivamente.
Sexta Regra - Dupla
Se há apenas duas cartas iguais e as outras são distintas (um par, ou seja, os valores das cartas são
,
,
,
e
), a pontuação é
pontos. Por exemplo, se as cartas recebidas são 12, 13, 5, 8 e 13, a pontuação é 13 pontos.
se ou
, então
retorna
senão se ou
,
então
retorna
fim-se
Separei em dois SEs porque senão não saberíamos que valor retornar.
Sétima Regra
Se todas as cartas são distintas, não há pontuação.
retorna 0
Função Inteira
Juntando todos os SEs, temos:
função pontua (C)
primeira regra
se e
e
e
, então
retorna
fim-se
segunda regra
se ou
, então
retorna
fim-se
terceira e quarta regra
se ou
ou
, então
se ( e
) ou (
e
), então
retorna
senão
retorna
fim-se
fim-se
quinta regra
se ou
, então
se ou
, então
retorna
fim-se
fim-se
sexta regra
se ou
, então
retorna
senão se ou
,
então
retorna
fim-se
sétima regra
retorna 0
fim-função
Já que a função retorna assim que encontra um resultado, não há risco de ocorrer nada errado (por exemplo, uma quadra é sempre uma trinca, que é sempre uma dupla). Agora basta colocarmos esta função no nosso código e adaptar para a saída ser igual a que o problema pede.
Saída
Para chegar a saída, basta fazermos o programa imprimir Teste nteste e depois o retorno da função pontua. Com isto, temos:
recebe N
para nteste 1 até N, faça
recebe
início da ordenação por inserção
para j 2 até 5
elemento
i j-1
enquanto i > 0 e > elemento, faça
fim-enquanto
elemento
fim-para
fim da ordenação por inserção
imprime "Teste "
imprime linha testen
imprime linha pontua(C)
imprime linha
fim-para
Fiz essa saída assim pra se parecer com Pascal, mas para cada linguagem ela pode ser bem diferente... Vejamos dois exemplos...
C
printf("Teste %dn%dnn", nteste, pontua(C));
PHP
echo "Teste ".$nteste."n".pontua($C)."nn";
função pontua (C)
primeira regra
se e
e
e
, então
retorna
fim-se
segunda regra
se ou
, então
retorna
fim-se
terceira e quarta regra
se ou
ou
, então
se ( e
) ou (
e
), então
retorna
senão
retorna
fim-se
fim-se
quinta regra
se ou
, então
se ou
, então
retorna
fim-se
fim-se
sexta regra
se ou
, então
retorna
senão se ou
,
então
retorna
fim-se
sétima regra
retorna 0
fim-função
recebe N
para nteste 1 até N, faça
recebe
início da ordenação por inserção
para j 2 até 5
elemento
i j-1
enquanto i > 0 e > elemento, faça
fim-enquanto
elemento
fim-para
fim da ordenação por inserção
imprime "Teste "
imprime linha testen
imprime linha pontua(C)
imprime linha
fim-para
2.7. Comentários sobre o problema
Este problema é muito chato. É trivial, mas perdemos um tempo enorme escrevendo ses. Ninguém gosta de um problema como esse, mas quando cai numa olimpíada somos obrigados a resolver... hehehe... Mas, para a felicidade geral de todos, saibam que a maioria dos problemas de olimpíadas não são assim. Exigem mais lógica e menos código. Com o tempo, vamos pegando problemas mais difíceis. Espero só ter cumprido meu objetivo dando uma utilidade pra ordenação, entrada e saída e que vocês tenham entendido tudo.
Espero que tenham gostado da solução. Eu implementei este programa em C há seis meses e se você estiver interessado, sua solução está aqui: poker.c.
Sugiro que quem esteja aprendendo algoritmos com meus artigos e já saiba programar um pouquinho, resolva alguns problemas simples do site da OBI, que separei especialmente pra vocês!
Quermesse
O mais importante é a interpretação e o seu pensamento... Programar é fácil!
Tiago Madeira - tmadeira[arroba]gmail.com
![]() | ![]() ![]() | Página seguinte ![]() |
|
|