You are on page 1of 13

Algoritmos e Estruturas de Dados I 5955026

Departamento de Computação e Matemática 1º Semestre/2019


Prof. Dr. José Augusto Baranauskas 2ª Lista de Exercícios

1. Considerando o contexto de estrutura de dados:


a) O que é Abstract Data Type (ADT)?
b) O que é uma ADT Pilha?
c) Cite um exemplo de uso de uma ADT Pilha.

2. Desenhe a sequência de empilhamento de caixas (exemplo visto em aula) mostrando cada fase dos
seguintes segmentos de código (S é uma pilha de caracteres e x, y, z são variáveis).

a) S.push(´a´); b) S.push(´a´); c) S.push(´a´); d) S.push(´a´);


S.push(´b´); S.push(´b´); S.push(´b´); S.push(´b´);
S.push(´c´); S.push(´c´); S.clear(); S.push(´c´);
S.pop(x); S.pop(x); S.push(´c´); while (! S.empty())
S.pop(y); S.pop(y); S.pop(x); S.pop(x);
S.pop(z); S.push(x); S.push(´a´);
S.push(y); S.pop(y);
S.pop(z); S.push(´b´);
S.pop(z);

3. Utilizando a representação contígua de uma pilha, formule os seguintes métodos:


a) toString que transforma todo o conteúdo da pilha em uma sequencia de caracteres, iniciando pelo
topo, sem alterá-la;
b) print que imprime todo o conteúdo da pilha (dica: você pode ativar o método anterior para isso);
c) get para obter seu i-ésimo elemento sem eliminá-lo;
d) change para alterar o valor do i-ésimo elemento contido na pilha para o valor contido na variável x;

4. Usando o método print definido em exercício anterior, execute as sequências de comandos do exercício
2, colocando entre cada comando uma chamada ao método print. Confira com a representação (desenho)
que você fez.

5. Considerando a representação contígua de uma pilha, a implementação vista em aula inicia a inserção de
elementos a partir do elemento de índice um no vetor, desperdiçando um item (índice zero). Implemente
os métodos de manipulação de pilhas em C++ considerando que:
a) os elementos são armazenados a partir do elemento de índice zero;
b) os elementos são armazenados a partir do final do vetor (assim, a pilha cresce do final do vetor em
direção ao início).
Houve alteração na complexidade dos algoritmos em relação à implementação vista em aula?

6. Como visto em aula, duas possíveis formas de implementação de uma ADT Pilha são: estática (contígua)
e dinâmica (encadeada).
a) Cite uma diferença entre uma ADT Pilha estática e dinâmica.
b) Cite uma vantagem e uma desvantagem das implementações estática e dinâmica.

7. Sobre os métodos Stack() e ~Stack() responda as questões abaixo:


a) O que é o método Stack() e quando é utilizado?
b) O que é o método ~Stack() e quando é utilizado?
c) O método ~Stack() não precisa ser implementado para uma pilha contígua. Por quê?

8. Elabore os mesmos métodos solicitados no exercício 3 (toString, print, get e change) para pilhas
encadeadas.

9. Considerando a implementação encadeada de pilhas, altere a implementação do finalizador ~Stack() de


modo mais eficiente, usando ponteiro diretamente, ou seja, sem utilizar os métodos já existentes como

1
empty e pop. Altere também a implementação para incluir um campo que armazena a quantidade de
elementos existentes na pilha.

10. Em uma pilha encadeada, o método full sempre retorna false. Por quê?

11. Considere o conjunto infinito de cadeias:

c, aca, bcb, abcba, bacab, abbcbba, abacaba, aabcbaa, ...

Uma cadeia típica neste conjunto pode ser especificada como wcwr, onde w contém a sequência de a’s e
b’s e wr é o reverso de w. Por exemplo, se w = ’ab’, então wr = ’ba’. Dada uma cadeia x de entrada,
formular um programa C++ que usa uma pilha para determinar se x pertence ou não ao conjunto de
cadeias, como descrito por wcwr.

12. Implemente e teste o procedimento reverseRead visto em aula usando uma pilha contígua.

13. Altere o programa reverseRead do exercício anterior para utilizar a pilha encadeada. Houve alguma
alteração no programa? Qual?

14. Escreva um programa C++ usando pilhas que leia uma linha e verifique se todos os parênteses, colchetes
e chaves encontrados estão apropriadamente em pares, ou seja, para cada (, [ e { deve haver um ), ] e }
do mesmo tipo corretamente aninhado. Se uma construção do tipo (... {... )... } é encontrada ou se um dos
símbolos não possui seu par correspondente, o programa deve escrever uma mensagem de erro,
indicando a posição na linha onde foi encontrada a falha.

15. Seja S uma pilha de inteiros e x um inteiro. Use os métodos push, pop, empty e full para escrever
procedimentos que realizem as seguintes tarefas (declare variáveis e uma segunda pilha se seus
procedimentos precisarem):
a) Faça x ser o novo topo da pilha S e deixe o antigo topo de S sem alteração.
b) Coloque x como o terceiro elemento a partir do topo de S, desde que S possua ao menos 3 inteiros.
Se não, atribua 9999 a x e deixe S inalterada.
c) Coloque x como o último elemento da pilha (fundo) ou atribua 9999 a x se S estiver vazia, deixando-
a inalterada. Dica: use uma segunda pilha.
d) Remova todas as ocorrências de x em S, deixando os demais elementos de S na mesma ordem.

16. Uma importante função teórica, conhecida como função de Ackermann é definida como:

+1 se = 0
, = − 1,1 se = 0
− 1, , −1 caso contrário

a) Defina uma função recursiva para esta função;


b) Defina uma função não recursiva para esta função. (Sugestão: use uma pilha para armazenar os
resultados intermediários).

17. Em alguns casos, um programa requer o uso de duas pilhas contendo o mesmo tipo de dados. Se duas
pilhas são armazenadas em arrays separados, então uma pilha pode estourar enquanto a outra possui
espaço não utilizado disponível. Uma maneira de evitar esse problema consiste em colocar as duas pilhas
num único array, sendo que uma pilha cresce da direita para a esquerda e a outra pilha cresce da
esquerda para a direita, uma em direção à outra. Assim, se uma pilha cresce demais e a outra não, as duas
caberão no array e não haverá estouro até que todo o espaço realmente seja usado. Declare um novo tipo
DoubleStack que inclui um array e dois índices topA e topB e escreva as operações neste tipo de pilha
bem como pushA, pushB, popA e popB para manipular duas pilhas dentro de uma DoubleStack.

...
→ TopA TopB ←

2
18. Considere uma rede de desvio ferroviário:

1, 2, ..., n

Carros ferroviários numerados 1, 2, 3, ..., n estão do lado direito. Cada carro é trazido dentro da pilha e
removido a qualquer momento. Por exemplo, se n = 3, pode-se mover para dentro o primeiro, depois o
segundo e depois o terceiro. Então, em seguida, pode-se retirar os carros produzindo a nova sequência 3,
2, 1. Quais são as possíveis permutações, para n=3 e 4 que pode-se obter para os carros? Existem
permutações que não sejam possíveis? Tente encontrar uma fórmula para o número de movimentos
necessários para inverter a ordem de n carros.

3
Resolução de Alguns Exercícios

1. Considerando o contexto de estrutura de dados:


a. O que é Abstract Data Type (ADT)?
É um conjunto de dados (ou itens) e operações (ou procedimentos ou métodos) que atuam
especificamente sobre esses dados, juntamente com seu encapsulamento (proteção).

b. O que é uma ADT Pilha?


É um tipo de ADT que permite inserir e remover elementos em uma única extremidade,
denominada topo. Essa regra também é denominada LIFO (Last-In-First-Out).

c. Cite um exemplo de uso de uma ADT Pilha.


Avaliação de expressões aritméticas, chamada e retorno de procedimentos e funções, busca
exaustiva (backtracking).

2. Desenhe a sequência de empilhamento de caixas (exemplo visto em aula) mostrando cada fase dos
seguintes segmentos de código (S é uma pilha de caracteres e x, y, z são variáveis).

a) S.push(´a´); b) S.push(´a´); c) S.push(´a´); d) S.push(´a´);


S.push(´b´); S.push(´b´); S.push(´b´); S.push(´b´);
S.push(´c´); S.push(´c´); S.clear(); S.push(´c´);
S.pop(x); S.pop(x); S.push(´c´); while (! S.empty())
S.pop(y); S.pop(y); S.pop(x); S.pop(x);
S.pop(z); S.push(x); S.push(´a´);
S.push(y); S.pop(y);
S.pop(z); S.push(´b´);
S.pop(z);
2. a)

2. b)

4
2. c)

2. d)

3. Utilizando a representação contígua de uma pilha, formule os seguintes métodos:


a. toString que transforma todo o conteúdo da pilha em uma sequência de caracteres, iniciando
pelo topo, sem alterá-la;

#include <sstream>
string Stack::toString()
// pre: Nenhuma
// pos: Retorna Stack no formato de string,
// Por exemplo, Stack contendo elementos 4,10,8
// retorna [4,10,8]
{ int i;
stringstream ss;
ss << "[";
for(i = 1; i <= top; i++)
{ ss << entry[i];
if( i != top )
ss << ",";
}
ss << "]";
return ss.str();
}

b. print que imprime todo o conteúdo da pilha (dica: você pode ativar o método anterior para
isso);
#include <iostream>
string Stack::print()
// pre: Nenhuma
// pos: Imprime Stack no formato de string
{ cout << toString() << endl;
}

5
c. get para obter seu i-ésimo elemento sem eliminá-lo;

#include <cstdlib>
StackEntry Stack::get(int i)
// pre: 1 <= i <= top
// pos: Retorna i-ésimo elemento da Stack sem modificá-la
{ if (i < 1 || i > top)
{ cout << "Indice invalido" << endl;
abort();
}
return entry[i];
}

d. change para alterar o valor do i-ésimo elemento contido na pilha para o valor contido na
variável x;

#include <cstdlib>
bool Stack::change(int i, StackEntry x)
// pre: 1 <= i <= top
// pos: Se for possível alterar i-ésimo elemento da Stack
// pelo valor x, retorna true; caso contrário, false.
{ if (i < 1 || i > top)
return false;

entry[i] = x;
return true;
}

4. (sem resolução)

5. Considerando a representação contígua de uma pilha, a implementação vista em aula inicia a inserção de
elementos a partir do elemento de índice um no vetor, desperdiçando um item (índice zero). Implemente
os métodos de manipulação de pilhas em C++ considerando que:
a. os elementos são armazenados a partir do elemento de índice zero;
//arquivo Stack.h
Class Stack{
public:
//….
private:
//…
StackEntry entry[MaxStack]; //remover o +1
};

//arquivo Stack.cpp
bool Stack::empty(){
return (top == -1);
}
void Stack::clear(){
top = -1;
}
int Stack::size(){
return top+1;
}
//as demais implementações continuam iguais.

b. os elementos são armazenados a partir do final do vetor (assim, a pilha cresce do final do
vetor em direção ao início).
//arquivo Stack.cpp
Stack::Stack(){ //método construtor
top = MaxStack+1;
}

6
bool Stack::empty(){
return (top == MaxStack+1);
}
bool Stack::full(){
return (top == 0);
}
void Stack::push(int x){
if (full()){
cout << "Pilha cheia";
abort();
}
top--;
entry[top] = x;
}
void Stack::pop(int &x){
if (empty()){
cout << "Pilha vazia" << endl;
abort();
}
x = entry[top];
top++;
}
void Stack::clear(){
top = MaxStack+1;
}

int Stack::size(){
return MaxStack+1 - top;
}
Houve alteração na complexidade dos algoritmos em relação à implementação vista em aula? Não

6. Como visto em aula, duas possíveis formas de implementação de uma ADT Pilha são: estática (contígua)
e dinâmica (encadeada).
a. Cite uma diferença entre uma ADT Pilha estática e dinâmica.
Op1: Estática possui tamanho fixo, dinâmica possui tamanho variável de acordo com a
necessidade.
Op2: Estática armazena os elementos de forma sequencial (contíguo) no espaço alocado de
memória, dinâmica cada elemento pode estar distante do seu próximo (não contíguo).

b. Cite uma vantagem e uma desvantagem das implementações estática e dinâmica.


Vantagem Desvantagem
Estática Simples de implementar, rápido limitação tamanho máximo,
acesso. fragmentação.
Dinâmica Simples de aumentar e reduzir Confiabilidade (quebra de
tamanho, sem fragmentação. um link).

7. Utilizamos em aula os métodos Stack() e ~Stack(), responda as questões abaixo sobre eles:
a. O que é o método Stack() e quando é utilizado?
Stack() é um método construtor, é utilizado assim que uma nova instância de um objeto é
criada, podendo inicializar e atribuir valores aos itens da classe, alocar memória dinâmica,
etc.
b. O que é o método ~Stack() e quando é utilizado?
~Stack() é um método destrutor, é utilizado para finalizar uma pilha (já criada), excluindo
seus itens e liberando a memória utilizada por eles (em uma pilha dinâmica).
No geral, o destrutor é chamado quando um objeto sai de escopo, por ex. uma função
termina, o programa termina, um bloco que contenha variáveis locais termina, uma operação
de remoção é chamada...
c. O método ~Stack() não precisa ser implementado para uma pilha contígua. Por quê?
Uma vez que um vetor é criado, é alocado o espaço necessário para todas as suas posições.
Esse espaço só é liberado quando o vetor deixa de existir. Com isso, mesmo que uma pilha

7
use apenas uma parte do vetor, não há como “quebrar” o vetor e liberar (desalocar da
memória) o espaço não utilizado pela pilha. O espaço, do vetor, não sendo utilizado pela
pilha fica disponível para ter seu conteúdo sobrescrito quando for necessário.
8. (sem resolução)
9. (sem resolução)
10. Em uma pilha encadeada, o método full sempre retorna false. Por quê?
Porque não é declarado uma quantidade máxima de elementos da pilha, enquanto houver memória
disponível pode-se inserir um novo elemento na pilha.
11. (sem resolução)
12. (sem resolução)
13. (sem resolução)
14. Escreva um programa C++ usando pilhas que leia uma linha e verifique se todos os parênteses, colchetes
e chaves encontrados estão apropriadamente em pares, ou seja, para cada (, [ e { deve haver um ), ] e }
do mesmo tipo corretamente aninhado. Se uma construção do tipo (... {... )... } é encontrada ou se um dos
símbolos não possui seu par correspondente, o programa deve escrever uma mensagem de erro,
indicando a posição na linha onde foi encontrada a falha.
Primeiramente crie um projeto com o nome “ValidateExpression”, em seguida – dentro do projeto – crie
os arquivos abaixo:

//file validateExpression.h
//#include <cstring>
#include <string>
using namespace std;

#ifndef VALIDATEEXPRESSION_H
#define VALIDATEEXPRESSION_H

typedef char StackEntry; //erros com string

class Stack{
public:
Stack();
bool empty();
bool full();
void push(StackEntry x);
void pop(StackEntry &x);

private:
static const int MaxStack = 5;
int top; //topo da pilha
StackEntry entry[MaxStack+1]; //vetor com elementos
};
#endif

//file validateExpression.cpp
#include <iostream>
#include <string>
#include "validateexpression.h"

using namespace std;

Stack::Stack() //método construtor


{ top = 0;
}

bool Stack::full(){
return (top == MaxStack);
}

void Stack::push(char x){


if (full()){

8
cout << "Full stack. If needed, increase 'MaxStack' value..." <<
endl;
cout << "Aborting...";
abort();
}
top++;
entry[top] = x;
}

void Stack::pop(char &x){


x = entry[top];
top--;
}

bool Stack::empty(){
return (top == 0);
}

//file d1validateExpression.cpp
#include <iostream>
#include <string>
#include "validateexpression.h"
using namespace std;

int checkBrackets(char x){


if (x == '(' || x == '{' ||x == '['){
return 0; //push
}else if (x == ')' || x == '}' ||x == ']'){
return 1; //pop
}else {
return -1; //do nothing
}
}

void validate(string x){


int i;
char last;
Stack stk;
for (i=0; i<x.size(); i++){
if (checkBrackets(x[i]) == 0){ //opening symbol
stk.push(x[i]);
// cout << "pushed: " << x[i] << endl;
}else if (checkBrackets(x[i]) == 1) { //closing symbol
stk.pop(last);
// cout << "x[i]: " << x[i] << endl;
// cout << "last: " << last << endl;
if ((x[i] == ')' && last != '(') || (x[i] == ']' && last != '[')
|| (x[i] == '}' && last != '{')){
cout << "Error on the expression! Character '" << x[i] << "'
in position: " << i+1 << endl;
cout << "Aborting the program...";
abort();
}else {
// cout << "pair matches!" << endl;
}
// cout << "popped: " << last << endl;
}else { //letters, numbers and arithmetic operators
// cout << "ops, ignoring a non symbol... " << x[i] << endl;
}
}
if (stk.empty()){
cout << "The expression is OK!" << endl;
}else{
cout << "Error on the expression! Missing pairs.." << endl;

9
}
}

int main (){


string x; // expression

cout << "Type an expression: ";


cin >> x;
validate(x);

return 0;
}

Obs: Descomente as linhas que contêm ‘cout’ para exibir a validação de cada
caracter da expressão.

15. (sem resolução)


16. (sem resolução)
17. Em alguns casos, um programa requer o uso de duas pilhas contendo o mesmo tipo de dados. Se duas
pilhas são armazenadas em arrays separados, então uma pilha pode estourar enquanto a outra possui
espaço não utilizado disponível. Uma maneira de evitar esse problema consiste em colocar as duas pilhas
num único array, sendo que uma pilha cresce da direita para a esquerda e a outra pilha cresce da
esquerda para a direita, uma em direção à outra. Assim, se uma pilha cresce demais e a outra não, as duas
caberão no array e não haverá estouro até que todo o espaço realmente seja usado. Declare um novo tipo
DoubleStack que inclui um array e dois índices topA e topB e escreva as operações neste tipo de pilha
bem como pushA, pushB, popA e popB para manipular duas pilhas dentro de uma DoubleStack.

...
→ Top Top ←
A B

Primeiramente crie um projeto com o nome “DoubleStack”, em seguida – dentro do projeto – crie os
arquivos abaixo:

//file DoubleStack.h
#ifndef DOUBLESTACK_H
#define DOUBLESTACK_H

typedef int StackEntry;

class DoubleStack{
public:
DoubleStack();
bool emptyA();
bool emptyB();
bool full();
void pushA(StackEntry x);
void pushB(StackEntry x);
void popA(StackEntry &x);
void popB(StackEntry &x);

private:
static const int MaxStack = 10;
int topA, topB; //topos das pilhas
StackEntry entry[MaxStack+1]; //vetor com elementos.. 'entry' é o
nome do vetor
};
#endif

//file DoubleStack.cpp

10
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include <conio.h>
#include "DoubleStack.h"

using namespace std;

DoubleStack::DoubleStack(){
topA = 0;
topB = MaxStack+1;
}

bool DoubleStack::emptyA(){
return (topA == 0);
}

bool DoubleStack::emptyB(){
return (topB == MaxStack+1);
}

bool DoubleStack::full(){
return (topA + 1 == topB);
}

void DoubleStack::pushA(int x){


if (full()){
cout << "Full DoubleStack A. Aborting...";
abort();
}
topA++;
entry[topA] = x;
}

void DoubleStack::pushB(int x){


if (full()){
cout << "Full DoubleStack B. Aborting...";
abort();
}
topB++;
entry[topB] = x;
}

void DoubleStack::popA(int &x){


if (emptyA()){
cout << "Empty DoubleStack A. Aborting...";
abort();
}
x = entry[topA];
topA--;
}

void DoubleStack::popB(int &x){


if (emptyB()){
cout << "Empty DoubleStack B. Aborting...";
abort();
}
x = entry[topB];
topB--;
}

//file d1doubleStack.cpp
#include <iostream>

11
#include <string>
#include "doubleStack.h"
using namespace std;

int menu(){
int option;

cout << endl;


cout << "1. Push to stack A" << endl;
cout << "2. Push to stack B" << endl;
cout << "3. Pop from stack A" << endl;
cout << "4. Pop from stack B" << endl;
cout << "9. Exit" << endl;
cout << " Your Option: ";
cin >> option;
cout << endl;
return option;
}

int main(){
DoubleStack stk;
int option;
int number;

while (option != 9){


switch(option){
case 1:
cout << "Insert the number: ";
cin >> number;
stk.pushA(number);
system ("CLS"); //clears the screen
cout << "Pushed A, number: " << number << endl;
break;
case 2:
cout << "Insert the number: ";
cin >> number;
stk.pushB(number);
system ("CLS");
cout << "Pushed B, number: " << number << endl;
break;
case 3:
stk.popA(number);
system ("CLS");
cout << "Popped A, number: " << number << endl;
break;
case 4:
stk.popB(number);
system ("CLS");
cout << "Popped B, number: " << number << endl;
break;
}
option = menu();
}
return 0;
}

12
18. Considere uma rede de desvio ferroviário:

Carros ferroviários numerados 1, 2, 3, ..., n estão do lado direito. Cada carro é trazido dentro da pilha e
removido a qualquer momento. Por exemplo, se n = 3, pode-se mover para dentro o primeiro, depois o
segundo e depois o terceiro. Então, em seguida, pode-se retirar os carros produzindo a nova sequência 3,
2, 1.
a) Quais são as possíveis permutações, para n=3 e n=4 que pode-se obter para os carros?
n=3 {{1,2,3}, {1,3,2}, {2,1,3}, {2,3,1}, {3,1,2}, {3,2,1}}
n=4 {1,2,3,4}, {1,3,2,4}, {1,4,3,2}, {2,1,3,4}, {2,1,4,3}, {2,3,1,4}, {2,3,4,1}, {2,4,3,1}, {3,2,1,4},
{3,2,4,1}, {3,4,2,1}, {4,3,2,1}}
b) Existem permutações que não sejam possíveis?
Sim, {3,1,4,2}, {4,1,2,3}, etc...
c) Tente encontrar uma fórmula para o número de movimentos necessários para inverter a ordem
de n carros.
2n

13

You might also like