Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


apostilas - apostila - ed2, Notas de estudo de Cultura

material para estudo

Tipologia: Notas de estudo

2012

Compartilhado em 23/05/2012

rafael-sousa-83
rafael-sousa-83 🇧🇷

5

(1)

25 documentos

1 / 36

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
1
Apostila de Algori tmos e Estrutura de Dados II 30-703 4 créditos
EMENTA: Análise e projeto dos tipos de dados abstratos, estruturas de dados e suas aplicações: listas lineares, pilhas, filas.
Métodos e técnicas de classificação de dados.
OBJETIVOS: Ao final desta disciplina o aluno deverá ser capaz de definir formalmente estruturas de dados, manipular estas
estruturas, selecioná-las e utilizá-las em suas aplicações.
RELAÇÃO DOS CONTEÚDOS:
Conceitos Iniciais
- Introdução: tipos primitivos de dados, vetores, matrizes, estruturas (structs).
- Tipos abstratos de dados (TADs)
- Representação e implementação de TDA.
Recursividade
- Definição, exemplos, simulação e implementação de recursividade. Exercícios
Listas lineares
- Definição, estruturas estáticas e dinâmicas, operações básicas em listas de elementos.
Pilhas
- Definição do tipo abstrato, aplicações e exemplos
- Operações básicas em uma pilha
- Exercícios e Implementações de pilhas
Filas
- Definição do tipo abstrato, aplicações e exemplos
- Operações básicas em uma fila
- Filas circulares
- Exercícios e Implementações de filas
Classificação
- Listas ordenadas. Métodos de classificação de dados por:
- Inserção (direta e incrementos decrescentes)
- Troca ( bolha e partição)
- Seleção (seleção direta e em árvore)
- distribuição e intercalação
Listas ligadas
- Pilhas ligadas
- Filas lidadas
- Listas ligadas
- Listas duplamente ligadas
- Exercícios e Implementações
BIBLIOGRAFIA BÁSICA (LIVROS TEXTOS):
TENEMBAUM, Aaron M. Estrutura de Dados Usando C. São Paulo: Makron Books do Brasil, 1995.
VELLOSO, Paulo. Estruturas de Dados. Rio de Janeiro: Ed. Campus, 1991.
VILLAS, Marcos V & Outros. Estruturas de Dados: Conceitos e Técnicas de implementação. RJ: Ed. Campus, 1993.
BIBLIOGRAFIA COMPLEMENTAR:
SKIENA, Steven; Revilla, Miguel. Programming Challenges. Springer-Verlag New York, 2003.
UVA Online Judge http://icpcres.ecs.baylor.edu/onlinejudge/
AZEREDO, Paulo A. Métodos de Classificação de Dados. Rio de Janeiro: Ed. Campus, 1996.
AVALIAÇÃO.: A avaliação consistirá de 3 notas (prova1 + prova2 + trabalho) / 3, sendo que os trabalhos serão os
problemas propostos em cada capítulo, que devem ser desenvolvidos em laboratório
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24

Pré-visualização parcial do texto

Baixe apostilas - apostila - ed2 e outras Notas de estudo em PDF para Cultura, somente na Docsity!

Apostila de Algoritmos e Estrutura de Dados II 30-703 4 créditos

EMENTA: Análise e projeto dos tipos de dados abstratos, estruturas de dados e suas aplicações: listas lineares, pilhas, filas.

Métodos e técnicas de classificação de dados.

OBJETIVOS: Ao final desta disciplina o aluno deverá ser capaz de definir formalmente estruturas de dados, manipular estas

estruturas, selecioná-las e utilizá-las em suas aplicações.

RELAÇÃO DOS CONTEÚDOS:

  • Conceitos Iniciais
    • Introdução: tipos primitivos de dados, vetores, matrizes, estruturas (structs).
    • Tipos abstratos de dados (TADs)
    • Representação e implementação de TDA.
  • Recursividade - Definição, exemplos, simulação e implementação de recursividade. Exercícios
  • Listas lineares
    • Definição, estruturas estáticas e dinâmicas, operações básicas em listas de elementos.
  • Pilhas
    • Definição do tipo abstrato, aplicações e exemplos
    • Operações básicas em uma pilha
    • Exercícios e Implementações de pilhas
  • Filas
    • Definição do tipo abstrato, aplicações e exemplos
    • Operações básicas em uma fila
    • Filas circulares
    • Exercícios e Implementações de filas
  • Classificação
    • Listas ordenadas. Métodos de classificação de dados por:
      • Inserção (direta e incrementos decrescentes)
      • Troca ( bolha e partição)
      • Seleção (seleção direta e em árvore)
      • distribuição e intercalação
  • Listas ligadas
    • Pilhas ligadas
    • Filas lidadas
    • Listas ligadas
    • Listas duplamente ligadas - Exercícios e Implementações

BIBLIOGRAFIA BÁSICA (LIVROS TEXTOS):

TENEMBAUM, Aaron M. Estrutura de Dados Usando C. São Paulo: Makron Books do Brasil, 1995.

VELLOSO, Paulo. Estruturas de Dados. Rio de Janeiro: Ed. Campus, 1991.

VILLAS, Marcos V & Outros. Estruturas de Dados: Conceitos e Técnicas de implementação. RJ: Ed. Campus, 1993.

BIBLIOGRAFIA COMPLEMENTAR:

SKIENA, Steven; Revilla, Miguel. Programming Challenges. Springer-Verlag New York, 2003.

UVA Online Judge http://icpcres.ecs.baylor.edu/onlinejudge/

AZEREDO, Paulo A. Métodos de Classificação de Dados. Rio de Janeiro: Ed. Campus, 1996.

AVALIAÇÃO.: A avaliação consistirá de 3 notas (prova1 + prova2 + trabalho) / 3, sendo que os trabalhos serão os

problemas propostos em cada capítulo, que devem ser desenvolvidos em laboratório

1. Conceitos Iniciais

1.1 Tipo de dados

Assume-se que cada constante, variável, expressão ou função é um certo tipo de dados. Esse tipo refere-se

essencialmente ao conjunto de valores que uma constante variável, etc. pode assumir.

Tipo primitivos de dados:

Essencialmente são os números, valores lógicos, caracteres, etc que são identificados na maioria das linguagens:

int: compreende os números inteiros float: compreende os números reais char: compreende os caracteres

1.2 Vetor ou Array

A[10] - Nome do vetor e um índice para localizar.

a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

Operações com vetor:

Some o 1º e o último elemento do vetor na variável x: x = a[0] + a[9] x = 6 + 1

Exercício:

Calcule a média dos elementos do vetor:

#include

using namespace std;

float media ( int vet2[10]);

int main( void ){

int i, vet[10];

for (i=0; i<10; i++)

cin >> vet[i];

float x = media(vet);

cout << "Média= " << x;

return (0);

float media ( int vet2[10]){

float soma = 0;

for (int i = 0 ; i <10; i++)

soma += vet2[i];

return (soma/10);

Vetores bidimensionais em C :

int a[5][2] (5 linhas e 2 colunas) int a[3][5][2] (3 planos, 5 linhas e 2 colunas)

Formas mais simples de declarar uma estrutura em C :

struct { char primeiro[10]; char inicialmeio; char ultimo[20]; } nome; typedef struct { char primeiro[10]; char inicialmeio; char ultimo[20]; } TIPONOME; TIPONOME nome, snome, fulano;

Obs: em todos os casos a inicialmeio é apenas um caracter. Exemplo.: Pedro S. Barbosa

#include using namespace std; typedef struct { string nome; string endereco; string cidade; } CADASTRO;

2. Recursividade

Um algoritmo que resolve um problema grande em problemas menores, cujas soluções requerem a aplicação dele

mesmo, é chamado recursivo.

Existem dois tipos de recursividade: direta e indireta :

Direta : uma rotina R pode ser expressa como uma composição formada por um conjunto de comandos C e uma

chamada recursiva à rotina R: R [C,R]

Indireta : as rotinas são conectadas através de uma cadeia de chamadas sucessivas que acaba retornando à primeira

que foi chamada:

R1 ← [C1,R2] R2 ← [C2,R13] ... Rn ← [Cn,R1]

Uso da recursividade na solução de problemas:

Ex: cálculo de fatorial:

0! = 1 // dado por definição

n! = n*(n-1) // requer reaplicação da rotina para (n-1)!

Veja a função:

Função fat(n)

Início

Se n=0 então retorne (1)

Senão retorne (n*fat(n-1)) //chamada recursiva

Fim;

Problema das torres de Hanoi : Considerando três torres, o objetivo é transferir três discos que estão na torre A

para a torre C, usando uma torre B como auxiliar. Somente o último disco de cima de uma pilha pode ser deslocado

para outra, e um disco maior nunca pode ser colocado sobre um menor.

Dessa forma, para mover n discos da torre A para a torre C, usando a torre B como auxiliar, fazemos:

se n = 1

mova o disco de A para C

senão

transfira n-1 discos de A para B, usando C como auxiliar

mova o último disco de A para C

transfira n-1 discos de B para C, usando A como auxiliar

A B C

A B C

Primeira etapa: se n = 1 mova o disco de A para C (sem auxiliar) (origem para destino) senão transfira n-1 discos de A (origem) para B (destino), usando C (auxiliar) mova disco n de A para C (origem para destino) transfira n-1 discos de B (origem) para C (destino), usando A (auxiliar) Segunda etapa: Como a passagem dos parâmetros é sempre: torre A, torre B (auxiliar) e torre C, pois esta é a seqüência das 3 torres, os parâmetros devem ser manipulados na chamada recursiva da rotina Hanoi, respeitando a lógica dos algoritmos:

Programa principal:

início limpa tela hanoi(3,'A','B','C') fim

Rotina Hanoi:

Hanoi (int n, char origem, auxiliar, destino); início se (n=1) então Escrever (“1. Mova disco 1 da torre”, origem, “ para ” , destino) senão Escrever (“2.”) Hanoi( n-1 , origem , destino , auxiliar) Escrever (“3. Mova disco” ,n, “da torre”, origem, “ para ” ,destino) Hanoi( n-1 , auxiliar , origem , destino) fim_se fim

Com a chamada hanoi(3,'A','B','C'), o programa produzirá a seguinte saída:

1. Mova disco 1 da torre A para C

3. Mova disco 2 da torre A para B

1. Mova disco 1 da torre C para B

3. Mova disco 3 da torre A para C

1. Mova disco 1 da torre B para A

3. Mova disco 2 da torre B para C

1. Mova disco 1 da torre A para C

Série de Fibonacci A série de Fibonacci é a seguinte: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... Para fins de implementação, a série inicia com fib(0) e cada termo n é referenciado por fib(n-1), ou seja o 1o^ termo é fib(0), o 2o^ termo é fib(1) e assim sucessivamente. Com exceção dos dois primeiros, cujos valores são pré- determinados (0 e 1), cada elemento da seqüência sempre será a soma dos dois elementos anteriores. Ex: fib(2) = 0+1 = 2, fib(3) = 1+1 = 2, fib(4) = 2+1 = 3, fib(5) = 2+3 = 5, ... Podemos então definir a série de Fibonacci através da seguinte definição recursiva: Fib(n) = n se n== 0 ou n== Fib(n) = Fib(n-2) + fib(n-1) se n >= Dessa forma, Fib(4) seria então: Fib(4) = Fib(2) + Fib(3) = Fib(0) + Fib(1) + Fib(3) = 0 + 1 + Fib(3) = 1 + Fib(1) + Fib(2) = 1 + 1 + Fib(0) + Fib(1) = 2 + 0 + 1 = 3 A C A B C A C A B C A B C

fib(0) fib(1) fib(2) fib(3) fib(4) fib(5) fib(6) ...

  1. Complete: a) Entrada: O,A,D,D,S Seqüência: Saída: D,A,D,O,S b) Entrada: E,E,X,E,C,E,L,N,T Seqüência: Saída: E,X,C,E,L,E,N,T,E
  2. Complete: a) Entrada: Seqüência: I,I,R,I,I,R,R,I,R Saída: D,A,D,O,S b) Entrada: Seqüência: I,I,I,R,I,I,I,R,I,I,I,R,R,R,R,R,R,R Saída: E,X,C,E,L,E,N,T,E

Implementação :

Pilha(0) Pilha(1) Pilha(2) Pilha(3) Pilha(4) Pilha(5)

Topo Início Fim Inserção

Retirada

Para implementarmos uma pilha seqüencial precisamos de uma variável (Topo) indicando o endereço da mais recente informação colocada na pilha. Por convenção, se a pilha está vazia, o Topo = -1. O tamanho da Pilha é limitado pelo tamanho do vetor.

Typedef struct PILHA {

int topo;

char dados[10];

PILHA p;

3.1.1. Inserção

Na inserção deve-se:

* incrementar o topo da pilha

* armazenar a informação X na nova área

3.1.2 Retirada Na retirada deve-se:

  • obter o valor presente em pilha.topo ou pilha->topo.
  • decrementar o topo da pilha

O que acontece ao se inserir uma informação quando já

usamos toda a área disponível do vetor (topo = fim)?

Resposta: ocorre uma situação denominada __________

Algoritmo de inserção:

O que acontece quando tentamos retirar um elemento de uma

pilha que já está vazia?

Resposta: ocorre uma situação denominada _____________

Algoritmo de retirada:

#include

#define TAM 10

using namespace std;

typedef struct {

int topo;

int dados [TAM];

} PILHA;

PILHA p1;

... Implemente o resto.

Como poderia ser feita a inserção e retirada no caso de se utilizar mais do que uma pilha?

  • deve-se utilizar ponteiros para indicar qual das pilhas será manipulada e qual é o seu endereço. Diferenças entre o algoritmo que manipula uma pilha e o algoritmo que manipula várias pilhas: a) deve-se passar 2 parâmetros na inserção: a pilha em que o elemento será inserido e o elemento a ser inserido; b) deve-se informar a pilha da qual o elemento será retirado; c) na chamada das funções insere e retira deve-se passar o endereço da estrutura e dentro da função deve-se indicar que está vindo um ponteiro; d) deve se usar a referência pilha->topo ao invés de pilha.topo.

Pilha p1,p2,p3;

void empilha (struct stack *p, int x){

int desempilha(struct stack *p ){

int main ( void ){

int x;

pilha1.topo=-1; pilha2.topo=-1; pilha3.topo=-1;

empilha(&pilha1,4); empilha(&pilha2,2); empilha(&pilha3,7);

x=desempilha(&pilha1); cout << x << endl;

x=desempilha(&pilha1); cout << x << endl;

x=desempilha(&pilha2); cout << x << endl;

return (0);

Implemente em laboratório:

  1. Implemente a rotina empilha e a rotina desempilha, demonstrando visualmente como fica a estrutura (pilha) após cada uma das operações. Obs.: Implemente a pilha sobre um vetor com 10 posições.
  2. Construa um programa que leia 10 valores e empilha-os conforme forem pares ou ímpares na pilha1 e pilha respectivamente. No final desempilhe os valores de cada pilha mostrando-os na tela. 3.1.3 Utilização pratica de pilhas - Avaliação de Expressões Agora que definimos uma pilha e indicamos as operações que podem ser executadas sobre ela, vejamos como podemos usar a pilha na solução de problemas. Examine uma expressão matemática que inclui vários conjuntos de parênteses agrupados. Por exemplo:

7-(( X *((X + Y) / (J - 3)) + Y) / (4 - 2.5))

Queremos garantir que os parênteses estejam corretamente agrupados, ou seja, desejamos verificar se: a) Existe um número igual de parênteses esquerdos e direitos. Expressões como “A((A + B)” ou “A + B(” violam este critério. b) Todo parêntese da direita está precedido por um parêntese da esquerda correspondente. Expressões como “)A+B(-C” ou “(A+B))-(C+D” violam este critério.

UM EXEMPLO: INFIXO, POSFIXO E PREFIXO Esta seção examinará uma importante aplicação que ilustra os diferentes tipos de pilhas e as diversas operações e funções definidas a partir delas. O exemplo é, em si mesmo, um relevante tópico de ciência da computação. Considere a soma de A mais B. Imaginamos a aplicação do operador "+" sobre os operandos A e B, e escrevemos a soma como A + B. Essa representação particular é chamada infixa. Existem três notações alternativas para expressar a soma de A e B usando os símbolos A, B e +. São elas:

    • A B prefixa
  • A + B infixa
  • A B + posfixa Os prefixos "pre", "pos" e "in" referem-se à posição relativa do operador em relação aos dois operandos. Na notação prefixa, o operador precede os dois operandos; na notação posfixa, o operador é introduzido depois dos dois operandos e, na notação infixa, o operador aparece entre os dois operandos. Na realidade, as notações prefixa e posfixa não são tão incômodas de usar como possam parecer a princípio. Por exemplo, uma função em C para retornar a soma dos dois argumentos, A e B, é chamada por Soma(A, B). O operador Soma precede os operandos A e B. Examinemos agora alguns exemplos adicionais. A avaliação da expressão A + B * C, conforme escrita em notação infixa, requer o conhecimento de qual das duas operações, + ou *, deve ser efetuada em primeiro lugar. No caso de + e *, "sabemos" que a multiplicação deve ser efetuada antes da adição (na ausência de parênteses que indiquem o contrário). Sendo assim, interpretamos A + B * C como A + (B * C), a menos que especificado de outra forma. Dizemos, então, que a multiplicação tem precedência sobre a adição. Suponha que queiramos rescrever A + B * C em notação posfixa. Aplicando as regras da precedência, converteremos primeiro a parte da expressão que é avaliada em primeiro lugar, ou seja a multiplicação. Fazendo essa conversão em estágios, obteremos: A + (B * C) parênteses para obter ênfase A + (BC *) converte a multiplicação A (BC *) + converte a adição ABC * + forma posfixa As únicas regras a lembrar durante o processo de conversão é que as operações com a precedência mais alta são convertidas em primeiro lugar e que, depois de uma parte da expressão ter sido convertida para posfixa, ela deve ser tratada como um único operando. Examine o mesmo exemplo com a precedência de operadores invertida pela inserção deliberada de parênteses. (A + B) * C forma infixa (AB +) * C converte a adição (AB +) C * converte a multiplicação AB + C * forma posfixa Nesse exemplo, a adição é convertida antes da multiplicação por causa dos parênteses. Ao passar de (A + B) * C para (AB +) * C, A e B são os operandos e + é o operador. Ao passar de (AB +) * C para (AB +)C *, (A.B +) e C são os operandos e * é o operador. As regras para converter da forma infixa para a posfixa são simples, desde que você conheça as regras de precedência. Consideramos cinco operações binárias: adição, subtração, multiplicação, divisão e exponenciação. As quatro primeiras estão disponíveis em C e são indicadas pelos conhecidos operadores +, -, * e /. A quinta operação, exponenciação, é representada pelo operador ^. O valor da expressão A ^ B é A elevado à potência de B, de maneira que 3 ^ 2 é 9. Veja a seguir a ordem de precedência (da superior para a inferior) para esses operadores binários: exponenciação multiplicação/divisão adição/subtração

Quando operadores sem parênteses e da mesma ordem de precedência são avaliados, pressupõe-se a ordem da esquerda para a direita, exceto no caso da exponenciação, em que a ordem é supostamente da direita para a esquerda. Sendo assim, A + B + C significa (A + B) + C, enquanto A ^ B ^ C significa A ^ (B ^ C). Usando parênteses, podemos ignorar a precedência padrão. Uma questão imediatamente óbvia sobre a forma posfixa de uma expressão é a ausência de parênteses. Examine as duas expressões, A + (B * C) e (A + B) * C. Embora os parênteses em uma das expressões sejam supérfluos [por convenção, A + B * C = A + (B * C)], os parênteses na segunda expressão são necessários para evitar confusão com a primeira. As formas posfixas dessas expressões são: Forma Infixa Forma Posfixa

A+(B*C) ABC *+

(A+B)C AB+C

Considerando a expressão: a/b^c + de - ac Os operandos são: Os operadores são: O primeiro passo a saber é qual a ordem de prioridade dos operadores:

Operador Prioridade

^, (+ e -) unário 6

AND 2

OR 1

Após, basta utilizarmos uma pilha para transformarmos a expressão Ex: A+BC em ABC+

Elemento Pilha Saída

A - A

+ + A

B + AB

* +* AB

C +* ABC

No final, basta juntarmos a saída ao que restou da pilha (desempilhando um a um) à saída: ABC * + Note que em (1) o operador * foi colocado na pilha sobre o operador +. Isso se deve ao fato de que a prioridade do * é maior do que a do +. Sempre que a prioridade de um elemento a empilhar for maior do que a prioridade do último elemento da pilha, esse elemento deverá ser empilhado. Quando a prioridade do elemento a empilhar for menor ou igual ao último elemento que está na pilha, deve-se então adotar o seguinte procedimento: desempilhar elementos até que fique como último elemento da pilha, algum elemento com prioridade menor do que o elemento que se está empilhando. Caso não houver na pilha nenhum elemento com prioridade menor do que o elemento que se está empilhando, fica somente o elemento que se está empilhando. Os demais saem para a saída.

^ ficam

* fica fica / *

Entra: + Entra: - Entra: * Saem: *- Sai: * Saem: ^ /

Início da solução Alguns passos devem ser considerados aqui para a resolução do problema: ● Leia o Problema cuidadosamente: leia cada linha do problema cuidadosamente. Após desenvolver a solução, leia atentamente a descrição do erro. Confira atentamente as entradas e saídas do problema. ● Não pressuponha entradas: sempre há um conjunto de entradas para exemplo de um problema. Para testar, deve-se utilizar mais casos para entrada, números negativos, números longos, números fracionários, strings longas. Toda entrada que não for explicitamente proibida é permitida. Deve se cuidar a ordem de entrada também, pois quando pede-se por exemplo para verificar um intervalo entre 2 números, estes 2 números podem estar em ordem crescente ou decrescente e assim por diante. ● Não se apressar: nem sempre a eficiência é fundamental, portanto não deve-se preocupar com isso a menos que isso seja um predicado do problema. Deve-se ler a especificação para aprender o máximo possível sobre o tamanho da entrada e decidir qual algoritmo pode resolver o problema para aquela determinada entrada de dados. Neste caso específico a eficiência não precisa ser uma grande preocupação. Sugestão para implementação Existem muitas maneiras diferentes para verificar a prioridade de um elemento no trabalho de implementação. Uma sugestão seria criar uma estrutura com 2 vetores ELEM[10] e PRI[10]. Procura-se inicialmente o operador no vetor ELEM. Ao encontrá-lo, pega-se a prioridade na mesma posição do vetor PRI. Exemplos:

Rotina para testar prioridade

#include

#include

using namespace std;

int main(void){

string operador;

string expr = "|.><=#+-*/^";

const int prior[11]= {1,2,3,3,3,3,4,4,5,5,6};

int prioridade=-1;

cin >> operador;

for (int i=0; i<=10; i++) {

if (expr.substr(i,1)==operador)

prioridade = prior[i];

cout << prioridade;

return (0);

Operando.cpp – Teste para ver se é um operando – (Letras maiúsculas, minúsculas e números)

//Obs: esta rotina deve ser usada em conjunto com a rotina “prioridade”. Inicialmente, faz a leitura da expressão.

Por exemplo a+b*c%. Faz-se então a verificação de cada caracter da entrada individualmente. O “ a ”, por

exemplo, é um operando válido. Então ele deve ir direto para a saída. No caso do “ + ”, que é o segundo elemento

da entrada, a rotina retorna que não é um operando válido. Então, deve-se passá-lo como parâmetro da rotina

“prioridade” para verificar se o mesmo é um operador. Em caso afirmativo, deve-se proceder com o

empilhamento ou desempilhamento do mesmo, conforme o caso. Ao testar um caracter inválido, por exemplo o

“ % ”, inicialmente verifica-se que não é um operando válido (rotina “operando”). Em seguida verifica-se também

que não é um operador válido (rotina “prioridade). Neste caso, o software deve retornar um erro

“operando/operador inválido!”

#include

#include

using namespace std;

int main(void){

const string VALIDOS (“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”);

string entrada;

cin >> entrada; //getline (cin,entrada) para o caso de permitir espaços em branco

for (int i=0; i < entrada.length(); i++) {

if (VALIDOS.find(entrada[i],0) != -1)

cout << entrada[i] << “ é um operando válido”;

else

cout << entrada[i] << “ não é operando válido: (operador, parênteses ou erro)!”;

return (0);

3.2. FILA (QUEUE) Filas mantém a ordem (first in, first out). Um baralho com cartas pode ser modelado como uma fila, se retirarmos as cartas em cima e colocarmos as carta de volta por baixo. Outro exemplo seria a fila de um banco. As operações de inserção são efetuadas no final e as operações de retirada são efetuadas no início. A inserção de um elemento torna-o último da lista (fila). As operações abstratas em uma fila incluem:

  • Enqueue(x,q): insere o item x no fim da fila q.
  • Dequeue(q): retorna e remove o item do topo da fila q.
  • Inicialize(q): cria uma fila q vazia.
  • Full(q), Empty(q): testa a fila para saber se ela está cheia ou vazia. Filas são mais difíceis de implementar do que pilhas, porque as ações ocorrem em ambas as extremidades. A implementação mais simples usa um vetor, inserindo novos elementos em uma extremidade e retirando de outra e depois de uma retirada movendo todos os elementos uma posição para a esquerda. Bem, pode-se fazer melhor utilizando os índices first ( frente) e last (re) ao invés de fazer toda a movimentação. Ao se usar estes índices, fica uma área não usada no início da estrutura. Isso pode ser resolvido através de uma fila circular, onde após a inserção na última posição, inicia-se novamente a inserção pelo início da fila, se tiver posição disponível, é claro. Dessa forma, teremos a seguinte estrutura:

typedef struct {

int q[SIZE+1];

int first;

int last;

int count;

} queue;

queue q;

Uma fila com 5 elementos, sem nenhum deles ter sido retirado é apresentada abaixo: fila.dados: A F C G B queue.first queue.last pseudo-algoritmo de inserção de uma fila:

void insere_fila (valor) {

se fila->count > SIZE então

OVERFLOW();

senão

fila.last++;

fila.dados[fila.last]=valor;

fila.count++;

fim_se

pseudo-algoritmo de retirada de uma fila:

int retra_fila (void) {

se fila.count <= 0 então

UNDERFLOW();

senão

char x = fila.dados[fila.first];

fila.first++;

fila.count--;

retornar(x);

fim_se

/* Queue.cpp. Baseado no algoritmo queue.c do livro do Steven Skiena (2002) e atualizado por Neilor Tonin em 03/01/2008. Implementation of a FIFO queue abstract data type. */

Quando a carta virada dos dois jogadores tem o mesmo valor acontece então a guerra. Estas cartas ficam na mesa e cada jogador joga mais duas cartas. A primeira com a face para baixo, e a segunda com a face para cima. A carta virada com a face para cima que for maior vence, e o jogador que a jogou leva as 6 cartas que estão na mesa. Se as cartas com a face para cima de ambos os jogadores tiverem o mesmo valor, a guerra continua e cada jogador volta a jogar uma carta com a face para baixo e outra com a face para cima. Se algum dos jogadores fica sem cartas no meio da guerra, o outro jogador automaticamente vence. As cartas são adicionadas de volta para os jogadores na ordem exata que elas foram distribuídas, ou seja a primeira carta própria (jogada pelo próprio jogador), a primeira carta do oponente, a segunda carta própria, a segunda carta jogada pelo oponente e assim por diante... Como qualquer criança de 5 anos, sabe-se que o jogo de guerra pode levar um longo tempo para terminar. Mas quanto longo é este tempo? O trabalho aqui é escrever um programa para simular o jogo e reportar o numero de movimentos. Construção do monte de cartas Qual a melhor estrutura de dados para representar um monte de cartas? A resposta depende do que se quer fazer com elas. Está se tentando embaralhá-las? Comparar seus valores? Pesquisar por um padrão na pilha. As intenções é que vão definir as operações na estrutura de dados. A primeira ação que necessitamos fazer é dar as cartas do topo e adicionar outras na parte de baixo do monte. Portanto, é natural que cada jogador utilize uma fila (estrutura FIFO) definida anteriormente. Mas aqui têm-se um problema fundamental. Como representar cada carta? Têm-se as naipes (Paus, Copas, Espada e Ouro) e valores na ordem crescente (2-10, valete, rainha, rei e Ás). Têm-se diversas escolhas possíveis. Pode-se representar cada carta por um par de caracteres ou números especificando a naipe e valor. No problema da GUERRA, pode-se ignorar as naipes – mas tal pensamento pode trazer um problema. Como saber que a implementação da Fila está funcionando perfeitamente? A primeira operação na GUERRA é comparar o valor da face das cartas. Isso é complicado de fazer com o primeiro caracter da representação, porque deve-se comparar de acordo com o ordenamento histórico dos valores da face. Uma lógica Had Hoc parece necessária para se lidar com este problema. Cada carta deverá ter um valor de 0 a 13. Ordena-se os valores das cartas do menor ao maior, e nota-se que há 4 cartas distintas de cada valor. Multiplicação e divisão são a chave para mapeamento de 0 até 51. #include #include #include #include "queue2.h" //Biblioteca com a estrutura da fila e suas funções #define NCARDS 52 //cartas #define NSUITS 4 //Naipes #define TRUE 1 #define FALSE 0 #define MAXSTEPS 100000 //Define o número máximo de jogadas using namespace std; char values[] = "23456789TJQKA"; char suits[] = "pceo"; // (p)aus (c)opas (e)spadas e (o)uros char value( int card){ return ( values[card/NSUITS] ); } void print_card_queue(queue *q) { int i=q->first,j; while (i != q->last) { cout << value(q->q[i])<< suits[q->q[i] % NSUITS]; i = (i+ 1 ) % QUEUESIZE; } cout << value(q->q[i]) << suits[q->q[i] % NSUITS]; } void clear_queue(queue *a, queue *b){ while (!empty(a)) enqueue(b,dequeue(a)); }

void embaralha ( int perm[NCARDS+ 1 ]){ randomize(); int a,b,i,aux; for (i = 0 ; i <= 20 ; i++){ a= rand()% 52 ; b= rand()% 52 ; aux = perm[a]; perm[a]=perm[b]; perm[b]=aux; } } void random_init_decks(queue *a, queue *b){ int i; // counter int perm[NCARDS+ 1 ]; for (i= 0 ; i<NCARDS; i=i+ 1 ) { perm[i] = i; } embaralha(perm); init_queue(a); init_queue(b); for (i= 0 ; i<NCARDS/ 2 ; i=i+ 1 ) { enqueue(a,perm[ 2 *i]); enqueue(b,perm[ 2 *i+ 1 ]); } //cout << endl << "CARTAS: " << endl; //print_card_queue(a); //cout << endl << "CARTAS: " << endl; //print_card_queue(b); } void war(queue *a, queue b) { int steps= 0 ; / step counter / int x,y; / top cards / queue c; / cards involved in the war / bool inwar; / are we involved in a war? */ inwar = FALSE; init_queue(&c); while ((!empty(a)) && (!empty(b) && (steps < MAXSTEPS))) { print_card_queue(a); cout << endl; print_card_queue(b); cout << endl << endl; steps = steps + 1 ; x = dequeue(a); y = dequeue(b); // x e y possuem valores de 0 até 51 cout << x <<":"<< value(x) <<suits[x%NSUITS] <<" "<<y<<":"<<value(y)<< suits[y%NSUITS] << endl; enqueue(&c,x); enqueue(&c,y); if (inwar) { inwar = FALSE; } else { if (value(x) > value(y)) clear_queue(&c,a); else if (value(x) < value(y)) clear_queue(&c,b); else if (value(y) == value(x)) inwar = TRUE; } } cout << "Cartas nas pilhas A: " << a->count << " B: " << b->count << endl; if (!empty(a) && empty(b)) cout << "a venceu em " << steps << " jogadas " << endl; else if (empty(a) && !empty(b)) cout << "b venceu em " << steps << " jogadas " << endl; else if (!empty(a) && !empty(b)) cout << "jogo empatado apos " << steps << " jogadas" << endl; else cout << "jogo empatado apos " << steps << " jogadas" << endl; } int main(){ queue a,b; int i; random_init_decks(&a,&b); war(&a,&b); return ( 0 ); } Problemas (exercícios) complementares 10038, 10315, 10050, 843, 10205, 10044, 10258 (páginas 42 até 54) - livro Steven Skiena e Miguel Revilla (http://icpcres.ecs.baylor.edu/onlinejudge/)

4.2 Seleção Direta Consiste em encontrar a menor chave por pesquisa sequencial. Encontrando a menor chave, essa é permutada com a que ocupa a posição inicial do vetor, que fica então reduzido a um elemento. O processo é repetido para o restante do vetor, sucessivamente, até que todas as chaves tenham sido selecionadas e colocadas em suas posições definitivas. Uma outra variação deste método consiste em posicionar-se no primeiro elemento e aí ir testando-o com todos os outros (segundo)... (último), trocando cada vez que for encontrado um elemento menor do que o que está na primeira posição. Em seguida passa-se para a segunda posição do vetor repetindo novamente todo o processo. Ex: ... Exercício: considerando o vetor: 9 25 10 18 5 7 15 3 Ordene-o pelo método de seleção direta: 4.3 Inserção Direta O método de ordenação por Inserção Direta é o mais rápido entre os outros métodos considerados básicos – Bubblesort e Seleção Direta. A principal característica deste método consiste em ordenarmos o arranjo utilizando um sub-arranjo ordenado localizado em seu inicio, e a cada novo passo, acrescentamos a este sub-arranjo mais um elemento, até que atingimos o último elemento do arranjo fazendo assim com que ele se torne ordenado. Realmente este é um método difícil de se descrever, então vamos passar logo ao exemplo. Consideremos inicialmente um arranjo qualquer desordenado: Inicialmente consideramos o primeiro elemento do arranjo como se ele estivesse ordenado, ele será considerado o o sub-arranjo ordenado inicial :

Agora o elemento imediatamente superior ao o sub-arranjo ordenado, no o exemplo o número 3, deve se copiado para uma variável auxiliar qualquer. Após copiá-lo, devemos percorrer o sub-arranjo a partir do último elemento para o primeiro. Assim poderemos encontrar a posição correta da nossa variável auxiliar dentro do sub-arranjo : No caso verificamos que a variável auxiliar é menor que o último elemento do o sub-arranjo ordenado ( o o sub- arranjo só possui por enquanto um elemento, o número 5 ). O número 5 deve então ser copiado uma posição para a direita para que a variável auxiliar com o número 3, seja colocada em sua posição correta : Verifique que o sub-arranjo ordenado possui agora dois elementos. Vamos repetir o processo anterior para que se continue a ordenação. Copiamos então mais uma vez o elemento imediatamente superior ao o sub-arranjo ordenado para uma variável auxiliar. Logo em seguida vamos comparando nossa variável auxiliar com os elementos do sub- arranjo, sempre a partir do último elemento para o primeiro : Neste caso verificamos que a nossa variável auxiliar é menor que o último elemento do sub-arranjo. Assim, copiamos este elemento para a direita e continuamos com nossas comparações : Aqui, mais uma vez a nossa variável auxiliar é menor que o elemento do sub-arranjo que estamos comparando. Por isso ele deve ser copiado para a direita, abrindo espaço para que a variável auxiliar seja colocada em sua posição correta : Verifique que agora o sub-arranjo ordenado possui 3 elementos. Continua-se o processo de ordenação copiando mais uma vez o elemento imediatamente superior ao o sub-arranjo para a variável auxiliar. Logo em seguida vamos comparar essa variável auxiliar com os elementos do o sub-arranjo a partir do último elemento : Veja que nossa variável auxiliar é menor que o elemento que está sendo comparado no o sub-arranjo. Então ele deve ser copiado para a direita para que continuemos com nossas comparações :