Uploaded by Eduardo Carvalho

4 - PilhaseFilas - Implementação Dinâmica Generics Iteradores v4 pdf (1)

advertisement
Algoritmos e Estrutura de Dados
(AED)
2º semestre 2022/2023
Semana 4
Pilhas e Filas - implementação dinâmica
Sumário
▪ Este módulo pretende introduzir implementação dinâmica
(Linked-Lists) dos tipos de dados Pilhas e Filas
▪ Parte 1:
▪ Stacks – Implementação com Linked-Lists
▪ Queues – Implementação com Linked-Lists
▪ Parte 2:
▪ Genéricos (Generics)
▪ Iteradores
▪ Aplicações
Referência
▪ Algorithms, 4th edition by R.
Sedgewick and K. Wayne,
Addison-Wesley Professional,
2011:
▪ Chapter 1.3
▪ Lecture Slides: Bags, Queues, and
Stacks
h t t p : / / a l g s 4. c s . p r i n c e t o n . e d u
Pilha (Stack) – Implementação
com Linked-List
Stack – Implementação com Linked-List
▪ Representação: Sequência de nós ligados
▪ A gestão do espaço na memória é dinâmica:
• Cria espaço para um novo nó sempre que necessário
(cria nova instância com new)
• Liberta o espaço de um nó sempre que este deixa de ser necessário
(Garbage Collector)
▪ first:
atributo usado para manter a referência do primeiro nó
(representa o topo da pilha)
▪ push(item): adiciona um novo item antes do primeiro
▪ pop():
remove o item apontado por first
topo da pilha
first
be
to
not
or
be
to
null
Stack – Implementação com Linked-List
▪ Classe Node:
▪ Classe interna que representa um nó
▪ item: contém um item da pilha
▪ next: contém a referência para o próximo nó da pilha, ou null
private class Node {
String item;
Node next;
nó
item
hello
null
next
}
classe interna privada
(não necessita de definir modificadores de acesso para os atributos)
Stack – Implementação com Linked-List
▪ Exemplo:
to
first
push("to")
be
first
push("be")
or
first
push("or")
not
push("not")
push("to")
first
to
push("be")
pop()
to
null
be
to
null
or
be
to
null
not
first
or
be
to
null
not
pop()
null
first
be
or
be
to
null
not
first
or
be
to
null
not
first
or
be
to
null
Stack – Implementação com Linked-List
Classe Nó
▪ Operação pop():
private class Node {
String item;
▪ Salva item do topo da pilha
Node next;
}
String item = first.item;
▪ Remove topo da pilha
first
or
be
to
null
first = first.next;
first
▪ Devolve o item
return item;
or
be
to
null
Stack – Implementação com Linked-List
Classe Nó
▪ Operação push(item):
private class Node {
String item;
Node next;
}
▪ Salva a referência do topo da pilha
oldfirst
Node oldfirst = first;
first
or
be
to
null
▪ Cria um novo nó para o topo
oldfirst
or
first = new Node();
be
first
to
null
▪ Inicializa os atributos do novo nó
oldfirst
first.item = item;
first.next = oldfirst;
not
first
or
be
to
null
Stack – Implementação com Linked-List
public class LinkedStackOfStrings {
private Node first = null;
private class Node {
String item;
Node next;
}
public boolean isEmpty() {
return first == null;
}
public void push(String item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public String pop() {
String item = first.item;
first = first.next;
return item;
}
}
Stack – Linked-List: Desempenho
▪ Proposição (Tempo de execução):
▪ Cada operação usa tempo constante no pior caso
▪ Proposição (Memória):
▪ Uma pilha de Strings com N itens usa ~ 40 N bytes
Classe interna
16 bytes (cabeçalho)
private class Node
8 bytes (cabeçalho extra para classe interna)
{
}
String item;
8 bytes (referência para String)
Node next;
8 bytes (referência para Node)
40 bytes para cada Node
▪ Nota: O valor só está a contabilizar a memória necessária para a
pilha (não considera a memória utilizada para as Strings)
Stack – Array Redimensionável vs. Linked-List
▪ Compromissos:
▪ Pode-se implementar uma pilha tanto com array redimensionável
como com linked-list
▪ Cliente pode escolher a implementaçáo de forma intercambiável
▪ Implementação com array redimensionável:
▪ Cada operação leva um tempo amortizado constante
▪ Menos desperdício de espaço (memória)
N=4
to
be
or
not
null
null
null
null
▪ Implementação com linked-list:
▪ Cada operação leva tempo constante no pior caso
▪ Utiliza tempo e espaço extra para lidar com as ligações entre nó
Fila (Queue) – Implementação
com Linked-List
Queue – Implementação com Linked-List
▪ Representação: Sequência de nós ligados
▪ A gestão do espaço na memória é dinâmica:
• Cria espaço para um novo nó sempre que necessário
(cria nova instância com new)
• Liberta o espaço de um nó sempre que este deixa de ser necessário
(Garbage Collector)
▪ first:
atributo usado para manter a referência do primeiro nó
▪ last:
atributo usado para manter a referência do último nó
▪ enqueue(item): adiciona novo item depois do apontado por last
▪ dequeue():
remove o item apontado por first
last
first
to
be
or
not
to
be
null
Queue – Implementação com Linked-List
▪ Classe Node:
▪ Classe interna que representa um nó
▪ item: contém um item da fila
▪ next: contém a referência para o próximo nó da fila, ou null
private class Node {
String item;
Node next;
nó
item
hello
null
next
}
classe interna privada
(não necessita de definir modificadores de acesso para os atributos)
Queue – Implementação com Linked-List
▪ Exemplo:
enqueue("to")
first
to
null
to
enqueue("be")
first
enqueue("not")
enqueue("to")
dequeue()
enqueue("be")
dequeue()
first
first
be
null
to
enqueue("or")
last
be
last
or
null
to
be
or
last
not
null
to
be
first
or
not
last
to
null
be
first
or
not
to
null
be
or
first
not
to
last
last
be
null
first
or
not
to
be
null
last
last
Queue – Implementação com Linked-List
Classe Nó
▪ Operação dequeue():
private class Node {
String item;
▪ Salva item da primeira posição da fila
Node next;
}
String item = first.item;
▪ Remove o primeiro nó da fila
last
to
first
be
or
null
last
first = first.next;
first
to
be
if (first == null)
last = null;
null
(caso especial, em que a fila fica vazia)
▪ Devolve o item salvo
return item;
or
last
first
to
null
last
first
to
null
null
Queue – Implementação com Linked-List
▪ Operação enqueue(item):
oldlast
▪ Salva a referência do último nó
last
to
first
Node oldlast = last;
be
null
▪ Cria um novo nó para o último
e inicializa-o
last = new Node();
last.item = item;
last.next = null;
oldlast
last
to
first
be
or
not
null
null
oldlast
▪ Liga o novo nó à fila
to
if (!isEmpty())
oldlast.next = last;
else
first = last;
or
first
be
or
last
not
null
(caso especial, em que a fila era vazia)
first
last
last
oldlast
null
first
to
null
Queue – Implementação com Linked-List
public class LinkedQueueOfStrings {
private Node first, last;
private class Node {/* mesmo que em LinkedStackOfStrings */ }
public boolean isEmpty() {
return first == null;
}
public void enqueue(String item) {
Node oldlast = last;
last = new Node();
last.item = item;
~ constante
last.next = null;
if (isEmpty())
first = last;
else
oldlast.next = last;
}
public String dequeue() {
String item = first.item;
first = first.next;
if (isEmpty())
last = null;
return item;
}
}
~ constante
Genéricos (Generics)
Genéricos – Motivação
▪ Tipo implementado: Pilha de Strings (StackOfStrings)
▪ Como implementar outros tipos?
▪ Ex: Pilha de URLs, Pilha de Inteiros, Pilha de Carrinhas, …
▪ Tentativa 1: Implementar uma classe separada para
cada tipo de pilha
▪ Ex: StackOfURLs, StackOfIntegers, StackOfVans, …
▪ A reescrita do código é entediante e suscetível à erros
▪ Repetição de código (“copy & paste”) deve ser evitado!
▪ Mas… foi a abordagem utilizada até o Java JDK 1.5
Genéricos – Motivação
▪ Como implementar outros tipos?
▪ Ex: Pilha de URLs, Pilha de Inteiros, Pilha de Carrinhas, …
▪ Tentativa 2:
Implementar uma pilha genérica StackOfObjects
Todas as classes do Java derivam
com itens do tipo Object
da classe Object !
▪ Requer conversões (casting) do tipo Object para String,
Integer, Van,…
▪ Casting é suscetível à erros - pode gerar erros em tempo de
execução (run-time error) se os tipos não corresponderem
public static void main(String[] args) {
StackOfObjects s = new StackOfObjects();
Apple a = new Apple();
Não especifica se será uma pilha
Orange b = new Orange();
de Apples ou de Oranges …
s.push(a);
s.push(b);
a = (Apple) (s.pop());
}
run-time error – Erro na conversão de Orange para Apple !
Solução com Genéricos (Generics)
▪ Implementar um tipo genérico Stack<E> que aceita itens de
qualquer tipo E
▪ Stack<E>: Tipo genérico de pilha
▪ E: Parâmetro de tipo genérico (similar a parâmetros de métodos)
▪ Vantagens:
• Evita casting
• Descobre os erros de correspondência entre os tipos em tempo de
compilação (evita run-time error)
public static void main(String[] args) {
Stack<Apple> s = new Stack<Apple>();
Apple a = new Apple();
Orange b = new Orange();
s.push(a);
s.push(b);
a = s.pop();
}
compile-time error
Genéricos (Generics) – Definições
▪ Definições:
▪ Stack<E>: Tipo genérico de pilha
▪ E: Parâmetro de tipo genérico (similar a parâmetros de métodos)
▪ Tipo parametrizado: Obtido por instanciação do tipo genérico,
substituindo o parâmetro E por um tipo de objetos concretos
• Ex: Stack<String>
Tipo Genérico
Stack<E>
instanciação
Tipo Parametrizado
Stack<String>
Parâmetro de
tipo genérico
Parâmetro de
tipo concreto
public class Stack<E> {
...
}
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
...
}
Genéricos (Generics) – Definições
▪ Parâmetro de tipo genérico:
▪ Declaração:
• A seguir ao nome da classe
• Entre parêntesis angulares <...>
public class Stack<E> {
...
}
▪ Nome da variável:
• Aceita qualquer nome (Ex: E, Item, T, K, …)
• Aceita mais que uma variável, separadas por vírgula
• Convenção Java: Utilizar letra maiúscula
(Ex: E para elemento de uma coleção)
▪ Parâmetro de tipo concreto:
▪ Só aceita tipos referenciados (tipos de objetos) !
• Ex: String, Date, ...
Genéricos (Generics) – Definições
▪ Pode ser usado para declarar:
▪ Variáveis locais e atributos
public class LinkedStackOfStrings {
...
private class Node {
String item;
Node next;
}
...
public class Stack<E> {
...
private class Node {
E item;
Node next;
}
...
▪ Parâmetros de métodos
public void push(String item) {
...
}
public void push(E item) {
...
}
▪ Tipos de retorno de métodos
public String pop() {
...
}
public E pop() {
...
}
Stack<E> – Implementação com Linked-List
public class LinkedStackOfStrings {
private Node first = null;
}
public class Stack<E> {
private Node first = null;
private class Node {
String item;
Node next;
}
private class Node {
E item;
Node next;
}
public boolean isEmpty() {
return first == null;
}
public boolean isEmpty() {
return first == null;
}
public void push(String item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public void push(E item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public String pop() {
String item = first.item;
first = first.next;
return item;
}
public E pop() {
E item = first.item;
first = first.next;
return item;
}
}
Stack<E> – Implementação com Array Fixo
▪ Instanciação de array genérico não é permitida em Java:
▪ Sem genérico:
public class FixedCapacityStackOfStrings {
private String[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = new String[capacity];
}
...
▪ Com genérico (com erro de compilação !):
public class FixedCapacityStack<E> {
private E[] s;
private int N = 0;
public FixedCapacityStack(int capacity) {
s = new E[capacity];
}
...
Stack<E> – Implementação com Array Fixo
▪ Instanciação de array genérico não é permitida em Java:
▪ Sem genérico:
public class FixedCapacityStackOfStrings {
private String[] s;
private int N = 0;
public FixedCapacityStackOfStrings(int capacity) {
s = new String[capacity];
}
...
▪ Com instância de Object + casting: (solução válida, apesar de não
ser muito “elegante”)
public class FixedCapacityStack<E> {
private E[] s;
private int N = 0;
public FixedCapacityStack(int capacity) {
s = (E[]) new Object[capacity];
}
...
“the ugly cast"
Stack<E> – Implementação com Array Fixo
public class FixedCapacityStack<E> {
private E[] s;
private int N = 0;
public FixedCapacityStack(int capacity) {
s = (E[]) new Object[capacity];
}
public boolean isEmpty()
return N == 0;
}
{
public void push(E item) {
s[N++] = item;
}
public E pop(){
E item = s[--N];
s[N] = null;
return item;
}
}
Stack<E> – Implementação com Array Fixo
▪ Nota: Quando forem compilar o código, o compilador Java
irá apresentar uma mensagem do tipo “Unchecked cast”
% javac FixedCapacityStack.java
Note: FixedCapacityStack.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
% javac -Xlint:unchecked FixedCapacityStack.java
FixedCapacityStack.java:26: warning: [unchecked] unchecked cast
found
: java.lang.Object[]
required: E[]
a = (E[]) new Object[capacity];
^
1 warning
Classes Empacotadoras & Autoboxing
▪ Classes empacotadoras (Wrapper Type):
▪ Usadas para processar tipos primitivos (int, char, double, …)
como objetos
▪ Exemplos:
▪ Para int: Classe Integer
▪ Para char: Classe Character
▪ Para double: Classe Double
▪ Autoboxing:
▪ Conversão automática entre um tipo primitivo e a empacotadora
public static void main(String[] args) {
Stack<Integer> s = new Stack<Integer>();
s.push(17);
// s.push(Integer.valueOf(17));
int a = s.pop();
// int a = s.pop().intValue();
}
Iteradores (Iterator)
Iteradores
▪ Desafio: Possibilitar a iteração sobre uma coleção de
elementos abstraindo-se da forma como essa coleção está
implementada
▪ Iterar: Percorrer os itens da coleção, um a um. A ordem em em
que se percorre não é especificada
▪ Ex: Suportar a iteração sobre os itens de uma Pilha (Stack), sem
revelar a sua representação interna ao cliente (vem de um vetor?
De uma lista? De uma pilha? Não é relevante!)
i
s[]
it
was
the
best
of
times
null
null
null
null
0
1
2
3
4
5
6
7
8
9
first
times
N
current
of
best
the
was
it
null
Iteradores no Java
▪ Solução: O Java oferece os interfaces Iterable e Iterator
▪ Iterable: Define o comportamento dos objetos iteráveis
▪ Os objetos que implementam o interface Iterable, podem ser
processados com base num objeto iterador (Iterator)
▪ Ex: Para se conseguir iterar sobre um objeto Stack, a classe
Stack deverá implementar a interface Iterable
public class Stack<E> implements Iterable<E>{
...
}
▪ Iterator: Define o comportamento de um objeto iterador
▪ Iterar sobre os itens de um objeto iterável, abstraindo-se da forma
como esse objeto está implementado
Iteradores no Java
▪ Interface Iterable<E>:
▪ iterator(): Devolve o objeto iterador a utilizar para percorrer o
objeto iterável
public interface Iterable<E> {
Iterator<E> iterator();
}
▪ Interface Iterator<E>:
▪ hasNext(): Devolve true caso ainda haja algum item por
processar. Desta forma sabe-se se é ou não seguro usar next()
▪ next(): Devolve o item atual e avança para o seguinte. Deve ser
lançada a exceção NoSuchElementException caso não se
encontre nenhum elemento a processar
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove();
}
[opcional] Remove o último
elemento obtido com next()
Iteradores no Java
▪ Ex:
public class Stack<E> implements Iterable<E>{
...
private class StackArrayIterator implements Iterator<E>{
...
}
}
.next()
StackArray
Iterator
Stack<String> .iterator()
.next()
times
of
.next()
best
i
s[]
N
it
was
the
best
of
times
null
null
null
null
0
1
2
3
4
5
6
7
8
9
Iteradores no Java
▪ Porquê tornar as estruturas de dados iteráveis (Iterable)?
▪ Utilizar as ferramentas do Java para suportar códigos de cliente mais
elegantes
▪ Ex: Ciclo for-each
▪ Forma mais abstrata de iterar sobre uma estrutura de dados (“para
cada elemento, fazer…”)
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
stack.push("what");
stack.push("am I");
stack.push("doing");
for(String str : stack)
System.out.println(str);
}
Simplificação do código abaixo
(equivalente à):
Iterator<String> i = stack.iterator();
while (i.hasNext())
{
String str = i.next();
System.out.println(str);
}
Stack iterator – Implementação com array
import java.util.Iterator;
public class Stack<E> implements Iterable<E> {
private E[] s;
private int N = 0;
... // Implementação vista nos slides anteriores
public Iterator<E> iterator() {
return new StackArrayIterator();
}
private class StackArrayIterator implements Iterator<E> {
private int i = N;
public boolean hasNext() {
return i > 0;
}
public void remove() { /* not supported */ }
public E next() {
return s[--i];
}
i
N
}
}
s[]
it
was
the
best
of
times
null
null
null
null
0
1
2
3
4
5
6
7
8
9
Stack iterator – Implementação com Linked-List
public class Stack<E> implements Iterable<E> {
private Node first = null;
private class Node {
E item;
Node next;
}
... // Implementação vista nos slides anteriores
public Iterator<E> iterator() {
return new StackListIterator();
}
private class StackListIterator implements Iterator<E> {
private Node current = first;
public boolean hasNext() {
return current != null;
}
throw new UnsupportedOperationException
public void remove() { /* not supported */ }
}
throw new NoSuchElementException
Se não houver mais itens para iterar
public E next() {
E item = current.item;
current = current.next;
return item;
first
}
current
}
times
of
best
the
was
it
null
Aplicações
(Slides extras em Inglês)
Java collections library
▪ List interface: java.util.List is API for an sequence of
items
public interface List<Item> implements Iterable<Item>
List()
create an empty list
boolean isEmpty()
is the list empty?
int size()
number of items
void add(Item item)
append item to the end
Item get(int index)
return item at given index
Item remove(int index)
return and delete item at given index
boolean contains(Item item)
Iterator<Item> iterator()
does the list contain the given item?
iterator over all items in the list
...
▪ Implementations. java.util.ArrayList uses resizing array;
java.util.LinkedList uses linked list
caveat: only some
operations are efficient
(from KW slides, used with permission)
Java collections library
▪ java.util.Stack
▪ Supports push(), pop(), and iteration.
▪ Extends java.util.Vector, which implements java.util.List interface
from previous slide, including get() and remove().
▪ Bloated and poorly-designed API (why?)
▪ java.util.Queue: An interface, not an implementation of a
queue
▪ Best practices: Use our implementations of Stack, Queue,
and Bag
(from KW slides, used with permission)
War story (from Assignment 1)
▪ Generate random open sites in an N-by-N percolation system
▪ Jenny: pick (i, j) at random; if already open, repeat.
Takes ~ c1 N 2 seconds.
▪ Kenny: create a java.util.ArrayList of N 2 closed sites.
Pick an index at random and delete.
Takes ~ c2 N 4 seconds.
Why is my program so slow?
Kenny
▪ Lesson: Don't use a library until you understand its API!
▪ This course: Can't use a library until we've implemented it in class
(from KW slides, used with permission)
Stack applications
▪
▪
▪
▪
▪
▪
▪
Parsing in a compiler.
Java virtual machine.
Undo in a word processor.
Back button in a Web browser.
PostScript language for printers.
Implementing function calls in a compiler.
...
(from KW slides, used with permission)
Function calls
▪ How a compiler implements a function.
▪ Function call: push local environment and return address.
▪ Return: pop return address and local environment.
▪ Recursive function: Function that calls itself.
▪ Note: Can always use an explicit stack to remove recursion.
gcd (216, 192)
p = 216, q = 192
static int gcd(int p,
if (q == 0) return
else return gcd(q,
}
p = 192, q = 24
p = 24, q = 0
int q) {
p;
pgcd
% q);
(192, 24)
static int gcd(int p, int q) {
if (q == 0) return p;
else return gcd(q, p % q);
gcd (24, 0)
}
static int gcd(int p, int q) {
if (q == 0) return p;
else return gcd(q, p % q);
}
(from KW slides, used with permission)
Arithmetic expression evaluation
▪ Goal: Evaluate infix expressions.
value stack
operator stack
(1+((2+3)*(4*5)))
▪ Two-stack algorithm: [E. W. Dijkstra]
▪ Value: push onto the value stack.
▪ Operator: push onto the operator stack.
▪ Left parenthesis: ignore.
▪ Right parenthesis: pop operator and two values;
push the result of applying that operator
to those values onto the value stack.
▪ Context: An interpreter!
(from KW slides, used with permission)
Arithmetic expression evaluation
public class Evaluate
{
public static void main(String[] args)
{
Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>();
while (!StdIn.isEmpty()) {
String s = StdIn.readString();
if
(s.equals("("))
;
else if (s.equals("+"))
ops.push(s);
else if (s.equals("*"))
ops.push(s);
else if (s.equals(")"))
{
String op = ops.pop();
if
(op.equals("+")) vals.push(vals.pop() + vals.pop());
else if (op.equals("*")) vals.push(vals.pop() * vals.pop());
}
else vals.push(Double.parseDouble(s));
}
StdOut.println(vals.pop());
}
% java Evaluate
}
(1+((2+3)*(4*5)))
101.0
(from KW slides, used with permission)
Correctness
▪ Q. Why correct?
▪ A. When algorithm encounters an operator surrounded by
two values within parentheses, it leaves the result on the
value stack.
(1+((2+3)*(4*5)))
▪ as if the original input were:
(1+(5*(4*5)))
▪ Repeating the argument:
( 1 + ( 5 * 20 ) )
( 1 + 100 )
101
▪ Extensions. More ops, precedence order, associativity.
(from KW slides, used with permission)
Stack-based programming languages
▪ Observation 1: Dijkstra's two-stack algorithm computes the
same value if the operator occurs after the two values.
(1((23+)(45*)*)+)
▪ Observation 2. All of the parentheses are redundant!
123+45**+
▪ Bottom line. Postfix or "reverse Polish" notation.
▪ Applications. Postscript, Forth, calculators, Java virtual
Jan Lukasiewicz
machine, …
(from KW slides, used with permission)
Laboratório desta Semana
▪ Exercício 12 (Folha de Exercícios):
▪ Implemente dinamicamente a seguinte API
public class Queue<Item> implements Iterable<Item>
Queue()
void enqueue(Item item)
Item dequeue()
boolean isEmpty()
int size()
Iterator<Item> iterator()
create an empty queue
add an item
remove the least recently added item
is the queue empty?
number of items in the queue
support iteration
Download