You are on page 1of 720

Algoritmos e Estruturas de Dados

Lio n. 1
Algoritmos e Estruturas de Dados
Algoritmos e Estruturas de Dados
Logstica
Tecnologia
Aulas
Avaliao
Programa da cadeira
O ambiente de programao

6/1/16 Algoritmos e Estruturas de Dados 2


Tecnologia
Linguagem: Java.
Componente grfica: Processing.
Estilo: Checkstyle.
Ambiente: Eclipse, linha de comando.
Sistema operativo: Windows, MacOS, Linux.
Avaliador automtico: Mooshak.
Tutoria: Moodle.

6/1/16 Algoritmos e Estruturas de Dados 3


Aulas
Tericas: Pedro Guerreiro.
Prticas: Antnio dos Anjos.

6/1/16 Algoritmos e Estruturas de Dados 4


Bibliografia principal
Algorithms, quarta edio (2011), Robert
Sedgewick e Kevin Wayne.
Book site: http://algs4.cs.princeton.edu/

http://www.amazon.co.uk/Algorithms-Robert-Sedgewick/dp/032157351X/
6/1/16 Algoritmos e Estruturas de Dados 5
Bibliografia de interesse
Introduction to Algorithms, 3.
edio, Thomas Cormen, Charles
Leiserson, Ronald Rivest, Clifford
Stein.
http://www.amazon.co.uk/Introduction-Algorithms-T-
Cormen/

The Art of Computer Programming,


Donald Knuth.
http://www.amazon.co.uk/Art-Computer-Programming-Volumes-1-
4a/

Algorithms + Data Structures =


Programs, Niklaus Wirth (1975).
http://www.amazon.co.uk/Algorithms-Structures-Prentice-Hall-
automatic-computation/

6/1/16 Algoritmos e Estruturas de Dados 6


Coursera
Algorithms, Part I
Instructors: Robert Sedgewick, Kevin Wayne.
This course covers the essential information that every
serious programmer needs to know about algorithms and
data structures, with emphasis on applications and scientific
performance analysis of Java implementations. Part I covers
() union-find algorithms; basic iterable data types (stack,
queues, and bags); sorting algorithms (quicksort, mergesort,
heapsort) and applications; priority queues; binary search
trees; red-black trees; hash tables; and symbol-table
applications.

https://www.coursera.org/course/algs4partI

6/1/16 Algoritmos e Estruturas de Dados 7


Mais Coursera
Algorithms, Part II
Part II covers graph-processing algorithms, including
minimum spanning tree and shortest paths algorithms, and
string processing algorithms, including string sorts, tries,
substring search, regular expressions, and data compression,
and concludes with an overview placing the contents of the
course in a larger context.

https://www.coursera.org/course/algs4partII
6/1/16 Algoritmos e Estruturas de Dados 8
Programa de AED
Conceitos fundamentais Grafos
Modelo de programao Busca em profundidade
Sacos, pilhas e filas Busca em largura
Arrays rvores de cobertura
Union-Find Caminho mais curto
Anlise de algoritmos Problema do caixeiro
Ordenao viajante
Algoritmos elementares Cadeias de carateres
Mergesort, quicksort Busca de subcadeias
Filas com prioridade Compresso de dados
Busca Estratgias programativas
rvores binrias de busca Diviso-conquista
rvores equilibradas Algoritmos gananciosos
Tabelas de disperso Programao dinmica
Intratabilidade
6/1/16 Algoritmos e Estruturas de Dados 9
Algoritmos de programao
Busca linear
Busca dicotmica
Insertionsort
Mergesort
Quicksort
Converso numrica
Reverso numrica
Mximo, mnimo de um array
Remoo de duplicados
Comparao lexicogrfica de arrays
6/1/16 Algoritmos e Estruturas de Dados 10
Algoritmos no ensino secundrio
Fatorizao
Mximo divisor comum
Simplificao de fraes
Regra de Ruffini
Mtodo de Gauss

6/1/16 Algoritmos e Estruturas de Dados 11


Algoritmos da escola primria
Adio
Subtrao
Multiplicao
Diviso
(Sucessor)

6/1/16 Algoritmos e Estruturas de Dados 12


Algoritmos clssicos
Algoritmo de Euclides.

Mtodo para o clculo de , de Arquimedes.

Crivo de Eratstenes.

6/1/16 Algoritmos e Estruturas de Dados 13


Algoritmo de Euclides
Calcula o mximo divisor comum de dois nmeros
inteiros positivos.
o mais antigo algoritmo inventado pelo esprito
humano.
public static int euclidAlgorithm(int x, int y)
{
while (x != y)
if (x < y)
y -= x;
else
x -= y;
return x;
} Isto ser um mtodo
esttico de alguma classe,
em Java.

6/1/16 Algoritmos e Estruturas de Dados 14


Algoritmo de Euclides, variantes recursivas
Formulao recursiva
public static int euclid(int x, int y)
{
return x < y ? euclid(x, y-x) : x > y ? euclid(x-y, y) : x;
}

Formulao recursiva, curto-circuito:


public static int greatestCommonDivisor(int x, int y)
{
int result = x;
if (y != 0)
result = greatestCommonDivisor(y, x % y);
return result;
} Esta verso substitui uma
sequncia de subtraes
x -= y at x ser menor
que y por x % y.
6/1/16 Algoritmos e Estruturas de Dados 15
Algoritmo de Euclides, verso de combate
Normalmente, usamos a seguinte variante iterativa:
public static int gcd(int x, int y)
{
while (y != 0)
{
int r = x % y;
x = y;
y = r;
}
return x;
}
Tenha esta sempre mo!

6/1/16 Algoritmos e Estruturas de Dados 16


Comparando as verses iterativas
Eis uma funo de teste para comparar na consolas
duas verses iterativas:
public static void testIterativeVersions()
{
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
int y = StdIn.readInt();
int z1 = euclidAlgorithm(x, y);
StdOut.println(z1);
int z2 = gcd(x, y);
StdOut.println(z2); Lemos e escrevemos usando a
} biblioteca stdlib.jar, que teremos
} juntado ao projeto no Eclipse,
importando-a e acrescentando-a
ao build path.
6/1/16 Algoritmos e Estruturas de Dados 17
Classes StdIn e StdOut
Usaremos a classe StdIn para ler e a classe StdOut
para escrever na consola. StdOut

StdIn

6/1/16 Algoritmos e Estruturas de Dados 18


Importando StdIn e StdOut
Para usar StdIn e StdOut, temos de importar:
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public final class Euclid


{
...
}

Alm disso, temos de acrescentar a biblioteca


algs4.jar ao Build Path:
No Eclipse, clica-se no projeto,
boto direito, Build Path->Add
External Archives..., navega-se
para a diretoria do workspace e
seleciona-se algs4.jar.

6/1/16 Algoritmos e Estruturas de Dados 19


A funo main
Em cada classe, a funo main apenas chamar as
funes de teste.
Por enquanto, s temos uma funo de teste:
public static void main(String[] args)
{
testIterativeVersions();
}

A funo main e todas as outras formam a classe


Euclid, que teremos desenvolvido no Eclipse.

6/1/16 Algoritmos e Estruturas de Dados 20


Classe Euclid
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

public final class Euclid


{

public static int euclid(int x, int y)


{
return x < y ? euclid(x, y - x) : x > y ? euclid(x - y, y) : x;
}

// ...

public static void testIterativeVersions()


{
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
int y = StdIn.readInt();
int z1 = euclidAlgorithm(x, y);
StdOut.println(z1);
int z2 = gcd(x, y);
StdOut.println(z2);
}
}

public static void main(String[] args)


{
testIterativeVersions();
}
6/1/16 Algoritmos e Estruturas de Dados 21
}
No Eclipse
Correndo no Eclipse, a
interao d-se na consola
do Eclipse, terminando
com ctrl-z (Windows) ou
ctrl-d (Mac ou Linux).

6/1/16 Algoritmos e Estruturas de Dados 22


Na linha de comando
Para correr na linha de comando, colocamo-nos na
diretoria bin, dentro do projeto, no workspace.
A damos o comando
java cp ../../algs4.jar:. Euclid
$ pwd
Observe: /Users/pedro/Dropbox/AED_1516/ws
Pedros-iMac-2:ws pedro$ ls -l
total 3600
drwxr-xr-x@ 7 pedro staff 238 Feb 1 17:59 Euclid
-rw-r--r--@ 1 pedro staff 1841174 Feb 1 16:52 algs4.jar
Pedros-iMac-2:ws pedro$ cd Euclid/bin
Pedros-iMac-2:bin pedro$ ls
Euclid.class
Pedros-iMac-2:bin pedro$ java -cp ../../algs4.jar:. Euclid
6770 9100
10
10 Note que estamos dentro da diretoria bin e que
70000 65850 a biblioteca algs4.jar est ao lado da diretoria
50
50
Euclid, a qual contm a diretoria bin (e tambm a
67 45 diretoria src). Note tambm que, neste caso, a
1 classe Euclid tem o mesmo nome que o projeto,
1
$
Euclid, mas nem sempre ser assim.
6/1/16 Algoritmos e Estruturas de Dados 23
O caminho das classes
O caminho das classes, em ingls class path, a sequncia
de diretorias onde a mquina virtual do Java procurar as
classes necessrias para correr o programa (atravs da
funo main da classe invocada).
Analogamente, o compilador recorre s classes do class
path para compilar a classes que tiver de compilar.
Na linha de comando, indica-se a class path por meio da
opo cp.
No Eclipse, a class path fica guardada automaticamente,
quando a definimos usando o comando Build Path e pode
ser consultada nas propriedades do projeto: comando
Project -> Properties, separador Libraries.

6/1/16 Algoritmos e Estruturas de Dados 24


Testando as funes todas
Eis uma segunda funo de teste, para comparar os
resultados das quatro funes:
public static void testAllVersions()
{
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
int y = StdIn.readInt();
int z1 = euclid(x, y);
int z2 = euclidAlgorithm(x, y);
int z3 = greatestCommonDivisor(x, y);
int z4 = gcd(x, y);
StdOut.printf("%d %d %d %d\n", z1, z2, z3, z4);
}
}

6/1/16 Algoritmos e Estruturas de Dados 25


Argumentos na linha de comando
Selecionamos a funo de teste por meio de um
argumento na linha de comando:
public static void main(String[] args)
{
char choice = 'A';
if (args.length > 0)
choice = args[0].charAt(0);
if (choice == 'A')
testIterativeVersions();
else if (choice == 'B')
testAllVersions();
else
StdOut.println("Illegal option: " + choice);
}
$ java -cp ../../algs4.jar:. Euclid A
333 99
9
9
$ java -cp ../../algs4.jar:. Euclid B
78 5
1 1 1 1
400 600
200 200 200 200
$ java -cp ../../algs4.jar:. Euclid
1000 825
25
25
6/1/16 Algoritmos e Estruturas de Dados 26
$
Algoritmos e Estruturas de Dados

Lio n. 2
Animao algortmica
Animao algortmica
Visualizao dos algoritmos
Utilizao do Processing
Recapitulao
Integrao com o Java
Animao do algoritmo de Euclides

6/1/16 Algoritmos e Estruturas de Dados 2


Animao
Os algoritmos so pensamento puro.
Devemos compreend-los abstratamente.
Mas divertido v-los a mexer.
Por exemplo, eis um site com animaes muito
interessantes de algoritmos de ordenao:
http://www.sorting-algorithms.com/.
De que precisamos para programarmos ns prprios
animaes destas?
Precisamos de uma biblioteca grfica, neste caso uma
biblioteca grfica para o Java.

6/1/16 Algoritmos e Estruturas de Dados 3


Processing
O Processing uma linguagem de programao que
vem com o seu prprio ambiente de
desenvolvimento.
De certa forma, o Processing uma camada sobre
o Java, que pretende ocultar certas particularidades
mais difceis.
Na verdade, o que nos interessa agora que o
Processing traz uma grande biblioteca grfica, bem
documentada, que podemos usar nos nossos
programas Java.

6/1/16 Algoritmos e Estruturas de Dados 4


Recapitulando...
Em Processing, um programa constitudo por
algumas declaraes globais e pelas funes setup e
draw.
Ambas so chamadas automaticamente.
A funo setup chamada uma vez, no incio do
programa.
A funo draw chamada repetidamente, 60 vezes
por segundo.
O nmero de vezes que a
funo draw chamada por
segundo pode ser mudado,
com a funo frameRate().

6/1/16 Algoritmos e Estruturas de Dados 5


setup e draw

6/1/16 Algoritmos e Estruturas de Dados 6


Java com Processing
import processing.core.PApplet;

public class TShirtSketch extends PApplet


{
private final int displayWidth = 400;
private final int displayHeight = 300; Note bem: ao usar o Processing assim, a
funo size deve ser chamada na funo
public void settings()
{
settings e no na funo setup.
size(displayWidth, displayHeight);
}

public void setup()


{
colorMode(HSB, 26);
noCursor();
}

public void draw()


{
background(color(key & 31, 26, 26));
}

public static void main(String[] args)


{
PApplet.main(new String[] { TShirtSketch.class.getName() });
}
Teremos acrescentado ao projeto a biblioteca
}
core.jar do Processing, colocando-a na diretoria
6/1/16 do workspace.
Algoritmos e Estruturas de Dados 7
Usando da linha de comando
Podemos correr o sketch de dentro do Eclipse ou a
partir da linha de comando:
public class TShirtSketch extends PApplet
{
// ...

public static void main(String[] args)


{
PApplet.main(new String[] {TShirtSketch.class.getName()});
}

}
$ pwd
/Users/pedro/Dropbox/AED_1516/ws2
$ cd TShirt/bin
$ ls
TShirtSketch.class
$ java -cp ../../core.jar:. TShirtSketch

6/1/16 Algoritmos e Estruturas de Dados 8


Animando o algoritmo de Euclides
Queremos representar cada varivel, x e y, por uma
barra vertical, com altura proporcional ao valor da
varivel.
public static int euclidAlgorithm(int x, int y)
{
while (x != y)
if (x < y)
y -= x;
else
x -= y;
return x;
}

A cada passo do ciclo while atualizamos o a figura,


por exemplo de segundo a segundo (ou de acordo
com uma frequncia pr-estabelecida).

6/1/16 Algoritmos e Estruturas de Dados 9


Animao em direto
Podemos considerar que no algoritmo de Euclides,
em cada passo, calcula-se um novo par de valores <x,
y> a partir do par de valores <x, y> corrente.
Podemos programar essa transformao assim, na
classe Euclid:
public static int[] next(int a[])
{
assert a.length == 2;
int[] result = a.clone();
if (a[0] > a[1])
result[0] -= result[1];
if (a[0] < a[1])
result[1] -= result[0];
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 10
Testando a funo next
Eis uma funo de teste que invoca repetidamente a
funo next, a partir de valores lidos, at ambos os
valores serem iguais, escrevendo cada para de
valores, ao longo do clculo:
$ java Euclid D
public static void testNext() 78 35
{ 78 35
while (!StdIn.isEmpty()) 43 35
{ 8 35
8 27
int x = StdIn.readInt();
8 19
int y = StdIn.readInt(); 8 11
int[] a = new int[]{x, y}; 8 3
StdOut.println(a[0] + " " + a[1]); 5 3
while (a[0] != a[1]) 2 3
{ 2 1
a = next(a); 1 1
StdOut.println(a[0] + " " + a[1]);
}
}
}
6/1/16 Algoritmos e Estruturas de Dados 11
Esquete de animao
No esquete de animao, cada novo par ser
calculado quando for preciso, na funo
update, e as barras que representam as
variveis mudam em conformidade.
A funo update chamada pela funo draw,
para recalcular a figura, que depois a funo
draw desenhar.

6/1/16 Algoritmos e Estruturas de Dados 12


Parametrizao geral
Parametrizamos a largura das barras, a margem
esquerda, que igual margem direita e o intervalo
entre as barras.
A largura da janela calculada a partir desses valores
e altura da janela fixa.
Parametrizamos tambm a frequncia da animao:
public class EuclidSketch extends PApplet
{ Assim evitamos nmeros
private static final int BAR_WIDTH = 40; mgicos no nosso
private static final int MARGIN = 40;
private static final int GAP = 20; programa.
private static final int WIDTH = 2 * MARGIN + 2 * BAR_WIDTH + GAP;
private static final int HEIGHT = 720;

private static final double HERTZ = 1;

// ...
6/1/16} Algoritmos e Estruturas de Dados 13
Variveis da animao
Registamos o tempo em que a animao comea, o
nmero de passos na animao e o valor do par de
inteiros:
public class EuclidSketch extends PApplet
{
// ...
private double startTime; // time when the animation started
private int steps; // number of steps in the animation
private int[] current; // current values being shown
// ...
}

Usamos ainda duas variveis para representar as


cores usadas:
private int colorX = color(0, 0, 255); // BLUE;
private int colorY = color(255, 0, 0); // RED;

6/1/16 Algoritmos e Estruturas de Dados 14


A funo setup
A funo setup l os dois nmeros da consola e
inicializa as variveis:
public void setup()
{
StdOut.print("Two numbers, please: ");
int x = StdIn.readInt();
int y = StdIn.readInt();
startTime = millis();
steps = 0;
current = new int[]{x, y};
}

A funo settings ocupa-se do tamanho da janela:


public void settings()
{
size(WIDTH, HEIGHT);
}

6/1/16 Algoritmos e Estruturas de Dados 15


A funo draw
A funo draw chamada automaticamente, com a
frequncia determinada pela frame rate.
De cada vez, desenha tudo: primeiro o background,
diretamente, e depois as barras, invocando o mtodo
display.
Entre as duas, invoca a funo update, que calcula a
figura a desenhar:
public void draw()
{
background(100);
update();
display();
}

6/1/16 Algoritmos e Estruturas de Dados 16


A funo update
Em geral, usamos a funo update para fazer os clculos
que determinam o desenho que a funo draw deve
depois fazer.
Neste caso, calcula o nmero de unidades de tempo
que passaram desde o incio; se for maior que o nmero
de passos, est na hora de animao mexer:
private void update()
{
double seconds = (millis() - startTime) / 1000.0;
int timeUnits = (int)(seconds * HERTZ);
if (timeUnits > steps) A constante HERTZ representa a
{ frequncia com que a imagem muda.
current = Euclid.next(current);
steps++;
} No fim, o sketch fica parado na ltima imagem,
} mas os clculos continua indefinidamente.
6/1/16 Algoritmos e Estruturas de Dados 17
Desenhando as barras
Temos uma funo para cada barra e a funo display
para desenhar as duas barras, usando aquelas:
private void displayX(int h)
{
stroke(colorX);
fill(colorX);
rect(MARGIN, height-h, BAR_WIDTH, h);
}

private void displayY(int h)


{
stroke(colorY);
fill(colorY);
rect(MARGIN + BAR_WIDTH + GAP, height-h, BAR_WIDTH, h);
}
Cada barra tem a altura
public void display() correspondente ao valor da
{ varivel respetiva.
displayX(current[0]);
displayY(current[1]);
6/1/16
} Algoritmos e Estruturas de Dados 18
Correndo na linha de comandos
A funo main como de costume:
public class EuclidSketch extends PApplet
{
// ...

public static void main(String[] args)


{
PApplet.main(new String[] {EuclidSketch.class.getName()});
}
}

Neste caso, ao invocar na linha de comando,


temos de indicar as duas bibliotecas:
$ java -cp ../../algs4.jar:../../core.jar:. EuclidSketch
Two numbers, please: 450 330

6/1/16 Algoritmos e Estruturas de Dados 19


Variantes
Fazer uma cpia da barra mais baixa deslocar-se
suavemente para sobre a barra mais alta, antes de
esta baixar.
Desafios:
Animar o algoritmo de Arquimedes.
Animar o algoritmo de Newton para a raiz quadrada.
Animar o algoritmo de Euclides para calcular o mxido
divisor comum de um conjunto de nmeros (e no de
apenas dois nmeros).
Animar o crivo de Eratstenes.
Animar as torres de Hani.

6/1/16 Algoritmos e Estruturas de Dados 20


Algoritmos e Estruturas de Dados

Lio n. 3
Colees: sacos, pilhas e filas
Colees: sacos, pilhas e filas
Especificao das colees
Classes iterveis
Sacos (em ingls bags)
Implementao com arrays
Pilhas (em ingls stacks)
Implementao com listas
Filas (em ingls queues)
Implementao como exerccio.
Estudaremos hoje os sacos. As
pilhas e as filhas ficam para
uma das prximas lies.

6/1/16 Algoritmos e Estruturas de Dados 2


Colees
Colees so... colees!
s colees podemos acrescentar objetos, remov-
los e iter-los.
Podemos ainda perguntar-lhes se esto vazias e
quantos objetos tm.
Em Java teremos colees genricas, parametrizadas
pelo tipo dos objetos que contm. Por exemplo:
public class Bag<T>
{
// ... As colees apenas guardam os
} objetos. Por isso no h restries
sobre o tipo de objetos que podem
conter.

6/1/16 Algoritmos e Estruturas de Dados 3


Classes iterveis
Queremos que as nossas colees sejam iterveis,
isto , que permitam aceder sequencialmente a cada
um dos objetos da coleo.
Programaremos isso fazendo cada coleo dispor de
um mtodo iterator que devolve um iterador.
Paralelamente, declaramos a classe como
implementando a interface Iterable<T>:
public class Bag<T> implements Iterable<T>
{
// ...
}

6/1/16 Algoritmos e Estruturas de Dados 4


APIs public class Bag<T> implements Iterable<T>
{
public Bag()
public void add(T x)
public boolean isEmpty()
public int size()
public Iterator<T> iterator()
}

public class Stack<T> implements Iterable<T>


{
public Stack()
public void push(T x)
public boolean isEmpty()
public int size()
public T pop()
public Iterator<T> iterator()
}

public class Queue<T> implements Iterable<T>


{
public Queue()
public void enqueue(T x)
public boolean isEmpty()
public int size(
public T dequeue()
public Iterator<T> iterator()
}
6/1/16 Algoritmos e Estruturas de Dados 5
Capacidade?
Como indicamos a capacidade das colees?
No indicamos! H apenas um construtor, sem
argumentos.
Logo, as colees tm de ser capazes de aumentar a
capacidade, se necessrio (e, porventura, de diminuir
a capacidade, se desejvel).
Para isso, implementamo-las base de listas ligadas
ou base de arrays redimensionveis.
Um array redimensionvel um array cuja capacidade
pode mudar durante a execuo do programa.
Aparentemente a expresso array redimensionvel
um oximoro, pois habitumo-nos a que a capacidade de
um array fixada na sua criao e no muda mais.
6/1/16 Algoritmos e Estruturas de Dados 6
Sacos, implementao
Vamos implementar a classe Bag<T> usando arrays
redimensionveis.
Haver um campo items, que o array, e um campo
size, que representa o nmero de elementos
presentes no array:
public class Bag<T> implements Iterable<T>
{
private T[] items;
private int size;

// ...
Recorde que a capacidade do array est
}
registada no membro length do array.

6/1/16 Algoritmos e Estruturas de Dados 7


Sacos, construtor
Inicialmente, o array ter capacidade 1 e tamanho 0:
public class Bag<T> implements Iterable<T>
{
private T[] items;
private int size;

public Bag()
{
items = (T[]) new Object[1];
size = 0;
}
Note bem: criamos um array de
// ...
objetos, e foramos a converso
}
para array de T.

6/1/16 Algoritmos e Estruturas de Dados 8


Questo tcnica: arrays genricos
Ns gostaramos de ter programado a criao do
array genrico items assim:
items = new T[1];
No entanto, por razes tcnicas, aceitveis mas
complicadas, o Java no permite a criao de arrays
genricos.
Remedeia-se criando um array de objetos Object e
forando a converso para o tipo de arrays
pretendido.
Em geral, forar converses de tipo mau estilo.

6/1/16 Algoritmos e Estruturas de Dados 9


Supresso de avisos
Como, forar converses uma operao arriscada,
o Java emite um warning:

Evitamos o warning com uma anotao:


@SuppressWarnings("unchecked")
public Bag()
{
S usaremos o SuppressWarnings
items = (T[]) new Object[1];
neste caso da construo de arrays
size = 0; genricos!
}

6/1/16 Algoritmos e Estruturas de Dados 10


Redimensionamento
Na verdade, no redimensionamos o array: criamos
outro array com a capacidade pretendida e fazemos a
com que a varivel que apontava para o array original
passe a apontar para o array novo:
private void resize(int capacity)
{
@SuppressWarnings("unchecked")
T[] temp = (T[]) new Object[capacity];
for (int i = 0; i < size; i++) Ateno: isto presume
temp[i] = items[i]; que size <= capacity.
items = temp;
} A memria do array original fica
inacessvel e ser libertada
automaticamente pelo garbage
collector dentro em pouco.
6/1/16 Algoritmos e Estruturas de Dados 11
Mtodo add
Se, ao acrescentar um elemento ao saco, notarmos
que j no h espao no array, redimensionamo-lo
para o dobro:
public void add(T x)
{
if (size == items.length)
resize(2 * items.length);
items[size++] = x;
}

6/1/16 Algoritmos e Estruturas de Dados 12


Mtodos size e isEmpty
Estes so muito simples:
public int size()
{
return size;
}

public boolean isEmpty()


{
return size == 0;
}

6/1/16 Algoritmos e Estruturas de Dados 13


O iterador
Optamos por iterar pela ordem de entrada.
Usamos uma classe interna:
public class Bag<T> implements Iterable<T>
{
// ...
private class BagIterator implements Iterator<T>
{
private int i = 0;

public boolean hasNext()


{
return i < size;
}

public T next()
{
return items[i++];
}

public void remove()


{
throw new UnsupportedOperationException();
O mtodo remove
} no suportado.
}
6/1/16 } Algoritmos e Estruturas de Dados 14
O mtodo iterator
O mtodo iterator apenas cria e retorna um objeto
de tipo BagIterator:
public Iterator<T> iterator()
{
return new BagIterator();
}
Mais tarde, para, por exemplo, mostrar o contedo
de saco de inteiros, b, faremos:
for (int x: b)
StdOut.println(x);

6/1/16 Algoritmos e Estruturas de Dados 15


Exemplo: problema dos estdios
A federao tem a lista dos estdios, com nome, comprimento
do relvado e largura do relvado. Pretende saber quais so os
estdios com relvado de rea mxima, de entre aqueles cujo
relvado tem as dimenses regulamentares.
Para a FIFA, o comprimento do relvado deve estar entre 90 e
120 metros e a largura entre 45 e 90 metros.
Cada linha do ficheiro tem o nome (um cadeia de carateres
sem espaos) o comprimento e a largura (ambos nmeros
inteiros).
Variante do problema usado em
drag 110 85 Laboratrio de Programao 13/14.
alg_arv 124 90
light 120 80
saintlouis 118 82
alv_xxi 115 80

6/1/16 Algoritmos e Estruturas de Dados 16


Classe Stadium
Uma classe imutvel, para representar estdios:
public class Stadium
{
private final String name;
private final int length;
private final int width;

public static final int MIN_LENGTH = 90;


public static final int MAX_LENGTH = 120;
public static final int MIN_WIDTH = 45;
public static final int MAX_WIDTH = 90;

Stadium(String name, int length, int width)


{
this.name = name; Note bem: por opo de desenho, os
this.length = length; valores destes membros no podem
this.width = width;
mudar aps a construo.
}

// ...

6/1/16 Algoritmos e Estruturas de Dados 17


Classe Stadium, continuao
public class Stadium
{
// ...

public int area()


{
return length * width;
}

public boolean isLegal()


{
return MIN_LENGTH <= length && length <= MAX_LENGTH
&& MIN_WIDTH <= width && width <= MAX_WIDTH;
}

public static Stadium read() Note bem: mtodo esttico.


{
String s = StdIn.readString();
int u = StdIn.readInt();
int w = StdIn.readInt();
return new Stadium(s, u, w);
}

public String toString() Note bem: mtodo redefinido.


{
return name + " " + length + " " + width;
}
}
6/1/16 Algoritmos e Estruturas de Dados 18
Classe do problema
Usamos um saco de estdios.
O saco criado vazio (como todos os sacos) e
depois preenchido por leitura:
public class StadiumProblem
{
private Bag<Stadium> stadiums = new Bag<Stadium>();

public void read()


{
while (!StdIn.isEmpty())
stadiums.add(Stadium.read());
}

// ...
}

6/1/16 Algoritmos e Estruturas de Dados 19


Estdios selecionados
Os estdios selecionados tambm formam um saco:
public class StadiumProblem
{
// ...

public Bag<Stadium> selected()


{
Bag<Stadium> result = new Bag<Stadium>();
int maxArea = -1;
for (Stadium x: stadiums)
if (x.isLegal())
{
if (maxArea < x.area())
{
maxArea = x.area(); De cada vez que surge uma nova rea
result = new Bag<Stadium>(); mxima, recomea-se com um novo saco.
}
if (x.area() == maxArea)
result.add(x);
}
return result;
}
// ...
}
6/1/16 Algoritmos e Estruturas de Dados 20
Mtodo solve
Resolvemos o problema no mtodo solve:
public void solve()
{
Bag<Stadium> solution = selected();
if (solution.isEmpty())
StdOut.println("(empty)");
else
for (Stadium x : selected())
StdOut.println(x);
}

A funo de teste cria o objeto, l e resolve:


public static void testSolve()
{
StadiumProblem sp = new StadiumProblem();
sp.read();
sp.solve();
}

A funo main chama a funo de teste:


public static void main(String [] args)
{
testSolve();
6/1/16
} Algoritmos e Estruturas de Dados 21
Diretoria work
Tipicamente, guardaremos os nosso ficheiros de dados numa
diretoria work, dentro do projeto, ao lado das diretorias bin e
src (estas duas criadas pelo Eclipse).
Nesse caso, podemos correr o programa a partir da diretoria
work, redirigindo o input, assim, por exemplo:
$ pwd
/Users/pedro/Dropbox/AED_1516/ws2/Stadium
$ ls
bin src work
$ cd work
$ ls
lz_in_01.txt lz_in_03.txt lz_in_05.txt lz_in_07.txt
lz_in_02.txt lz_in_04.txt lz_in_06.txt lz_in_08.txt
$ java -cp ../../algs4.jar:../bin StadiumProblem < lz_in_01.txt
saintlouis 118 82
$ Tambm podemos redirigir o output, de
maneira anloga, claro.

6/1/16 Algoritmos e Estruturas de Dados 22


Correndo na diretoria bin
Alternativamente, podemos correr o programa na
diretoria bin, como antes, tendo o cuidado de ir
buscar o ficheiro diretoria work:
$ pwd
/Users/pedro/Dropbox/AED_1516/ws2/Stadium
$ ls
bin src work
$ cd bin
$ java -cp ../../algs4.jar:. StadiumProblem < ../work/lz_in_07.txt
(empty)
$ java -cp ../../algs4.jar:. StadiumProblem < ../work/lz_in_08.txt
arena 120 90
stade_france 120 90
millenium 120 90
camp_nou 120 90
bernabeu 120 90
$

6/1/16 Algoritmos e Estruturas de Dados 23


Custo do redimensionamento?
Redimensionar o array que suporta o saco parece ser um grande trabalho
suplementar. Ser que ?
Quando redimensionamos de N para 2N, quantos acessos se fazem aos arrays
items e temp?
O array temp criado com 2N elementos e cada um deles tem de ser inicializado
com null. Logo 2N acessos.
A afectao temp[i] = items[i] faz-se N vezes. Logo, ao todo, mais 2N acessos.
Portanto, ao redimensionar de 1 para 2, foram 4 acessos; de 2 para 4 foram 8
acessos; ...; de N/2 para N foram 2N acessos.
Portanto, se o array tiver N elementos e N for uma potncia de 2, teremos usado
4 + 8 + 16 + ... + 2N acessos s para o redimensionamento.
Ora 4 + 8 + 16 + ... + 2N = 4N 4.
A estes h que somar os N acessos normais, para acrescentar no novo elemento
ao saco.
Logo, ao todo teremos tido 5N 4 acessos, para N elementos no array.
Note que se tivssemos alocado logo N elementos na criao, teria havido 2N
acessos ao array, ao todo, mas em geral no sabemos o nmero de elementos
exato, por isso, ao dimensionar por excesso logo de incio pode acontecer
trabalharmos mais do que ir redimensionando aos poucos.

6/1/16 Algoritmos e Estruturas de Dados 24


Algoritmos e Estruturas de Dados

Lio n. 4
Interldio: arrays base de sacos
Interldio: arrays base de sacos
Arrays em Java e arrays em C
Classe ArrayBag<T>.
Interfaces funcionais.
Expresses lambda.

6/1/16 Algoritmos e Estruturas de Dados 2


Arrays em Java e arrays em C
Em C, um array um pedao de memria bruto.
Em Java, um array um pedao de memria no
completamente bruto: por exemplo, se o ndice estiver fora
dos limites, o array reclama, lanando uma exceo.
Em C, mesmo quando processamos um array usando ndices,
estamos de facto a usar aritmtica de apontadores.
Em Java no h aritmtica de apontadores.
A aritmtica de apontadores em C permite-nos considerar
pedaos do array com sendo subarrays, o que muito prtico.
Em Java, a classe Arrays contm uma srie de mtodos
estticos com operaes habituais sobre arrays.
Em C, podemos fazer contas com apontadores, e, por exemplo, criar um
apontador a partir de outro, por via de uma operao aritmtica. Em Java, a
nica operao que podemos fazer com apontadores copi-los.
6/1/16 Algoritmos e Estruturas de Dados 3
ArrayList<T>
ArrayList<T> uma classe genrica que implementa
arrays automaticamente redimensionveis.
Tem algumas operaes habituais para arrays, como
o acesso por ndice, para consulta e modifio, e
busca sequencial, mas no tem mtodos de
ordenao.
uma classe itervel.
Se precisamos de ordenar arrays, ento usamos
arrays mesmo e os mtodos estticos da classe
Arrays.

6/1/16 Algoritmos e Estruturas de Dados 4


Os sacos no so arrays
A caracterstica fundamental dos arrays permitirem
aceder aos elementos, por ndice, em tempo
constante.
Na classe Bag<T> prescindimos voluntariamente de
acrescentar mtodos de acesso por ndice, porque
isso no faz parte da natureza dos sacos.
Mas podemos criar os nossos prprios arrays
redimensionveis, usando a tcnicas que vimos com a
classe Bag<T>.
E podemos ir acrescentando os mtodos que
entendermos serem interessantes.

6/1/16 Algoritmos e Estruturas de Dados 5


Classe ArrayBag<T>
Para transformar um saco num array, basta acrescentar o
acesso por ndice:
Na verdade, acrescentaremos dois acessos por ndice: o
acesso absoluto, get, que falha se o ndice estiver fora dos
limites, e o acesso modular, at, que usa o resto da diviso
do ndice indicado pelo tamanho do array:
public class ArrayBag<T> implements Iterable<T>
{
public ArrayBag()
public void add(T x)
public boolean isEmpty()
public int size()
public Iterator<T> iterator()
public T get(int x)
public T at(int x)
}

6/1/16 Algoritmos e Estruturas de Dados 6


Classe ArrayBag<T>, implementao
Os membros de dados so como na classe Bag<T>, e as
operaes comuns so anlogas:
public class ArrayBag<T> implements Iterable<T>
{
private T[] items;
private int size;

// ...
}

Mesmo assim, acrescentamos um segundo construtor,


que especifica a capacidade inicial:
public ArrayBag() @SuppressWarnings("unchecked")
{ public ArrayBag(int n)
this(1); {
} items = (T[]) new Object[n];
size = 0;
}

6/1/16 Algoritmos e Estruturas de Dados 7


Outros construtores
public ArrayBag(ArrayBag<T> other)
O construtor de cpia: {
items = other.items.clone();
size = other.size;
}
Frequentemente, teremos um public ArrayBag(T[] a)
array, com os elementos do {
items = a.clone();
qual queremos criar um size = a.length;
}
ArrayBag:
public ArrayBag(Iterator<T> it)
Mais geralmente, teremos um {
this();
iterador: while (it.hasNext())
add(it.next());
}

... ou ento um objeto de uma public ArrayBag(Iterable<T> x)


{
classe itervel: this(x.iterator());
}

6/1/16 Algoritmos e Estruturas de Dados 8


Mtodos get e at
O mtodo get falha se o argumento representar um
ndice invlido:
public T get(int x)
{
assert 0 <= x;
assert x < size;
return items[x];
}
O mtodo at mais geral: se o argumento no
representar um ndice vlido, corrigido modularmente:
public T at(int x)
{
// Note: in Java (-5)%3 is -2;
assert size > 0;
x = x % size;
if (x < 0)
Assim, a.at(-1) representa o
x += size;
return items[x];
ltimo elemento do array a.
}
6/1/16 Algoritmos e Estruturas de Dados 9
Contando
Dado um array, frequentemente queremos contar o
nmero de ocorrncias de um dado valor.
Com mais generalidade, queremos contar o nmero
de elementos que verificam uma dada propriedade.
A propriedade representado por um predicado:
public int count(Predicate<T> p)
{
int result = 0;
for (int i = 0; i < size; i++)
if (p.test(items[i]))
result++;
return result; Tecnicamente, Predicate<T> uma interface
} funcional que representa as funes
booleanas com um argumento de tipo T.
6/1/16 Algoritmos e Estruturas de Dados 10
Testando com expresses lambda
Eis uma funo de teste, que consulta o array para
saber quantas palavras existem com o comprimento
obtido interativamente:
public static void testCount()
{
String[] cities = new String[] {
"lisboa", "porto", "faro", "coimbra", "tavira",
"aveiro", "braga", "viseu", "beja", "leiria",
"guarda", "chaves", "portalegre", "funchal", "horta"};
ArrayBag<String> a = new ArrayBag<>(cities);
while (!StdIn.isEmpty())
{
int n = StdIn.readInt();
int z = a.count(x -> x.length() == n);
StdOut.println(z);
A expresso a vermelho
}
} uma expresso lambda.

6/1/16 Algoritmos e Estruturas de Dados 11


Filtrando
Mais interessante do que saber quantos so os que
verificam o predicado saber quais so.
Chama-se a isto filtrar o array (segundo o
predicado).
O resultado outro array, bem entendido:
public ArrayBag<T> filter(Predicate<T> p)
{
ArrayBag<T> result = new ArrayBag<>();
for (int i = 0; i < size; i++)
if (p.test(items[i]))
result.add(items[i]);
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 12


Testando a filtrao
Eis uma funo de teste, que filtra as palavras que
terminam pela cadeia lida na consola, e depois
mostra o contedo do array resultado:
public static void testFilter()
{
String[] cities = new String[] {
"lisboa", "porto", "faro", "coimbra", "tavira",
"aveiro", "braga", "viseu", "beja", "leiria",
"guarda", "chaves", "portalegre", "funchal", "horta"};
ArrayBag<String> a = new ArrayBag<>(cities);
while (!StdIn.isEmpty())
{
String s = StdIn.readLine();
ArrayBag<String> z = a.filter(x -> x.endsWith(s));
for (String i : z)
StdOut.println(i);
}
}

6/1/16 Algoritmos e Estruturas de Dados 13


Visitao
Visitar uma coleo usar cada elemento da coleo
com argumento de uma dada operao.
Com arrays, exprime-se assim:
public void visit(Consumer<T> a)
Consumer<T> a interface
{
for (int i = 0; i < size; i++) funcional que representa as
a.accept(items[i]); funes com um argumento
} de tipo T e resultado void.

Com isto, a parte final da funo de teste da pgina


anterior fica mais elegante:
public static void testFilter()
{
...
while (!StdIn.isEmpty())
{
String s = StdIn.readLine();
ArrayBag<String> z = a.filter(x -> x.endsWith(s));
z.visit(x -> StdOut.println(x));
}
6/1/16 } Algoritmos e Estruturas de Dados 14
Mximo
O mximo de um array calcula-se sempre com relao a
uma funo de comparao.
Quer dizer, o mximo de um array aquele elemento
que nunca d negativo quando qualquer um dos outros
compara com ele.
Calcula-se assim:
Comparator <T> a
public T max(Comparator<T> cmp) interface funcional que
{ representa as funes de
assert size > 0; comparao do tipo T.
T result = items[0];
for (int i = 1; i < size; i++)
if (cmp.compare(result, items[i]) < 0)
result = items[i];
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 15
Testando o mximo
Observe, notando para achar o mnimo basta trocar
o sinal do resultado da comparao:
public static void testMax()
{
String[] cities = new String[] {
"lisboa", "porto", "faro", "coimbra", "tavira",
"aveiro", "braga", "viseu", "beja", "leiria",
"guarda", "chaves", "portalegre", "funchal", "horta"};
ArrayBag<String> a = new ArrayBag<>(cities);
// the last (max) in alphabetical order
String s1 = a.max((x, y) -> x.compareTo(y));
// the first (min) in alphabetical order
String s2 = a.max((x, y) -> -x.compareTo(y));
// the first longest
String s3 = a.max((x, y) -> x.length() - y.length());
// the first shortest
String s4 = a.max((x, y) -> -(x.length() - y.length()));
StdOut.printf("%s %s %s %s\n", s1, s2, s3, s4);
}

6/1/16 Algoritmos e Estruturas de Dados 16


ArrayBags no problema dos estdios
Em vez de Bag<Stadium> usamos ArrayBag<Stadium>:
public class StadiumProblem2016
{
private ArrayBag<Stadium> stadiums = new ArrayBag<>();
// ...
}

A funo selected fica assim:


public ArrayBag<Stadium> selected()
{
ArrayBag<Stadium> result = stadiums.filter(x -> x.isLegal());
if (!result.isEmpty())
{
Stadium max = result.max((x, y) -> x.area() - y.area());
result = result.filter(x -> x.area() == max.area());
}
return result;
}
Palavras para qu?
6/1/16 Algoritmos e Estruturas de Dados 17
E ainda, mapeamento
Mapear um array construir um array em que cada elemento
resulta de uma dada transformao do elemento
correspondente do array objeto:
Primeiro caso: mapear para outro array do mesmo tipo:
public ArrayBag<T> map(UnaryOperator<T> f)
{
UnaryOperator<T> a
ArrayBag<T> result = new ArrayBag<>();
for (int i = 0; i < size; i++)
interface funcional que
result.add(f.apply(items[i])); representa as funes com
return result; um argumento de tipo T e
} resultado de tipo T.
Segundo caso: mapear para um array de outro tipo:
public <R> ArrayBag<R> map2(Function<T, R> f)
{
Function<T, R> a interface
ArrayBag<R> result = new ArrayBag<>();
for (int i = 0; i < size; i++) funcional que representa as
result.add(f.apply(items[i])); funes com um argumento
return result; de tipo T e resultado de tipo
} R.
6/1/16 Algoritmos e Estruturas de Dados 18
E, para terminar, enrolamento
Enrolar um array aplicar sucessivamente uma dada funo
binria ao resultado anterior e ao elemento seguinte do array,
sendo dado tambm o resultado inicial:
public T fold(BinaryOperator<T> f, T zero)
{
T result = zero;
for (int i = 0; i < size; i++)
result = f.apply(result, items[i]);
return result;
}
public static void testFold()
{
BinaryOperator<Integer> plus = (x, y) -> x + y;
BinaryOperator<String> concat = (x, y) -> x + y;
ArrayBag<Integer> b = new ArrayBag<>(new Integer[]{4, 8, 2, 7});
ArrayBag<String> s = new ArrayBag<>(new String[]{"aaa", "bbb", "ccc", "ddd"});
int z1 = b.fold(plus, 0);
StdOut.println(z1); Neste exemplo, as expresses lambda vm
String z2 = s.fold(concat, ""); representadas por variveis, para ilustrar a
StdOut.println(z2);
}
tcnica, mas isso no essencial.
6/1/16 Algoritmos e Estruturas de Dados 19
Exemplo, a rea mdia
Eis uma funo para calcular a mdia da reas dos
estdios com dimenso legal, assumindo que h pelo
menos um estdio desses:
double averageLegalArea()
{
ArrayBag<Stadium> legal = stadiums.filter(x -> x.isLegal());
assert !legal.isEmpty();
ArrayBag<Integer> areas = legal.map2(x -> x.area());
int total = areas.fold((x, y) -> x + y, 0);
return (double) total / legal.size();
}
Moral da histria: nos processamentos
sequenciais, faz-se tudo com map, filter e fold!

6/1/16 Algoritmos e Estruturas de Dados 20


Algoritmos e Estruturas de Dados

Lio n. 5
Pilhas
Pilhas
Implementao com arrays redimensionveis.
Implementao com listas ligadas.
Aplicao: avaliao de expresses
aritmticas usando o algoritmo da pilha dupla,
de Dijkstra.

6/1/16 Algoritmos e Estruturas de Dados 2


Pilhas
As pilhas chamam-se pilhas porque funcionam como
pilhas...; pilhas de livros, de pratos, de papis, no
pilhas no sentido de baterias eltricas!
A ideia que o elemento que sai o ltimo que
entrou.
Dizemos que as pilhas so colees LIFO: last in first
out.

6/1/16 Algoritmos e Estruturas de Dados 3


API da pilha
J sabemos:
public class Stack<T> implements Iterable<T>
{
public Stack()
public void push(T x)
public boolean isEmpty()
public int size()
public T pop()
public Iterator<T> iterator()
}
Comparando com Bag<T>, temos push em vez de
add, temos pop e o iterador emitir os elementos
por ordem inversa de entrada na pilha (os que
entraram mais recentemente surgem primeiro).

6/1/16 Algoritmos e Estruturas de Dados 4


Implementao com arrays redimensionveis
anloga da classe Bag<T>:
public class Stack<T> implements Iterable<T>
{
private T[] items;
private int size;

public Stack()
{
// ...
}

private void resize(int capacity)


{
// ...
}

public void push(T x)


{
if (size == items.length) Se o array cresce quando est
resize(2 * items.length); cheio, ser que deve encolher
items[size++] = x;
}
quando fica relativamente vazio?

// ...
6/1/16 Algoritmos e Estruturas de Dados 5
Encolhendo no pop
A estratgia ser encolher para metade quando o
tamanho estiver a 25% da capacidade:
public class Stack<T> implements Iterable<T>
{
// ... Anula-se o apontador para que no
permanea no array uma referncia para o
public T pop() elemento removido, o que atrasaria a
{ garbage collection. Se no anulssemos,
T result = items[--size]; teramos vadiagem (em ingls, loitering):
referncias no array para valores inteis.
items[size] = null;
if (size > 0 && size == items.length / 4)
resize(items.length / 2);
return result;
} Note bem: no seria sensato encolher para
metade quando o array estivesse meio cheio,
// ... pois nesse caso ele ficava cheio logo aps ter
encolhido, e se houvesse um push a seguir, teria
de crescer de novo.
6/1/16 Algoritmos e Estruturas de Dados 6
Iterador reverso
Nas pilhas, queremos iterar os elementos de maneira a ver
primeiro os que entraram na pilha mais recentemente:
// ...

private class StackIterator implements Iterator<T>


{
private int i = size;

public boolean hasNext()


{
return i > 0;
}

public T next()
{
return items[--i];
}

// ...

public Iterator<T> iterator()


{
return new StackIterator();
}

6/1/16 // ... Algoritmos e Estruturas de Dados 7


Funo de teste
Eis uma funo de teste, que empilha os nmeros lidos e
desempilha com -. No fim mostra o tamanho, se vazia e os
elementos, por iterao: $ java -cp ../../algs4.jar:. Stack
private static void testStackInteger() 6 3 9 7
{ -
Stack<Integer> s = new Stack<Integer>(); 7
while (!StdIn.isEmpty()) -
{ 9
String t = StdIn.readString(); 5 8 2
if (!"-".equals(t))
-
s.push(Integer.parseInt(t)); Ctrl-D aqui!
else if (!s.isEmpty()) 2
{ size of stack = 4
int x = s.pop(); is stack empty? false
StdOut.println(x); items: 8 5 3 6
} $
}
StdOut.println("size of stack = " + s.size());
StdOut.println("is stack empty? " + s.isEmpty());
StdOut.print("items:");
for (int x : s)
StdOut.print(" " + x);
StdOut.println();
}

6/1/16 Algoritmos e Estruturas de Dados 8


Implementao com listas ligadas
A lista ligada programada em termos da classe
interna Node, a qual detm um campo para o valor e
uma referncia para o prximo n:
public class StackLists<T> implements Iterable<T>
{
private class Node
{
private T value; Ver transparentes de POO,
private Node next; pgina 13-8 e seguintes.
Node(T x, Node z)
{
value = x;
next = z;
}
}

// ...

6/1/16 Algoritmos e Estruturas de Dados 9


O primeiro n
Na pilha, um dos membros o primeiro n da lista; o
outro o tamanho:
public class StackLists<T> implements Iterable<T>
{
// ...

private Node first;


private int size;

// ...

Omitimos o construtor por defeito, uma vez que os


valores iniciais automticos (null para first e 0 para
size) so os que nos interessam.

6/1/16 Algoritmos e Estruturas de Dados 10


Empilhar, desempilhar
Para empilhar, criamos um novo n, que passa a ser o
primeiro, ligando-o ao que estava em primeiro antes:
public void push(T x)
{
first = new Node(x, first);
size++;
}

Para desempilhar, devolvemos o valor do primeiro


n, e avanamos para o seguinte a referncia:
public T pop()
{
T result = first.value;
first = first.next;
size--;
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 11
isEmpty, size
Os mtodos isEmpty e size so muito simples:
public boolean isEmpty()
{
return first == null;
}

public int size()


{
return size;
}
Poderamos ter omitido o campo size e ter
programado a funo size contando
iterativamente os ns, mas, por deciso de
desenho preferimos que funo size seja
calculada em tempo constante.

6/1/16 Algoritmos e Estruturas de Dados 12


Iterador de listas
O cursor uma referncia para o n corrente:
private class StackIterator implements Iterator<T>
{
private Node cursor = first; Repare que o n first contm o
valor que mais recentemente foi
public boolean hasNext()
{
empilhado, de entre todos os que
return cursor != null; esto na pilha.
}

public T next()
{
T result = cursor.value;
cursor = cursor.next;
return result;
}

// ... Tal como na outra implementao,


}
os valores so enumerados pela
ordem inversa de entrada na pilha.
public Iterator<T> iterator()
{
return new StackIterator();
6/1/16 } Algoritmos e Estruturas de Dados 13
Avaliao de expresses aritmticas
Como exemplo de aplicao das listas, estudemos a avaliao
de expresses aritmticas usando o algoritmo da pilha dupla
de Dijkstra.
As expresses aritmtica que nos interessam so
completamente parentetizadas, para evitar as questes da
precedncia dos operadores.
Admitiremos os quatro operadores bsicos (+, -, * e /)
e a raiz quadrada (sqrt).
Os operandos so nmeros decimais, com ou sem parte
decimal.
Exemplos:
((7*2)+5)
((sqrt(9+16))/3)
(((6*3)+(7-3))/2)

6/1/16 Algoritmos e Estruturas de Dados 14


Pilha dupla de Dijkstra
Usamos duas pilhas: uma pilha de cadeias, para os operadores,
e uma pilha de nmeros, para os operandos e para os
resultados parciais.
Varremos a expresso da esquerda para a direita, recolhendo
os operadores, os operandos e os parnteses:
Cada operador empilhado na pilha dos operandos.
Cada operando empilhado na pilha dos nmeros.
Os parnteses a abrir so ignorados.
Ao encontrar um parntesis a fechar, desempilha-se um operador,
depois desempilham-se os operandos (em nmero apropriado ao
operador), aplica-se a operao a esses operandos e empilha-se o
resultado, na pilha dos nmeros.
Depois de o ltimo parntesis ter sido processado, a pilha dos
nmeros ter um nico elemento, cujo valor o valor da expresso.

6/1/16 Algoritmos e Estruturas de Dados 15


Classe DijkstraTwoStack
Primeiro as operaes de suporte:
public final class DijkstraTwoStack
{
private DijkstraTwoStack() { }

private static String[] knownOperators = {"+", "-", "*", "/", "sqrt"};


private static String[] binaryOperators = {"+", "-", "*", "/"};
private static String[] unaryOperators = {"sqrt"};

private static <T> boolean has(T[] a, T x)


{
for (T i : a)
if (x.equals(i)) Busca linear.
return true;
return false;
}

private static boolean isOperator(String s)


{
return has(knownOperators, s);
}

private static boolean isBinaryOperator(String s)


{
return has(binaryOperators, s);
}

private static boolean isNumeric(String s)


{
return Character.isDigit(s.charAt(0));
}
6/1/16 // ... Algoritmos e Estruturas de Dados 16
Avaliao da expresso
A seguinte funo meramente exprime as regras do algoritmo:
private static double evaluationSimple(String[] ss)
{ A expresso repre-
Stack<String> operators = new Stack<String>(); sentada pelo array dos
Stack<Double> values = new Stack<Double>(); tquenes: operandos,
for (String s : ss) operadores e
if ("(".equals(s)) { } // nothing to do parntesis.
else if (isOperator(s)) operators.push(s);
else if (isNumeric(s)) values.push(Double.parseDouble(s));
else if (")".equals(s))
{
String op = operators.pop();
double z;
// ...
values.push(z);
}
else throw new UnsupportedOperationException();
return values.pop();
}

6/1/16 Algoritmos e Estruturas de Dados 17


Aplicao dos operadores
{
String op = operators.pop();
// StdOut.println(op);
double y = values.pop();
// StdOut.println(y);
double x = isBinaryOperator(op) ? values.pop() : 0.0;
// StdOut.println(x);
double z;
if ("+".equals(op))
z = x + y;
else if ("-".equals(op))
z = x - y; Comprido e
else if ("-".equals(op)) desinteressante.
z = x + y;
else if ("*".equals(op))
z = x * y;
else if ("/".equals(op))
z = x / y;
else if ("sqrt".equals(op))
z = Math.sqrt(y);
else throw new UnsupportedOperationException();
// will not happen;
values.push(z);
6/1/16 } Algoritmos e Estruturas de Dados 18
Funo de teste
Para abreviar, determinamos que os tquenes na expresso
vm separados por espaos.
Assim, podemos obter o array de tquenes diretamente,
usando a funo split, da classe String:
public static void testEvaluationSimple()
{
while (StdIn.hasNextLine())
{
String line = StdIn.readLine();
double z = evaluationSimple(line.split(" "));
StdOut.println(z);
} O mtodo split, da
} classe String, devolve
split um array de String.
public String[] split(String regex)
Splits this string around matches of the
given regular expression.

6/1/16 Algoritmos e Estruturas de Dados 19


Experincia
Na linha de comando:
pedros-imac-4:bin pedro$ java -cp ../../algs4.jar:. DijkstraTwoStack
( 8 + 1 )
9.0
( sqrt 2 )
1.4142135623730951
( ( 6 + 9 ) / 3 )
5.0
( ( 5 + 1 ) / ( 7 + 17 ) )
0.25
( sqrt ( 1 + ( sqrt ( 1 + ( sqrt ( 1 + ( sqrt ( 1 + 1 ) ) ) ) ) ) ) )
1.6118477541252516

Prximas tarefas:
Evitar ter de separar os tquenes por espaos.
Evitar ter de distinguir os operadores com if-else em
cascata
6/1/16 Algoritmos e Estruturas de Dados 20
Algoritmos e Estruturas de Dados

Lio n. 6
Avaliao de expresses aritmticas
Avaliao de expresses aritmticas
Utilizao de tabelas de disperso.
Toquenizao de cadeias de caracteres.
Expresses regulares.
Esta lio antecipa matrias que
veremos mais adiante com mais
profundidade.

6/1/16 Algoritmos e Estruturas de Dados 2


Tabela de operaes
Na avaliao de expresses aritmticas, faz-nos falta
uma tabela de operadores, que associe a cada sinal
de operao o respetivo operador.
Cada sinal de operao uma cadeia de caracteres.
Cada operador um DoubleBinaryOperator ou um
DoubleUnaryOperator.
Portanto, precisamos de duas tabelas: uma para as
operaes binrias e outra para as operaes
unrias.

6/1/16 Algoritmos e Estruturas de Dados 3


Interface Table<K, V>
Uma tabela uma classe que implementa a seguinte
interface:
public interface Table<K, V>
{
public V get(K key);
public void put(K key, V value);
public void delete(K key);
public boolean has(K key);
public boolean isEmpty();
public int size();
public Iterable<K> keys();
}

A tabela guarda pares <K, V>. Os K so as chaves; os V


so os valores.
O acesso aos valores faz-se atravs da chave.

6/1/16 Algoritmos e Estruturas de Dados 4


Tabela de disperso: HashTable<K, V>
Dispomos de uma classe para tabelas, que usa a
tcnica da disperso de chaves:
public class HashTable<K, V> implements Table<K, V>, Iterable<K>
{
private int size;
private int capacity;
private K[] keys;
Internamente, as chaves e os valores
private V[] values; esto guardados em arrays, mas isso
no nos diz respeito.
public HashTable()
{
// ...
}

// ...
}

Mais adiante, estudaremos isto; agora, s queremos


usar.
6/1/16 Algoritmos e Estruturas de Dados 5
Preenchimento das tabelas
As tabelas so preenchidas no construtor:
public class DijkstraTwoStack2016
{
private HashTable<String, DoubleBinaryOperator> ops2;
private HashTable<String, DoubleUnaryOperator> ops1;

public DijkstraTwoStack2016()
{ Temos aqui mais operaes do
ops2 = new HashTable<>(); que no exerccio da lio
ops2.put("+", (x, y) -> x + y);
ops2.put("-", (x, y) -> x - y);
anterior.
ops2.put("*", (x, y) -> x * y);
ops2.put("/", (x, y) -> x / y);
ops2.put("%", (x, y) -> x % y);
ops2.put("max", (x, y) -> x >= y ? x : y);
ops2.put("min", (x, y) -> x <= y ? x : y);
ops2.put("^", (x, y) -> Math.pow(x, y));

ops1 = new HashTable<>();


ops1.put("inv", x -> 1/x);
ops1.put("sq", x -> x*x);
ops1.put("sqrt", x -> Math.sqrt(x));
// ...
}

6/1/16 Algoritmos e Estruturas de Dados 6


Avaliao
private double evaluationSimple(String[] ss)
{
Stack<String> operators = new Stack<String>();
Stack<Double> values = new Stack<Double>(); private boolean isOperator(String s)
for (String s : ss) {
if ("(".equals(s)) { } // nothing to do return ops2.has(s) || ops1.has(s);
else if (isOperator(s)) operators.push(s); }
else if (isNumeric(s)) values.push(Double.parseDouble(s));
else if (")".equals(s))
{
String op = operators.pop();
DoubleBinaryOperator dbo = ops2.get(op);
if (dbo != null)
{
// ...
}
else
{
DoubleUnaryOperator duo = ops1.get(op);
if (duo != null)
{
// ...
}
else
assert false; // cannot happen.
}
}
else throw new UnsupportedOperationException();
return values.pop();
}
6/1/16 Algoritmos e Estruturas de Dados 7
Avaliao dos operadores
private double evaluationSimple(String[] ss)
{
// ...
else if (")".equals(s))
{
String op = operators.pop();
DoubleBinaryOperator dbo = ops2.get(op);
if (dbo != null)
{
double y = values.pop();
double x = values.pop();
double z = dbo.applyAsDouble(x, y);
values.push(z);
}
else
{
DoubleUnaryOperator duo = ops1.get(op);
if (duo != null)
{
double x = values.pop();
double z = duo.applyAsDouble(x);
values.push(z);
}
else
// ...
}
6/1/16 Algoritmos e Estruturas de Dados 8
Toquenizao
Precisamos de transformar a cadeia lida na sequncia de
tquenes para avaliao.
Por exemplo, em sqrt(3.0*3.0+4.0*4.0)*2, os tquenes
so sqrt, (, 3.0, *, 3.0, +, 4.0, *, 4.0, ), *,
2.
Quer dizer, um tquene ou um identificador ou um
nmero ou um sinal de operao ou um parntesis a
abrir ou um parntesis a fechar.
Os espaos so apenas separadores, sem outro valor.
H nmeros inteiros e nmeros com parte decimal.
No nosso caso, um sinal de operao formado por um
nico caractere, que no nem uma letra nem um
algarismo.

6/1/16 Algoritmos e Estruturas de Dados 9


Classe StringUtils
Reunimos nesta classe algumas funes suplementares sobre
cadeias de caracteres.
Neste problema, precisamos de analisar sequncias de
caracteres dentro da cadeia.
Usaremos as seguintes funes:
public class StringUtils
{
public static int countWhile(String s, Predicate<Character> p)
{
int n = s.length();
int result = 0;
while (result < n && p.test(s.charAt(result)))
result++;
return result;
}

// ...
}
6/1/16 Algoritmos e Estruturas de Dados 10
StringUtils: take e drop
Observe:
public static String take(String s, int n)
{
assert n > 0;
if (n > s.length())
n = s.length();
return s.substring(0, n);
}

public static String drop(String s, int n)


{
assert n > 0;
if (n > s.length())
n = s.length();
return s.substring(n, s.length());
}

public static String takeWhile(String s, Predicate<Character> p)


{
return take(s, countWhile(s, p));
}

public static String dropWhile(String s, Predicate<Character> p)


{
return drop(s, countWhile(s, p));
6/1/16
} Algoritmos e Estruturas de Dados 11
Toquenizando
Veja com ateno: private
private
static
static
final
final
char
char
SPACE = ' ';
PERIOD = '.';
public static ArrayBag<String> tokens(String s) private static final char RPAREN = ')';
{ private static final char LPAREN = '(';
ArrayBag<String> result = new ArrayBag<>();
while (!s.isEmpty())
{
char c = s.charAt(0);
if (c == SPACE)
s = StringUtils.drop(s, 1);
else if (c == LPAREN || c == RPAREN)
{
result.add(StringUtils.take(s, 1));
s = StringUtils.drop(s, 1);
}
else if (Character.isLetterOrDigit(c))
{
result.add(StringUtils.takeWhile(s, x -> Character.isLetterOrDigit(x) || x == PERIOD));
s = StringUtils.drop(s, result.at(-1).length());
}
else
{
result.add(StringUtils.take(s, 1));
s = StringUtils.drop(s, 1);
}
}
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 12


Nova funo de teste
Lemos, uma linha, toquenizamos, etc.:
public static void testEvaluationTokenized()
{
DijkstraTwoStack2016 dts = new DijkstraTwoStack2016();
while (StdIn.hasNextLine())
{
String line = StdIn.readLine();
ArrayBag<String> t = tokens(line);
double z = dts.evaluationSimple(t.toArray());
StdOut.println(z);
}
}

Ateno ao mtodo toArray, da classe ArrayBag:


public T[] array()
{
@SuppressWarnings("unchecked")
T[] result = (T[])java.lang.reflect.Array.newInstance(items[0].getClass(), size);
for (int i = 0; i < size; i++)
result[i] = items[i];
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 13
Expresses regulares
Modernamente, a anlise de cadeias para efeitos de
toquenizao faz-se com expresses regulares.
Uma expresso regular uma cadeia de caracteres
que representa de forma compacta um conjunto de
cadeias de caracteres.
A funo find da classe de biblioteca Matcher,
procura a prxima subcadeia que pertence ao
conjunto de cadeias especificado pela expresso
regular, se houver uma tal cadeia.
Essa cadeia depois obtida pela funo group.
A expresso regular compilada por meio da funo
compile da classe de biblioteca Pattern.

6/1/16 Algoritmos e Estruturas de Dados 14


Expresses regulares, exemplos
a: representa o conjunto cujo nico elemento a cadeia a.
abc: representa o conjunto cujo nico elemento a cadeia abc.
a+: representa o conjunto das cadeias no vazias, formadas exclusivamente
pela letra a.
(abc)+: representa o conjunto das cadeias no vazias, formadas
exclusivamente por uma ou mais repeties da cadeia abc.
abc+: representa o conjunto das cadeias cujo primeiro caractere a, cujo
segundo caractere b, cujos restantes caracteres so c, havendo pelo
menos um c.
(a|e|i|o|u): representa o conjunto formado pelas cadeias a, e, i, o e
u.
(a|e|i|o|u)+: representa o conjunto das cadeias no vazias formadas
exclusivamente pelas letra a, e, i, o, e u.
[a-zA-Z]: representa o conjunto das cadeias formadas por uma nica letra,
minscula ou maiscula.
[a-zA-Z]+: representa o conjunto dos nomes, isto , das cadeias no vazias
formadas exclusivamente por letras, minsculas ou maisculas.
6/1/16 Algoritmos e Estruturas de Dados 15
Expresses regulares, mais exemplos
[0-9]: representa o conjunto das cadeias formadas por um nico algarismo.
[0-9]+: representa o conjunto dos numerais decimais, isto , das cadeias no
vazias formadas exclusivamente por algarismos decimais.
[a-zA-Z][a-zA-Z_0-9]*: o conjunto dos identificadores, isto , cadeias que
comeam por um letra seguida por zero ou mais letras, algarismos ou caractere de
sublinhado.
[a-zA-Z]+|[0-9]+: a reunio do nomes e dos numerais decimais.
\S: o conjunto das cadeias formadas por um nico caractere que no um
espao em branco (em ingls, whitespace).
\d: o mesmo que [0..9].
\w: o mesmo que [a-zA-Z_0-9].
\d+: o mesmo que [0..9]+.
\w+: o mesmo que [a-zA-Z_0-9]+.
\d+\.\d+: os numerais com parte inteira e parte decimal, ambas no vazias,
separadas por um ponto.
Para mais informao sobre expresses regulares em Java, veja
http://docs.oracle.com/javase/tutorial/essential/regex/index.html.
6/1/16 Algoritmos e Estruturas de Dados 16
Aprendendo as expresses regulares
public static void learnRegularExpressions()
{
StdOut.print("Enter your regular expression: ");
String regex = StdIn.readLine();
Pattern pattern = Pattern.compile(regex); Esta funo pertence classe
StdOut.print("Enter a string to search: "); RegularExpressions. Aceita uma
while (StdIn.hasNextLine()) expresso regular, depois uma
{
String line = StdIn.readLine();
sequncia de cadeias (at ao fim
Matcher matcher = pattern.matcher(line); dos dados), aplicando a
int n = 0; // number of matches expresso regular a cada uma das
while (matcher.find()) cadeias.
{
String group = matcher.group();
int start = matcher.start();
int end = matcher.end();
int size = end - start;
StdOut.printf("Group: \"%s\". Start: %d. Length: %d\n", group, start,
size);
n++;
}
StdOut.printf("Number of matches: %d\n", n);
StdOut.print("Enter another string to search: ");
}
} Adaptado de http://docs.oracle.com/javase/tutorial/essential/regex/test_harness.html.
6/1/16 Algoritmos e Estruturas de Dados 17
Exemplos
$ java -cp ../../algs4.jar:. RegularExpressions
Enter your regular expression: (a|e|i|o|u)+
Enter a string to search: auuejjiiiapppahhhaip
Group: "auue". Start: 0. Length: 4
Group: "iiia". Start: 6. Length: 4
Group: "a". Start: 13. Length: 1
Group: "ai". Start: 17. Length: 2
Number of matches: 4

$ java -cp ../../ algs4.jar:. RegularExpressions


Enter your regular expression: [0-9]+
Enter a string to search: hhhh76lshf3yyy66666666a
Group: "76". Start: 4. Length: 2
Group: "3". Start: 10. Length: 1
Group: "66666666". Start: 14. Length: 8
Number of matches: 3

$ java -cp ../../ algs4.jar:. RegularExpressions


Enter your regular expression: [0-9]+|[a-z]+|\S
Enter a string to search: aaa+15***34/(57)
Group: "aaa". Start: 0. Length: 3
Group: "+". Start: 3. Length: 1
Group: "15". Start: 4. Length: 2
Group: "*". Start: 6. Length: 1
Group: "*". Start: 7. Length: 1
Group: "*". Start: 8. Length: 1
Group: "34". Start: 9. Length: 2
Group: "/". Start: 11. Length: 1
Group: "(". Start: 12. Length: 1
Group: "57". Start: 13. Length: 2
Group: ")". Start: 15. Length: 1
6/1/16Number of matches: 11 Algoritmos e Estruturas de Dados 18
Grupos
Podemos obter os grupos que quisermos,
parametrizando convenientemente a expresso regular:
public final class RegularExpressions
{
// ...
public static ArrayBag<String> groups(String s, String regex)
{
ArrayBag<String> result = new ArrayBag<>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
while (matcher.find())
{
String z = matcher.group();
result.add(z);
}
return result;
}
// ...

6/1/16 Algoritmos e Estruturas de Dados 19


Tquenes
Para obter os tquenes de uma expresso aritmtica,
basta indicar a expresso regular apropriada:
public class DijkstraTwoStack2016
{
// ...

public static final String REGEX_TOKEN =


"\\d+\\.\\d+|\\d+|[a-zA-Z]\\w+|\\S";

public static ArrayBag<String> tokensRegEx(String s)


{
return RegularExpressions.groups(s, REGEX_TOKEN);
}

// ... Ateno: ao escrever expresses regulares


literais num programa, temos de escapar o
carcter \, o qual tem um significado especial
quando inserido numa cadeia literal.
6/1/16 Algoritmos e Estruturas de Dados 20
Anlise da expresso regular
A expresso regular que usmos tem quatro alternativas:
public static final String REGEX_INTEGER = "\\d+";
public static final String REGEX_DECIMAL =
"\\d+\\.\\d+";
public static final String REGEX_IDENTIFIER =
"[a-zA-Z]\\w+";
public static final String REGEX_SINGLE_NON_WHITESPACE =
"\\S";
public static final String REGEX_TOKEN2 =
REGEX_DECIMAL + "|" + REGEX_INTEGER
+ "|" + REGEX_IDENTIFIER + "|"
+ REGEX_SINGLE_NON_WHITESPACE;

A expresso dos decimais tem de vir antes da dos inteiros.


Caso contrrio, a parte inteira seria considerada um grupo, e
perdia-se a parte decimal.
De facto, as alternativas so processadas da esquerda para a
direita, at uma acertar.
6/1/16 Algoritmos e Estruturas de Dados 21
Toque final
Podemos agora substituir a toquenizao que usa a
classe StringUtils pela que feita base de
expresses regulares:
public static void testEvaluationRegEx()
{
DijkstraTwoStack2016 dts = new DijkstraTwoStack2016();
while (StdIn.hasNextLine())
{
String line = StdIn.readLine();
ArrayBag<String> t = tokensRegEx(line);
double z = dts.evaluationSimple(t.toArray());
StdOut.println(z);
}
}

6/1/16 Algoritmos e Estruturas de Dados 22


Algoritmos e Estruturas de Dados

Lio n. 7
Filas
Filas
Filas simples.
Implementao das filas simples com listas
ligadas.
Filas com prioridade.
Implementao das filas com prioridade com
heaps.

6/1/16 Algoritmos e Estruturas de Dados 2


Filas
As filas so a contrapartida informtica das filas de
espera que encontramos na vida corrente.
A ideia que o prximo elemento a ser atendido o
que est na fila h mais tempo
Dizemos que as filas so colees FIFO: first in first out.
Nas filas com prioridade, elemento que entrarem mais
tarde podero passar frente de outros que j estavam
na fila, por terem maior prioridade.

6/1/16 Algoritmos e Estruturas de Dados 3


API da fila simples
J a conhecemos:
public class Queue<T> implements Iterable<T>
{
public Queue()
public void enqueue(T x)
public boolean isEmpty()
public int size(
public T dequeue()
public T front() Nota: o mtodo front no apareceu na lio 3.
public Iterator<T> iterator()
}

Comparando com Stack<T>, temos enqueue em vez de push,


dequeue em vez de pop, e front em vez de top. O iterador
emitir os elementos por ordem de entrada na fila. (Na pilha,
era ao contrrio). Nota: o mtodo top da classe Stack<T> no apareceu
ainda nesta coleo de transparentes. No entanto, est na
classe fornecida.
6/1/16 Algoritmos e Estruturas de Dados 4
Implementao com listas
semelhante a StackLists<T>, com uma diferena essencial: os
elementos so acrescentados no fim da lista e retirados do
incio da lista.
Para isso, temos de manter uma referncia para o ltimo n:
public class Queue<T> implements Iterable<T>
{
private class Node public Queue()
{ {
// ... first = null;
} last = null;
size = 0;
private Node first; }
private Node last;
private int size;
Nas pilhas os elementos eram
// ... acrescentados no incio da lista e
} retirados tambm do incio da lista.
Por isso bastava a referncia first.
6/1/16 Algoritmos e Estruturas de Dados 5
Entrar na fila
Para fazer um elemento entrar na fila, primeiro criamos um
novo n para esse elemento.
Depois, das duas uma:
A fila estava vazia: ento o novo n passa a ser o primeiro e ltimo n
da fila.
A fila no estava vazia: ento o novo n passa a ser o ltimo.
public void enqueue(T x)
{
Node z = new Node(x, null);
if (first == null)
{
last = z;
first = z;
}
else
{
last.next = z; Aqui o novo n ligado ao
last = z; anterior ltimo n.
}
size++;
}
6/1/16 Algoritmos e Estruturas de Dados 6
Sair da fila
Para fazer um elemento sair da fila, basta avanar a referncia
first para o n seguinte. Se no houver n seguinte, first fica a
valer null, e, neste caso, last tambm deve ficar null, pois a fila
ter esvaziado.
public T dequeue()
{
T result = first.value;
first = first.next;
if (first == null)
last = null;
size--;
return result;
}

A funo front apenas retorna o primeiro elemento, sem o


remover
public T front()
{
return first.value;
}
6/1/16 Algoritmos e Estruturas de Dados 7
isEmpty, size, iterator
Os mtodos isEmpty, size e iterator so iguais aos
das pilhas (quando implementadas com listas):
public boolean isEmpty()
{
return first == null;
}

public int size()


{
return size;
}

private class QueueIterator implements Iterator<T>


{
private Node cursor = first;
// ... Note que nas pilhas (com listas) cada
}
novo elemento acrescentado
public Iterator<T> iterator() cabea, enquanto nas filas cada novo
{ elemento acrescentado cabea.
return new QueueIterator();
}

6/1/16 Algoritmos e Estruturas de Dados 8


Funo de teste
private static void testQueueInteger()
{
Queue<Integer> q = new Queue<Integer>();
$ java ... Queue A
while (!StdIn.isEmpty())
{ Queue of Integers
String t = StdIn.readString(); 7 9 3 12 71 5
if (!"-".equals(t)) -
q.enqueue(Integer.parseInt(t)); 7
else if (!q.isEmpty()) -
{ 9
int x = q.dequeue();
StdOut.println(x);
4
} 20
} -
StdOut.println("size of queue = " + q.size()); 3
StdOut.println("is queue empty? " + q.isEmpty()); -
if (!q.isEmpty()) 12
StdOut.println("front of queue = " + q.front()); size of queue = 4
StdOut.print("items:");
for (int x : q)
is queue empty? false
StdOut.print(" " + x); front of queue = 71
StdOut.println(); items: 71 5 4 20
}

6/1/16 Algoritmos e Estruturas de Dados 9


Processamento por ordem de chegada
Quando precisamos de processar elementos por ordem de
chegada enquanto eles vo chegando usamos filas.
O pior se alguns dos elementos so mais importantes que
outros, e tm prioridade no atendimento.
Nestes casos, o prximo elemento a ser atendido o mais
prioritrio de todos os presentes na fila, e no o que chegou
h mais tempo.
Se a fila estiver implementada base de uma lista ligada ou de
um array, teremos de fazer uma busca linear para descobrir o
elemento mais prioritrio no momento de selecionar o
prximo elemento a ser atendido.
Ou ento temos de reorganizar a fila de cada vez que chega
um novo elemento, para a manter ordenada por prioridade
decrescente.

6/1/16 Algoritmos e Estruturas de Dados 10


Filas com prioridade
Nas filas com prioridade, os elementos esto
guardados num array, e organizados de tal
maneira que aceder ao elemento com maior
prioridade uma operao instantnea e de tal
maneira que quando se retira um elemento ou se
insere um novo elemento, a reorganizao do
array muito eficiente.
Por muito eficiente, queremos dizer logartmica.
De cada vez que removemos ou inserimos temos
de reorganizar, para garantir que elemento com
maior prioridade (que pode ter mudado)
continua a ser acedido instantaneamente.
6/1/16 Algoritmos e Estruturas de Dados 11
Classe PriorityQueue<T>
No construtor indicamos o operador de comparao
usado, por meio de um comparador:
public class PriorityQueue<T>
{
PriorityQueue(Comparator<T> c) {...}

public void insert(T x) {...}

public T remove() {...} Apenas programamos a remoo


do elemento mais prioritrio
public T first() {...}

public boolean isEmpty() {...}

public int size() {...}


}

6/1/16 Algoritmos e Estruturas de Dados 12


Funes de teste para filas com prioridade
So semelhantes s usadas com pilhas e filas,
anteriormente:
private static void testPriorityQueueInteger(Comparator<Integer> cmp)
{
PriorityQueue<Integer> pq = new PriorityQueue<Integer>(cmp);
while (!StdIn.isEmpty())
{
String t = StdIn.readString();
if (!"-".equals(t))
pq.insert(Integer.parseInt(t));
else if (!pq.isEmpty())
{
int x = pq.first();
StdOut.println(x);
pq.remove();
}
}
StdOut.println("size of queue = " + pq.size());
StdOut.println("is queue empty? " + pq.isEmpty());
}

A funo de teste para filas de String anloga.


6/1/16 Algoritmos e Estruturas de Dados 13
Funo main
public static void main(String[] args)
{
char choice = 'A';
if (args.length >= 1)
choice = args[0].charAt(0);
if (choice == 'A')
{
StdOut.println("Priority queue of Integers, max");
testPriorityQueueInteger((x, y) -> x-y);
}
else if (choice == 'B')
{
StdOut.println("Priority queue of Integers, min");
testPriorityQueueInteger((x, y) -> -(x-y));
}
else if (choice == 'C')
{
StdOut.println("Priority queue of Strings, max");
testPriorityQueueString(String::compareTo);
}
else if (choice == 'D')
{
StdOut.println("Priority queue of Strings, min");
testPriorityQueueString(Collections.reverseOrder(String::compareTo));
}
else
StdOut.printf("Illegal choice: %s\n", args[0]);
}
6/1/16 Algoritmos e Estruturas de Dados 14
Testando (por antecipao)
Com inteiros, max Com inteiros, min
$ java ... PriorityQueue A $ java ... PriorityQueue B
Priority queue of Integers, max Priority queue of Integers, min
12 56 28 43 91 64 16 27 74 12 98 51 73 17 33 29
- -
91 12
- -
64 17
89 5 21 50
- -
89 21
- -
56 29
- -
43 33
- -
28 50
- -
27 51
- -
16 73
- -
12 74
- -
5 98
- -
- -

6/1/16 Algoritmos e Estruturas de Dados 15


Implementao das filas com prioridade
Certamente no queremos implementaes
ingnuas, que ordenem o array de cada vez
que entra um novo elemento ou que busquem
o elemento mais prioritrio usando um
algoritmo linear.
Essas implementaes teriam um curso
proibitivo.
Usaremos montes (em ingls heaps).
Os montes so arrays com uma arrumao
interna muito bem imaginada!

6/1/16 Algoritmos e Estruturas de Dados 16


Montes
Os montes so arrays com a seguinte propriedade:
a[i] >= a[2*(i+1)-1] Note bem: em geral, em vez do >=
teremos a correspondente operao
expressa em termos do comparador:
a[i] >= a[2*(i+1)] c.compare(a[i], a[2*(i+1)-1)]) >= 0.

Estas frmulas s se aplicam se os ndices forem


vlidos, isto , se o seu valor for menor que o
nmero de elementos do array.
Elas implicam que a[0] o mximo do array.
As filas com prioridade esforar-se-o por manter
a propriedade dos montes eficientemente, quando
entra um novo elemento ou quando sai o primeiro,
a[0].
6/1/16 Algoritmos e Estruturas de Dados 17
Representao visual dos montes
No array Em rvore
24 24

16
16 12
12

9
9 7 5 4
7

5
4 7 2
4

4 A rvore mostra cada filho menor ou igual ao seu


pai.
7
A rvore mostra tambm que num monte com N
2 elementos, h no mximo 2X sequncias decrescentes,
comeando na raiz, onde X = floor(log2 N).
6/1/16 Algoritmos e Estruturas de Dados 18
Implementao das filas com prioridade
Membros de dados, construtor, comparao seletores bsicos:
public class PriorityQueue<T> ...
{
private final Comparator<T> cmp; public boolean isEmpty()
private T[] items; {
private int size; return size == 0;
}
@SuppressWarnings("unchecked")
PriorityQueue(Comparator<T> cmp) public T first()
{ {
this.cmp = cmp; return items[0];
items = (T[]) new Object[1]; }
size = 0;
Usamos arrays redimensionveis,
} como na classe Stack<T>.
public int size()
{
private boolean less(T x, T y) return size;
{ }
return c.compare(x, y) < 0;
} ...
... }

6/1/16 Algoritmos e Estruturas de Dados 19


Subindo o monte
Para inserir um elemento no monte (mantendo a propriedade
dos montes), faremos assim:
Acrescentamos o novo elemento ao array, na primeira
posio livre.
Esse elemento ser o ltimo da sequncia que vem da raiz at
ele, na rvore.
Provavelmente essa sequncia, que estava ordenada antes da
insero, ter deixado de estar.
Nesse caso, inserimos o ltimo elemento na sequncia
ordenada, por trocas sucessivas, com o elemento precedente,
at repor a ordenao, tal como no insertionsort.
A diferena que aqui o elemento precedente o elemento
precedente na sequncia que vem da raiz e no o elemento
precedente no array.

6/1/16 Algoritmos e Estruturas de Dados 20


Pais e filhos
Ateno aritmtica dos pais e dos filhos.
Os filhos do elemento a[i] so a[2*(i+1)-1] e
a[2*(i+1)].
O pai do elemento a[i] a[(i-1)/2].
Podemos exprimir isto com operadores bitwise.
Nesse caso:
Os filhos do elemento a[i] so a[(i+1 >> 1) - 1] e
a[i+1 >> 1].
O pai do elemento a[i] a[i-1 << 1].
Ateno precedncia dos operadores! Os operadores
bitwise >> e << tm precedncia mais baixa que a dos
operadores aritmticos.

6/1/16 Algoritmos e Estruturas de Dados 21


Funo increase
A funo privada increase faz o monte aumentar,
inserindo o ltimo elemento do array (que ter
acabado de entrar) na sua posio ordenada na
sequncia decrescente respetiva:
private void increase(int k)
Pai Filho
{
while (k > 0 && less(items[(k-1)/2], items[k]))
{
exchange(items, k, (k-1)/2);
k = (k-1)/2;
}
} public void exchange(T[] a, int x, int y)
{
T m = a[x];
a[x] = a[y];
a[y] = m;
}

6/1/16 Algoritmos e Estruturas de Dados 22


Funo insert
A funo pblica insert primeiro acrescenta o
novo elemento ao array, redimensionando-o
se necessrio, e depois f-lo subir, com
increase, at a sua justa posio:
public void insert(T x)
{
if (size == items.length)
resize(2 * items.length);
items[size++] = x;
increase(size - 1);
}

6/1/16 Algoritmos e Estruturas de Dados 23


Descendo o monte
Nas nossas filas com prioridade, o nico elemento que sai o
primeiro.
Quando o primeiro sai, o array tem ser reorganizado.
Faremos assim:
Primeiro copiamos o ltimo elemento (a[size-1]) para o
primeiro (a[0]), assim removendo este, mas invalidando a
propriedade dos montes.
Enquanto o elemento que colocmos na raiz for menor que
um dos filhos vamos faz-lo descer no monte, trocando-o
com o maior dos filhos.
Desta forma, ele parar na sua posio ordenada numa das
sequncias decrescentes, e ao mesmo tempo garantimos que
todas as outras se mantm decrescentes.
Note bem: se trocasse com o menor dos filhos (sendo ambos
maiores que o pai, bem entendido) estragaria a sequncia do
6/1/16
outro lado, que deixaria de ser decrescente.
Algoritmos e Estruturas de Dados 24
O maior dos filhos
Trata-se de um caso particular de calcular o ndice do maior
elemento de um subarray, especificado pelo array de base, o
deslocamento e o nmero de elementos:
private int argMax(T[] a, int d, int n)
{
int result = -1;
if (n > 0)
{
T max = a[d];
result = 0;
for (int i = 1; i < n; i++)
if (less(max, a[d+i]))
{
max = a[d+i]; Note bem: a funo argMax devolve o
result = i; ndice relativo ao subarray ou -1 se o
} subarray for vazio. Para obter o elemento
} usando no array, h que adicionar d ao
return result; resultado da funo (se no for -1).
}
6/1/16 Algoritmos e Estruturas de Dados 25
Funo decrease
A funo privada decrease faz o monte diminuir, comeando
na posio indicada no argumento, trocando-o com o maior
dos seus filhos se tiver filhos e for maior que todos eles ou
retorna logo se o elemento na posio indicada no tiver
filhos ou, tendo, for maior do que todos eles. Aps uma troca,
a funo continua, a partir da posio do filho trocado:
private void decrease(int r)
{ Note que o nmero de elementos
int left = 2*r+1; no subarray ser 0, 1 ou 2.
int z = argMax(items, left, Math.min(2, size - left));
if (z != -1 && less(items[r], items[left+z]))
{
exchange(items, r, left+z);
decrease(left+z); Questo tcnica: esta funo recursiva, com recursividade
} terminal. Isto , a ltima coisa que a funo faz chamar-se a
} si prpria. No seria complicado reprogramar iterativamente,
mas em geral no vale a pena, pois o compilador tem
obrigao de fazer isso sozinho, automaticamente.

6/1/16 Algoritmos e Estruturas de Dados 26


Funo remove
A funo pblica remove copia o ltimo elemento no
monte para a primeira posio e depois f-lo descer,
com decrease, at a sua justa posio. No final,
redimensiona o array, se for caso disso:
public T remove()
{
T result = items[0];
items[0] = items[--size];
items[size] = null; Compare com a funo
if (size > 0) pop, da classe Stack<T>.
decrease(0);
if (size > 0 && size == items.length / 4)
resize(items.length / 2);
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 27


private static int weight(int x)

Extra: teoria de comparadores


{
int result = 0;
while (x != 0)
{
public static void testComparatorsExamples(String[] args) result += x % 10;
x /= 10;
{
}
Comparator<Integer> byValue0 = ((x, y) -> x-y);
return result;
}
Comparator<Integer> byValue = Integer::compare;
Comparator<Integer> byWeight = Comparator.comparing(PriorityQueue::weight);
Comparator<Integer> byWeightThenValue = byWeight.thenComparing(byValue); O peso de um
nmero a soma
Comparator<String> byString = String::compareTo;
Comparator<String> byLength = Comparator.comparing(String::length); dos seus algarismos.
Comparator<String> byLengthThenString = byLength.thenComparing(byString);

Comparator<String> byString1 = (x, y) -> x.compareTo(y);


Comparator<String> byLength1 = (x, y) -> x.length() - y.length();
Comparator<String> byLengthThenString1 = byLength1.thenComparing(byString1);

testPriorityQueueInteger(byValue0);
testPriorityQueueInteger((x, y) -> x-y); Esta funo de demonstrao ilustra
testPriorityQueueInteger(byWeight);
testPriorityQueueInteger(byWeightThenValue); a utilizao de comparadores.
testPriorityQueueInteger(Integer::compare);
testPriorityQueueInteger(Comparator.comparing(PriorityQueue::weight));
testPriorityQueueInteger(byWeight.thenComparing(byValue));
testPriorityQueueInteger(Collections.reverseOrder(byWeightThenValue));
testPriorityQueueInteger(Integer::compare);

testPriorityQueueString(String::compareTo);
testPriorityQueueString(byString);
testPriorityQueueString(Collections.reverseOrder(byString));
testPriorityQueueString(byLengthThenString1);
testPriorityQueueString(Collections.reverseOrder(byLengthThenString));
}

6/1/16 Algoritmos e Estruturas de Dados 28


Algoritmos e Estruturas de Dados

Lio n. 8
Anlise de Algoritmos
Anlise de Algoritmos
Questes centrais: tempo e memria.
Exemplo: soma tripla.
Determinao da frequncia absoluta
das instrues.
Aproximao til.
Medio do tempo de clculo.

6/1/16 Algoritmos e Estruturas de Dados 2


Questes centrais
Quanto tempo levar o meu programa?
Quanta memria gastar o meu programa?
Gastar apenas uma forma coloquial
de falar, j que a memria no se gasta

6/1/16 Algoritmos e Estruturas de Dados 3


Exemplo: problema da soma tripla
Dada uma lista de nmeros, sem repeties, quantos triplos
de nmeros dessa lista somam X, para um dado X?
Admitindo que os nmeros residem num array, isso pode
programar-se assim:
public final class ThreeSum
{
public static int count0(int[] a, int x)
{
int result = 0;
final int n = a.length;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (a[i] + a[j] + a[k] == x)
result++;
return result; Bom, mas no bem isto, porque para efeitos do
} problema, queremos contar cada triplo s uma vez e,
por exemplo <4, 6, 8> o mesmo triplo que <6, 8, 4>
// ... ou <8, 6, 4>, por exemplo.
6/1/16 } Algoritmos e Estruturas de Dados 4
Soma tripla: primeiro algoritmo
S contamos um triplo se for o primeiro. Isto , se
encontramos um triplo nas posies de ndices i0, j0,
k0, no queremos cont-lo de novo, quando
passarmos pelos ndices j0, i0, k0, por exemplo:
public static int countBrute(int[] a, int x)
{
int result = 0;
final int n = a.length;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (a[i] + a[j] + a[k] == x)
if (i < j && j < k)
result++;
return result;
} Claro que podemos fazer melhor...

6/1/16 Algoritmos e Estruturas de Dados 5


Soma tripla: segundo algoritmo
Podemos evitar a repetio da contagens, ajustando a
variao dos ndices diretamente, de maneira que o
mesmo triplo no surja mais que uma vez:
public static int countBasic(int[] a, int x)
{
int result = 0;
final int n = a.length;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
for (int k = j + 1; k < n; k++)
if (a[i] + a[j] + a[k] == x)
result++;
return result;
} Esta melhor que a
anterior, mas quo melhor?

6/1/16 Algoritmos e Estruturas de Dados 6


Testando cada um dos algoritmos
J que as duas funes de teste sero idnticas, a
menos da funo chamada, countBrute ou
countBasic, vamos usar uma funo de teste nica,
parametrizada pela funo de contagem.
Para fazer isso, precisamos da interface funcional da
funo de contagem:
@FunctionalInterface
interface ThreeSumCount {
public int count(int [] a, int x);
}

6/1/16 Algoritmos e Estruturas de Dados 7


Funo de teste, geral
A funo de teste geral parametrizada pela
funo de contagem, pela soma alvo; o array
ser lido da consola:
public static void testCount(ThreeSumCount op, int x)
{
int[] a = StdIn.readAllInts();
int z = op.count(a, x);
StdOut.println(z);
}

6/1/16 Algoritmos e Estruturas de Dados 8


Funo main
A funo main ter dois argumentos na linha de
comando: uma letra, que servir para selecionar o teste
pretendido, e um nmero, que representar a soma alvo:
public static void main(String[] args)
{
assert args.length >= 2;
char choice = args[0].charAt(0);
int target = Integer.parseInt(args[1]);
if (choice == 'A')
testCount((a, x) -> ThreeSum.countBrute(a, x), target);
else if (choice == 'B')
testCount((a, x) -> ThreeSum.countBasic(a, x), target);
else
StdOut.println("Illegal option: " + choice);
}
} $ java -cp ../../algs4.jar:. ThreeSum A 20
12 7 5 1 8 3
3
$ java -cp ../../algs4.jar:. ThreeSum B 20
12 7 5 1 8 3
3
6/1/16 Algoritmos e Estruturas de Dados 9
Comparando o tempo de execuo
A funo countBasic faz nitidamente menos
operaes do que a funo countBrute.
Logo, chegar ao resultado mais depressa.
Mas, quo mais depressa?
Por outras palavras, para o mesmo input, qual
a razo entre os tempos de execuo de
uma e outra?
Ou ainda, informalmente: quantas vezes o
countBasic mais rpido (a calcular...) do que o
countBrute?
6/1/16 Algoritmos e Estruturas de Dados 10
Quantas vezes mais rpido?
Quantas vezes mais rpido uma forma de falar, pois o
nmero de vezes pode depender da dimenso dos dados.
Por exemplo, um certo algoritmo pode ser duas vezes mais
rpido que do outro, se houver 1000 nmeros, e quatro vezes
mais rpido, se houver 2000 nmeros.
Para responder pergunta podemos fazer medies e depois,
perante os resultados, propor uma explicao para os valores
observados.
Ou ento, analisar primeiro os programas, colocar a seguir
uma hiptese sobre quantas vezes um mais rpido que o
outro e s ento fazer experincias para verificar a hiptese.
Se, mesmo tentando afincadamente, no conseguirmos
refutar a hiptese, aceit-la-emos como vlida.

6/1/16 Algoritmos e Estruturas de Dados 11


Tempo de execuo
O tempo de execuo de um programa
determinado por dois fatores:
O tempo de execuo de cada instruo.
O nmero de vezes que cada instruo
executada.
Esta observao, aparentemente trivial, foi feita
originalmente por Knuth. Para Knuth, o desafio,
era, bem entendido, determinar o nmero de
vezes que cada instruo executada.

O tempo de execuo de cada instruo depende do computador, da


linguagem e do sistema operativo.

O nmero de vezes que cada instruo executada depende do


programa e do input.
6/1/16 Algoritmos e Estruturas de Dados 12
O ciclo interno
Na prtica, em muitos casos apenas as instrues
que so executadas com maior frequncia tm peso
significativo no tempo de execuo total.
Essas instrues constituem o ciclo interno.
No caso da funo countBrute, o ciclo interno
constitudo pela seguinte instruo:
if (a[i] + a[j] + a[k] == x)
if (i < j && j < k)
result++;
fcil concluir, do contexto, que esta instruo
executada N3 vezes (representando por N o valor da
varivel n, que contm o nmero de elementos do
array).
6/1/16 Algoritmos e Estruturas de Dados 13
O ciclo interno, em countBasic
Na funo countBasic, o ciclo interno contm a instruo.
if (a[i] + a[j] + a[k] == x)
result++;
Quantas vezes executada?
Para cada valor de i e j, a instruo executada para k
valendo j+1, j+2, , n-1. Logo, executada (n-1)-(j+1)+1 = (n-
1)+j vezes.
Para cada valor de i, a instruo executada para j valendo
i+1, i+2, , n-1. Logo, executada, ((n-1)+(i+1))+((n-1)+(i+2))
+ +((n-1)+(n-1)) vezes.
Ora i vale sucessivamente 0, 1, , n-1. Portanto, basta somar
a expresso anterior n vezes, substituindo i por 0, 1, , n-1, o
que dar uma expresso s envolvendo n.
s fazer as contas!

6/1/16 Algoritmos e Estruturas de Dados 14


O ciclo interno, ao contrrio
A seguinte variante equivalente anterior,
mas faz as variveis variar descendentemente
e torna a anlise mais fcil:
public static int countBack(int[] a, int x)
{
int result = 0;
final int n = a.length;
for (int i = n - 1; i >= 0; i--)
for (int j = i - 1; j >= 0; j--)
for (int k = j - 1; k >= 0; k--)
if (a[i] + a[j] + a[k] == x)
result++;
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 15


O ciclo interno, ao contrrio
O ciclo interno o mesmo:
if (a[i] + a[j] + a[k] == x)
result++;
Quantas vezes executada esta instruo?
Para cada valor de i e j, a instruo executada para j vezes.
Para cada valor de i, a instruo executada para j valendo i-1, i-2,
, 0. Logo, executada, (i-1)+(i-2)+ +0 vezes.
Ora esta expresso vale i*(i-1)/2 ou i2/2-i/2 (usando a frmula da
soma da progresso aritmtica.
Ora i vale sucessivamente 0, 1, , n-1. Portanto, basta somar a
expresso anterior n vezes, substituindo i por 0, 1, , n-1.
A segunda parcela, i/2, d n2/4-n/4, claro (usando a mesma frmula
de h pouco).
Para a frmula da soma dos quadrados, podemos consultar o
Wolfram Alpha, por exemplo.
Feitas as contas, no fim d n3/6-n2/2+n/3.

6/1/16 Algoritmos e Estruturas de Dados 16


Aproximao til
A expresso N3/6-N2/2+N/3 complicada...
mais prtico usar uma aproximao, notando que
para valores grandes de N (os que nos interessam) o
valor da primeira parcela muito maior do que os
valores das outras.
Em vez de dizer rigorosamente, mas
complicativamente que a instruo executada N3/6-
N2/2+N/3 vezes, diremos que executada ~N3/6
vezes.
De certa forma, a expresso ~f(N) representa todas as funes g(N) tais
que g(N)/f(N) tende para 1, medida que N aumenta. Para significar que
g(N) uma dessas funes, escrevemos g(N) ~ f(N).

6/1/16 Algoritmos e Estruturas de Dados 17


Comparao countBrute e countBasic
O nmero de vezes que a instruo if
executada em countBrute ~N3.
Em countBasic ~N3/6.
Pela observao de Knuth, o segundo
algoritmo 6 vezes mais rpido do que o
primeiro.
Faamos medies, para verificar essa
hiptese.

6/1/16 Algoritmos e Estruturas de Dados 18


Medio do tempo
Queremos comparar vrias funes de contagem.
Sendo assim, usaremos de novo a interface funcional,
para parametrizar a funo de contagem na funo
geral de medio de tempo.
Essa funo que mede o tempo tem como
argumentos a funo de contagem, o array e o valor
da soma alvo:
public static double timing(ThreeSumCount op, int[] a, int x)
{
Stopwatch timer = new Stopwatch();
op.count(a, x); // result of this computation is not used.
double result = timer.elapsedTime();
return result;
} A classe Stopwatch vem na
6/1/16 biblioteca algs4.jar.
Algoritmos e Estruturas de Dados 19
Resultados da observao
Corremos com ficheiros com 1000, 2000, 3000, 4000 e 5000
nmeros. Em cada ficheiro h nmeros aleatrios entre 0 (inclusive)
e 10 vezes o nmero de nmeros (exclusive), sem repetio:
$ java -cp ../../algs4.jar:../bin ThreeSum C 10000 < t_1000.txt
0.428 0.097
4.41
$ java -cp ../../algs4.jar:../bin ThreeSum C 20000 < t_2000.txt
3.306 0.670
4.93
$ java -cp ../../algs4.jar:../bin ThreeSum C 30000 < t_3000.txt
Este testes correm
11.008 2.217 na diretoria work,
4.97 que est ao lado
$ java -cp ../../algs4.jar:../bin ThreeSum C 40000 < t_4000.txt da diretoria bin.
25.883 5.209
4.97
$ java -cp ../../algs4.jar:../bin ThreeSum C 50000 < t_5000.txt
50.443 10.332
4.88
Constatamos que a razo dos tempos est mais prxima do 5 do
que de 6. Enfim, no mau...

6/1/16 Algoritmos e Estruturas de Dados 20


Funo de teste
A funo de teste tem como argumento a soma alvo e l o
array da consola.
Depois invoca a funo timing para cada um dos algoritmos e
mostra os tempos medidos e a razo entre eles:
public static void testTiming(int target)
{
int[] numbers = StdIn.readAllInts();
double t1 = timing((a, x) -> ThreeSum.countBrute(a, x), numbers, target);
double t2 = timing((a, x) -> ThreeSum.countBasic(a, x), numbers, target);
StdOut.printf("%.3f %.3f\n", t1, t2);
StdOut.printf("%.2f\n", t1 / t2);
}

A funo main ter mais um caso, para este teste:


public static void main(String[] args)
{
...
else if (choice == 'C')
testTiming(Integer.parseInt(args[1]));
...
}
}
6/1/16 Algoritmos e Estruturas de Dados 21
Algoritmos e Estruturas de Dados

Lio n. 9
Ordem de crescimento do tempo de execuo
Ordem de crescimento do tempo de
execuo
Classificao por ordem de crescimento do
tempo de execuo.
Gerao de arrays aleatrios.
Ensaios de razo dobrada.

6/1/16 Algoritmos e Estruturas de Dados 2


Ordem de crescimento
Em anlise de algoritmos, as aproximaes til que nos
interessam so geralmente da forma g(N) ~ a*f(N),
onde f(N) = Nb(log N)c, sendo a, b e c constantes.
Dizemos que f(N) a ordem de crescimento de
g(N).
Nota tcnica: quando usamos logaritmos neste contexto, a base
irrelevante, pois uma mudana de base ser sempre absorvida pela
constante a, por aplicao da frmula da mudana de base:
logbx = logba * logax.
Portanto, em countBrute e em countBasic a ordem
de crescimento da frequncia da instruo if N3.

6/1/16 Algoritmos e Estruturas de Dados 3


Ordens de crescimento habituais
Troca do valor
Descrio Funo de duas variveis
Constante 1 Busca
dicotmica
Logartmica log N
Mximo de um array
Linear N
Lineartmica N log N Quicksort, Mergesort
Quadrtica N2 Selectionsort, Insertionsort
Cbica N3
Soma tripla...
Exponencial 2N
Busca exaustiva: por
exemplo, encontrar todos
os subconjuntos cuja soma
X.

6/1/16 Algoritmos e Estruturas de Dados 4


Ordem de crescimento do tempo
As medies que fizemos (na aula anterior) parecem
indicar que o tempo de execuo cresce
aproximadamente 8 vezes quando o tamanho do
array duplica, para ambos os algoritmos.
CountBrute CountBasic
N T T(i+1)/T(i) N T T(i+1)/T(i)
1000 0.428 1000 0.097
2000 3.306 7.72 2000 0.670 6.91
4000 25.883 7.83 4000 5.209 7.77
Isso um reflexo da ordem de crescimento cbica
da frequncia da instruo if.
Analisemos isso sistematicamente.

6/1/16 Algoritmos e Estruturas de Dados 5


Ensaios de razo dobrada
Primeiro, construmos um gerador de inputs
apropriado ao problema.
Depois experimentamos o programa
repetidamente, de cada vez dobrando o
tamanho dos dados.
Medido o tempo numa experincia, calculamos
a razo para o tempo da experincia anterior.
Em muitos casos, observaremos que a razo
tende para 2b, para uma certa constante b.

6/1/16 Algoritmos e Estruturas de Dados 6


Array de nmeros aleatrios
Para o problema da soma tripla precisamos de arrays
de nmeros arbitrrios, sem duplicados.
Comecemos por gerar arrays de size nmeros
aleatrios, em geral, no intervalo [0..max[.
Em cada caso, indicamos os valores de size e de max:
public class RandomArrays Note bem: neste caso pode haver duplicados.
{
// creates array with n uniformly generated
// random numbers in [0..max[
public static int[] uniform(int n, int max)
{
int[] result = new int[n];
for (int i = 0; i < size; i++)
result[i] = StdRandom.uniform(max);
return result;
} Cada chamada da funo StdRandom.uniform() devolve um nmero aleatrio no
6/1/16 intervalo [0..max[. A classe StdRandom
Algoritmos e Estruturasvem na biblioteca algs4.jar.
de Dados 7
Nmeros aleatrios sem duplicados
Criar um array de nmeros aleatrios mais subtil.
Observe:
// creates array with n uniformly generated
// unique random numbers in [0..max[
public static int[] uniformUnique(int n, int max)
{
assert n <= max; Explicao: inicialmente, todos os
int[] result = new int[n]; nmeros em [0..max[ so candidatos.
int[] candidates = new int[max]; Em cada passo, recolhe-se um
for (int i = 0; i < max; i++) candidato aleatoriamente, colocando
candidates[i] = i; na sua posio o ltimo candidato e
int maxRandom = max; decrementando o nmero de
for (int i = 0; i < n; i++) candidatos.
{
int r = StdRandom.uniform(maxRandom);
result[i] = candidates[r];
candidates[r] = candidates[maxRandom - 1];
maxRandom--;
}
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 8
Testando a criao de nmeros aleatrios
Montemos uma funo para testar, observando, a
criao de arrays de nmeros aleatrios.
Primeiro, uma interface funcional:
@FunctionalInterface
interface SupplierRandomArray {
public int [] supply(int size, int max);
}
Agora, uma funo de teste geral:
public static void testRandomArrays(SupplierRandomArray r, int n, int max)
{
int[] a = r.supply(n, max);
show(a);
}

A funo show mostra private static void show(int a[])


{
o array: for (int x : a)
StdOut.print(" " + x);
StdOut.println();
}
6/1/16 Algoritmos e Estruturas de Dados 9
Invocao da funo de teste
Eis a parte da funo main que se encarrega de testar as duas
funes de criao de arrays de nmeros aleatrios:
public static void main(String[] args)
{
char choice = 'A';
int x = 10;
int y = 20;
if (args.length > 0)
choice = args[0].charAt(0);
if (args.length > 1)
x = Integer.parseInt(args[1]);
if (args.length > 2)
y = Integer.parseInt(args[2]);
if (choice == 'A')
testRandomArrays((s, m) -> uniform(s, m), x, y);
else if (choice == 'B')
testRandomArrays((s, m) -> uniformUnique(s, m), x, y);
else
StdOut.println("Illegal option: " + choice);
6/1/16
} Algoritmos e Estruturas de Dados 10
Experimentando os nmeros aleatrios
Indicamos na linha de comando qual a funo a testar
e os argumentos para a funo: tamanho do array e
valor mximo; se no, usam-se os valores por defeito:
$ java -cp ../../algs4.jar:. RandomArrays
7 0 3 18 8 3 8 12 6 6
$ java -cp ../../ algs4.jar:. RandomArrays A 20 100
43 75 17 74 96 37 22 30 86 20 51 0 96 91 90 23 30 2 75 45
$ java -cp ../../ algs4.jar:. RandomArrays A 20 10 Os azuis, criados na
3 6 3 6 9 2 0 9 5 6 4 7 6 7 8 9 0 2 7 7 opo A, podem ter
$ java -cp ../../ algs4.jar:. RandomArrays B duplicados; os
2 0 8 17 9 5 3 18 10 19 vermelhos, criados
$ java -cp ../../ algs4.jar:. RandomArrays B 20 50 na opo B, no
tm duplicados,
34 42 15 24 17 38 40 16 37 18 19 6 10 1 31 3 22 48 14 0
garantidamente.
$ java -cp ../../ algs4.jar:. RandomArrays B 25 25
8 20 24 1 0 3 12 11 6 13 14 5 10 4 23 18 7 17 15 22 21 9 19 2 16
$ java -cp ../../ algs4.jar:. RandomArrays B 50 1000
630 681 873 488 717 534 744 281 722 590 448 89 656 620 224 99 191
875 834 308 12 275 454 379 97 278 878 260 968 144 228 785 311 790
212 751 504 50 505 736 843 879 940 335 557 276 662 992 766 279
6/1/16 Algoritmos e Estruturas de Dados 11
Medio do tempo, com arrays aleatrios
Queremos medir os tempos, agora com arrays
aleatrios, e no com arrays lidos da consola.
Usamos a seguinte funo de teste, que anloga
que usamos na lio anterior, mas agora usando
arrays aleatrios:
public static void testTimingRandom2(int target)
{
int[] numbers = RandomArrays.uniformUnique(target / 10, target);
double t1 = timing((a, x) -> ThreeSum.countBrute(a, x), numbers, target);
double t2 = timing((a, x) -> ThreeSum.countBasic(a, x), numbers, target);
StdOut.printf("%.3f %.3f\n", t1, t2);
StdOut.printf("%.2f\n", t1 / t2);
}

6/1/16 Algoritmos e Estruturas de Dados 12


Comparando os dois algoritmos
Haver na funo de teste um caso para este teste:
...
int target = Integer.parseInt(args[1]);
...
else if (choice == 'D')
testTimingRandom2(target);
else ...

$ java -cp ../../algs4.jar:. ThreeSum D 10000


0.435 0.098
4.44
$ java -cp ../../algs4.jar:. ThreeSum D 20000
3.330 0.680
4.90
Os resultados so muito
$ java -cp ../../algs4.jar:. ThreeSum D 30000
11.141 2.194 parecidos com os da lio
5.08 anterior, pois os argumentos
$ java -cp ../../algs4.jar:. ThreeSum D 40000 foram escolhidos de forma a
25.851 5.233 que os arrays fossem
4.94 anlogos aos usados ento.
$ java -cp ../../algs4.jar:. ThreeSum D 50000
50.414 10.046
5.02
6/1/16 Algoritmos e Estruturas de Dados 13
Razo dobrada
Para os testes de razo dobrada, queremos usar um
algoritmo e sucessivos arrays, cada vez com o dobro
do tamanho.
No entanto, para maior preciso, se os testes
demorarem menos que um dado limiar, vamos
repeti-los com arrays do mesmo tamanho, at o
tempo acumulado ultrapassar esse limiar, e no fim
tiramos a mdia.
O limiar dado por uma varivel esttica.
private static double tMin = 0.5;

6/1/16 Algoritmos e Estruturas de Dados 14


Funo de razo dobrada
Parametrizamos o algoritmo e, antecipando futuras
necessidades, a funo criao do array, e ainda, o tamanho, o
mximo, a soma-alvo e o nmero de testes:
public static double[] doubleRatio(
ThreeSumCount op, SupplierRandomArray r,
int size, int max, int x, int times)
{
double[] result = new double[times];
for (int i = 0; i < times; i++, size *= 2, max *= 2, x *= 2)
{
double t = 0.0;
int n = 0;
while (t < tMin)
{
t += timing(op, r.supply(size, max), x);
n++;
} O resultado um array de double que
result[i] = t / n; contm os tempos medidos os testes com
} cada tamanho. Para tempos inferiores ao
return result; limiar, o valor ser uma mdia.
}
6/1/16 Algoritmos e Estruturas de Dados 15
Funo printTable
Mostra os nmeros no array dos tempos, linha a
linha, precedidos pelo tamanho do array usado no
ensaio, e seguidos pela razo em relao ao ensaio
anterior (se no for zero), com um formato
minimalista:
private static void printTable(int size, double[] t)
{
double t0 = 0.0;
for (int i = 0; i < t.length; i++, size *= 2)
{
StdOut.printf("%d %.4f", size, t[i]);
if (t0 > 0) // for i == 0 and also in case t[i] == 0
StdOut.printf(" %.2f", t[i] / t0);
StdOut.println();
t0 = t[i];
}
}
6/1/16 Algoritmos e Estruturas de Dados 16
Testando a razo dobrada
Eis a funo de teste e a parte da funo main que interessa:
public static void testDoubleRatio(
ThreeSumCount op, SupplierRandomArray r,
int size, int max, int x, int times)
{
double z[] = doubleRatio(op, r, size, max, x, times);
printTable(size, z);
}

public static void main(String[] args)


{
...
else if (choice == 'G')
testDoubleRatio((a, x) -> ThreeSum.countBrute(a, x),
(a, x) -> RandomArrays.uniformUnique(a, x),
100, 1000, 1500, times);
else if (choice == 'H')
testDoubleRatio((a, x) -> ThreeSum.countBasic(a, x),
(a, x) -> RandomArrays.uniformUnique(a, x),
100, 1000, 1500, times); else
StdOut.println("Illegal option: " + choice);
else
...
}
} A funo printTable vem na pgina seguinte.
6/1/16 Algoritmos e Estruturas de Dados 17
Resultados da razo dobrada
$ java -cp ../../algs4.jar:. ThreeSum G 7
100 0.0004
200 0.0038 8.43
400 0.0282 7.46 Este o countBrute.
800 0.2177 7.73
1600 1.7100 7.86
3200 14.1000 8.25
6400 107.8180 7.65
$ java -cp ../../ algs4.jar:. ThreeSum H 7
100 0.0001
200 0.0007 7.15
400 0.0050 7.60 Este o countBasic. cbico, como o
800 0.0381 7.58 outro, mas mais rpido, como sabemos.
1600 0.2935 7.69
3200 2.3390 7.97
6400 18.0590 7.72
$

6/1/16 Algoritmos e Estruturas de Dados 18


Um algoritmo mais rpido
Vimos que o countBasic 6 vezes mais rpido do que o countBrute; no
entanto, ambos so algoritmos cbicos, isto , em ambos o tempo de execuo
proporcional ao cubo do tamanho do array.
Ser que conseguimos encontrar um algoritmo mais rpido, isto , com ordem
de crescimento subcbica?
Ora bem: dois valores do array a[i] e a[j], com i < j, faro parte de um triplo
que soma x, se existir no array, direita de j, um elemento de valor
x-(a[i]+a[j]).
Se o array estiver ordenado, podemos procurar esse valor usando busca
dicotmica.
Na verdade, de certa forma, os anteriores algoritmos procuram usando busca
linear.
Para decidir se um dado valor existe num array ordenado com N elementos
bastam no mximo floor(log2 N) + 1 iteraes, usando busca dicotmica.
Portanto, descontando a ordenao, o algoritmo deve ser ~aN2 log2 N, para
um certo a.
A ordem de crescimento ser N2 log2 N.
6/1/16 Algoritmos e Estruturas de Dados 19
Custo da ordenao
Os algoritmos de ordenao elementaresinsertionsort,
selectionsort, bubblesortso quadrticos.
Portanto, ordenando com um desses, o tempo de execuo
do novo algoritmo seria ~(aN2 log2 N + bN2).
Ora isto o mesmo que ~aN2 log2 N.
Repare, quando N for muito grande, a segunda parcela
desprezvel face primeira.
Ainda por cima, os algoritmos de ordenao usados pela
biblioteca do Javauma variante do mergesort, para objetos e
o quicksort para tipos primitivosso N log N, que melhor
que N2.
Portanto, esta estratgia promete um algoritmo mais rpido.
Experimentemos.
6/1/16 Algoritmos e Estruturas de Dados 20
Busca dicotmica
A busca dicotmica o algoritmo que procura uma
ocorrncia de um valor dado num array ordenado, dividindo
ao meio o intervalo de busca em cada passo, por comparao
do valor procurado com o valor do elemento central no
intervalo de busca.
Se o valor procurado for menor que o valor do elemento
central, a busca prossegue, agora no meio-intervalo da
esquerda; se for maior, a busca prossegue no meio-intervalo
da direita; se for igual, a busca termina, com xito.
Se o intervalo de busca ficar vazio, a busca termina, sem xito.
A busca dicotmica um algoritmo logartmico: o tempo de
execuo proporcional ao logaritmo do tamanho do array.

6/1/16 Algoritmos e Estruturas de Dados 21


Renque
O renque de um valor relativamente a um
array o nmero de elementos do array cujo
valor menor que esse valor.
Se o array estiver ordenado, o renque pode
ser calculado dicotomicamente.
Sobre este assunto, reveja os transparentes de
Programao Imperativa, em
http://w3.ualg.pt/~pjguerreiro/sites/24_pi_1516/lessons/pi_1516_tudo.pdf.

6/1/16 Algoritmos e Estruturas de Dados 22


Renque em Java
Esta a transcrio para Java da funo usada em
Programao Imperativa, colocada numa nova classe,
BinarySearch: public static int rank(int[] a, int
// a is sorted
x)

{
public class BinarySearch int result = 0;
{ int d = 0;
int n = a.length;
// ... while (n > 0)
{
} int m = n / 2;
if (x <= a[d+m])
n = m;
else
{
result += m+1;
d += m+1;
n -= m+1;
}
}
return result;
6/1/16 Algoritmos }
e Estruturas de Dados 23
Funo da busca dicotmica
A busca dicotmica programa-se nas calmas
recorrendo ao renque.
Em caso de busca falhada, a funo retorna -1, como
de costume; em caso de busca bem sucedida, a
funo retorna o ndice do primeiro elemento do
array cujo valor igual ao valor procurado.
public static int bfind(int[] a, int x)
{
Esta funo pertence classe BinarySearch.
int r = rank(a, x);
return r < a.length && a[r] == x ? r : -1;
}
Note bem: esta funo devolve o ndice do primeiro
elemento com o valor dado, mesmo que haja vrios
elementos iguais a esse. Outras variantes da busca
dicotmica devolvem o ndice de um dos elementos
com o valor dado, no necessariamente o primeiro.
6/1/16 Algoritmos e Estruturas de Dados 24
Curiosidade: busca dicotmica tradicional
Esta a verso tradicional, que no se baseia no renque.
public static int bsearch(int x, int[] a)
{
int result = -1;
int i = 0;
int j = a.length - 1;
while (result == -1 && i <= j)
{
int m = i + (j - i) / 2; Porqu escrever i+(j-i)/2 e no,
if (x < a[m]) mais simplesmente, (i+j)/2?
j = m - 1; Resposta em
else if (x > a[m]) http://googleresearch.blogspot.pt/
i = m + 1; 2006/06/extra-extra-read-all-
else about-it-nearly.html.
result = m;
} Se houver mais do que um elemento
return result; com o valor procurado, esta acerta num
} deles, no necessariamente no primeiro.
6/1/16 Algoritmos e Estruturas de Dados 25
Funo countFaster
Eis o novo algoritmo para o problema da soma
tripla:
public static int countFaster(int[] a, int x)
{
// a is sorted
int result = 0;
final int n = a.length;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
int r = BinarySearch.bfind(a, x - (a[i] + a[j]));
if (r > j)
result++; Com dois ciclos for imbricados e uma busca
} dicotmica l dentro, o tempo de crescimento
return result; de ordem N2logN, certamente.
}

6/1/16 Algoritmos e Estruturas de Dados 26


Razo dobrada para o novo algoritmo
Comprovemos experimentalmente que N2logN.
Mas ateno: precisamos agora de um array ordenado:
// creates sorted array with n uniformly generated
// unique random numbers in [0..max[
public static int[] uniformUniqueSorted(int n, int max)
{
assert n <= max;
int[] result = uniformUnique(n, max);
Arrays.sort(result);
return result;
}

...
else if (choice == 'I')
testDoubleRatio((a, x) -> ThreeSum.countFaster(a, x),
(a, x) -> RandomArrays.uniformUnique(a, x),
100, 1000, 1500, times);
...
6/1/16 Algoritmos e Estruturas de Dados 27
Resultados do ensaio
Eis os resultados do ensaio de razo dobrada com a
funo countFaster:
$ java -cp ../../algs4.jar:. ThreeSum I 7
100 0.0001
200 0.0007 4.68
400 0.0031 4.63 A razo ligeiramente superior a 4 (e
800 0.0138 4.40 muito inferior a 8). Isto confirma a
1600 0.0598 4.32
constatao de que esta funo
3200 0.2520 4.22
6400 1.0510 4.17
~N2logN.
$

Concluso: a utilizao esclarecida de algoritmos que


fazem parte do arsenal de qualquer programador
evoludo permite conseguir impressionantes ganhos
em eficincia.
6/1/16 Algoritmos e Estruturas de Dados 28
Soma dupla
Um problema anlogo ao do soma tripla o da soma dupla:
contar os pares de nmeros num array sem duplicados que
somam um dado valor.
Imaginamos facilmente a soluo bruta, com dois ciclos for
cada um varrendo o array todo, e a soluo bsica, em que o
ciclo interno s percorre o resto do array.
Ambas so quadrticas, mas quo melhor ser a bsica em
relao bruta?
Por outro lado, se o array estiver ordenado, podemos
substituir o ciclo interno por uma busca dicotmica,
melhorando a complexidade para NlogN.
Ser que conseguiremos baixar ainda mais, para aqum de
NlogN?

6/1/16 Algoritmos e Estruturas de Dados 29


Algoritmo linear para a soma dupla
Se o array estiver ordenado (e no tiver duplicados)
e a soma do primeiro elemento (que o menor) e
do ltimo (que o maior) for maior que a a soma
alvo, conclumos que o ltimo no far parte de
nenhum cuja soma seja a soma alvo; se for menor,
conclumos que o primeiro no far parte de
nenhum desses pares; se for igual, ento os dois
nmeros formam um par cuja soma a soma alvo,
contabilizamos esse par e conclumos que esses dois
nmeros no faro parte de mais nenhum par.
Vamos encolhendo o array e repetindo os clculos,
at o array se esgotar.
6/1/16 Algoritmos e Estruturas de Dados 30
Classe TwoSum
Eis a classe para a soma dupla, s com a funo
countSuper, que implementa o algoritmo descrito.
public class TwoSum public static int countSuper(int[] a, int x)
{ {
int result = 0;
// ... int i = 0;
int j = a.length - 1;
} while (i < j)
{
int s = a[i] + a[j];
if (s < x)
i++;
else if (s > x)
j--; Claramente s h um
else ciclo e cada elemento
{ do array s visitado
i++;
j--;
uma vez. Isto implica que
result++; o algoritmo linear.
}
}
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 31
Ensaios de razo dobrada para a soma dupla
Observemos o comportamento linear da soma
dupla:
public static void main(String[] args) Note que vamos buscar a
{ funo de teste classe
if (args.length < 2)
return;
ThreeSum.
char choice = args[0].charAt(0);
int times = Integer.parseInt(args[1]);
...
else if (choice == 'B')
ThreeSum.testDoubleRatio((a, x) -> countSuper(a, x),
(a, x) -> RandomArrays.uniformUniqueSorted(a, x),
1000000, 10000000, 15000000, times);
...
}
$ java -cp ../../algs4.jar:. TwoSum B 6
1000000 0.0040
2000000 0.0081 2.00
4000000 0.0164 2.02
8000000 0.0332 2.03 A razo 2, demonstrando
16000000 0.0650 1.96 comportamento linear.
32000000 0.1335 2.05
$

6/1/16 Algoritmos e Estruturas de Dados 32


Soma tripla quadrtica
Na soma tripla, queremos contar os triplos (a[i], a[j],
a[k]), como i<j<k, tais que a[i]+a[j]+a[k] = x, para um
x dado.
Seja n = a.length.
Ento contemos os pares (a[i], a[j]) para i<j<n-1, que
somam x-a[n-1], usando o algoritmo linear.
Para cada um dos pares contados, o triplo (a[i], a[j],
a[n-1]) soma x, e no h mais nenhum triplo que
some x cujo terceiro elemento seja a[n-1].
Repetimos, para n = a.length-1, a.length-2, ..., 3,
acumulando os pares contados em cada passo.

6/1/16 Algoritmos e Estruturas de Dados 33


Retocando a classe da soma dupla
Observamos que precisamos de calcular a soma dupla, no
para um array todo, mas apenas para a parte inicial de um
array.
Por isso, reorganizamos a classe TwoSum assim:
public class TwoSum
{
public static int countSuper(int[] a, int x)
{
return countSuper(a, a.length, x);
}

public static int countSuper(int[] a, int n, int x)


{
int result = 0;
int i = 0;
int j = n - 1;
while (i < j)
{
...
}
return result;
}

6/1/16 ... Algoritmos e Estruturas de Dados 34


Algoritmo quadrtico para a soma tripla
At fica simples:
public class ThreeSum
{
// ...

public static int countSuper(int[] a, int x)


// a is sorted
{
int result = 0;
for (int n = a.length; n >= 3; n--)
result += TwoSum.countSuper(a, n-1, x - a[n-1]);
return result;
}
Um algoritmo linear em N repetido
// ... N vezes d um algoritmo quadrtico.
}
6/1/16 Algoritmos e Estruturas de Dados 35
Razo dobrada para o algoritmo quadrtico
Observe:
...
else if (choice == 'J')
testDoubleRatio((a, x) -> ThreeSum.countSuper(a, x),
(a, x) -> RandomArrays.uniformUnique(a, x),
100, 1000, 1500, times);
...

Obtemos os resultados j apresentados na


lio anterior:
$ java -cp ../../algs4.jar:. ThreeSum J 7
100 0.0000
200 0.0001 3.86 A razo ser 4 um sinal
400 0.0003 4.01 claro de que se trata de um
800 0.0014 3.93
algoritmo quadrtico.
1600 0.0055 4.04
3200 0.0219 3.96
6400 0.0885 4.04
6/1/16
$ Algoritmos e Estruturas de Dados 36
Moral da histria
No basta ter um programa que calcula o que
queremos.
A questo no otimizar o cdigo; sim
procurar algoritmos melhores.
Os algoritmos brutos ou bsicos so
interessantes como padro de comparao e
permitem validar os algoritmos melhores.
Temos de ser capazes de analisar os tempos
de execuo dos nossos algoritmos,
matematicamente ou experimentalmente.
6/1/16 Algoritmos e Estruturas de Dados 37
Extra: teste de todos
Eis um teste suplementar, s para confirmar que,
depois destas experincias todas, os algoritmos ainda
esto a funcionar...
public static void testAll(int target)
{
int[] numbers = RandomArrays.uniformUnique(target / 10, target);
int n1 = countBrute(numbers, target);
int n2 = countBasic(numbers, target);
Arrays.sort(numbers);
int n3 = countFaster(numbers, target);
int n4 = countSuper(numbers, target);

StdOut.printf("%d %d %d %d\n", n1, n2, n3, n4);


assert n1 == n2; $ java -cp ../../algs4.jar:. ThreeSum K 1000
assert n2 == n3;
83 83 83 83
assert n3 == n4; $ java -cp ../../algs4.jar:. ThreeSum K 10000
} 8429 8429 8429 8429
$ java -cp ../../algs4.jar:. ThreeSum K 10000
7811 7811 7811 7811
$ java -cp ../../algs4.jar:. ThreeSum K 20000
6/1/16 Algoritmos33743 33743
e Estruturas 33743 33743
de Dados 38
Algoritmos e Estruturas de Dados

Lio n. 10
Union-Find
Union-Find
Problema da conectividade dinmica.
Union-Find, em geral.
Quick-Find.
Quick-Union.
Quick-Union-Weighted.

6/1/16 Algoritmos e Estruturas de Dados 2


Conectividade
Temos um conjunto de ns: x1, x2, ... xn.
Alguns desses ns esto ligados, dois a dois.
Um n xi estar conectado a um n xj se
existir uma sequncia de ns <y1, y2, ..., ym> tal
que xi est ligado a y1, y1 est ligado a y2, etc., e
ym est ligado a xj.
Queremos um programa que, perante um par
de ns, xi e xj, nos diga se esses ns esto
conectados.
Mas no s...
6/1/16 Algoritmos e Estruturas de Dados 3
Conectividade dinmica
A relao de estar conectado uma relao de
equivalncia:
Reflexiva Recorde: uma relao de equivalncia
particiona o universo em classes de
Simtrica equivalncia disjuntas.
Transitiva
Portanto, o nosso programa, perante um par de ns,
xi e xj, dir-nos- se os dois ns pertencem mesma
classe de equivalncia.
Mas queremos tambm que o programa recalcule as
classes de equivalncia quando estabelecemos uma
nova ligao entre dois ns que no estavam
conectados. Na nossa gria, uma classe de equivalncia de
ns uma componente.
6/1/16 Algoritmos e Estruturas de Dados 4
Exemplo: reticulado dinmico
Visualizar um reticulado em que cada vez que ligamos dois
ns vizinhos, as conexes aos vizinhos que pertencem
mesma componente so recalculadas e mostradas.

Note bem: o programa desenha uma conexo entre dois ns vizinhos se eles estiverem
na mesma componente. Outra coisa seria desenhar uma conexo entre dois ns da
mesma componente se eles fossem vizinhos, ainda que o efeito visual fosse o mesmo.
6/1/16 Algoritmos e Estruturas de Dados 5
Classe abstrata UnionFind
Precisamos de uma operao para ligar dois ns, union, e
outra para ver se dois ns pertencem mesma componente,
connected.
J agora, tambm uma operao para dar o nmero de
componentes, count, e outra para calcular o identificador da
componente a que um dado n pertence, find.
Reunimos isto numa classe abstrata:
public abstract class UnionFind
{
public abstract void union(int x, int y);
public abstract boolean connected(int x, int y);
public abstract int count();
public abstract int find(int x);
}
O nome consagrado para a classe (e para o algoritmo em
anlise) vem do nome das operaes tpicas: union e find.
6/1/16 Algoritmos e Estruturas de Dados 6
Programando connected
Podemos oferecer j uma implementao da
operao connected, uma vez que, por hiptese, dois
ns estaro conectados se pertencerem mesma
componente:
public abstract class UnionFind
{
public abstract void union(int x, int y);
public abstract int find(int x);
public abstract int count();

public boolean connected(int x, int y)


{
return find(x) == find(y);
}
Algumas classes derivadas podero
} querer redefinir esta funo.

6/1/16 Algoritmos e Estruturas de Dados 7


Classes derivadas
As classes derivadas tero de definir a estrutura de
dados mediante a qual representamos as conexes.
E, em funo disso, tero de definir as funes union,
find e count; e devero redefinir connected, se valer a
pena.
Em todos os casos que nos interessam, os ns so
representados pelos nmeros inteiros do intervalo
[0..N[, sendo N o nmero de ns.
As conexes sero representadas implicitamente por
meio de um array de ns, indexado pelos ns.
A ideia registar apenas uma de entre todas as
conexes de um n, sem perder as outras.

6/1/16 Algoritmos e Estruturas de Dados 8


QuickFind
Na estratgia QuickFind, todos os ns de uma
componente registam a sua conexo ao lder da
componente.
Se um n estiver isolado, ele o seu prprio lder.
Quando ligamos um n x a um n y, todos os ns da
componente do n x passam a ter como lder o lder
da componente a que pertence o n y. Podia ser ao
contrrio, claro.
Claro que a ligao incua se x e y j estiverem
conectados.

6/1/16 Algoritmos e Estruturas de Dados 9


Classe QuickFind
Os membros sero o array dos lderes e o nmero de
componentes:
public class QuickFind extends UnionFind
{
private int[] id;
private int count;
...
}
No construtor, indicamos o nmero de ns e assinalamos que
inicialmente cada n lder de si prprio:
public QuickFind(int n)
Note bem: inicialmente, todos os ns esto
{
isolados; por isso, inicialmente o nmero de
count = n;
componentes igual ao nmero de ns.
id = new int[n];
for (int i = 0; i < n; i++)
id[i] = i;
}
6/1/16 Algoritmos e Estruturas de Dados 10
Funes count, find e connected
So as trs muito simples e eficientes:
public int count()
{
return count;
}

public int find(int x)


As trs operaes so de
{
return id[x]; tempo constante.
}

public boolean connected(int x, int y)


{
return id[x] == id[y];
}
Redefinimos a funo connected por uma questo de zelo
algortmico, visto que neste caso to simples no se
justifica o overhead da dupla chamada da funo find.
6/1/16 Algoritmos e Estruturas de Dados 11
Funo union
Esta a nica que exibe alguma subtileza.
Cada n cujo lder o lder de x, passa a ter como
lder o lder de y:
public void union(int x, int y)
{ O lder de x.
int ix = id[x];
O lder de y.
int iy = id[y];
if (ix != iy) Se tiverem o mesmo
{ lder, no h nada a fazer.
for (int i = 0; i < id.length; i++)
if (id[i] == ix)
id[i] = iy;
count--;
}
} Esta uma operao linear, pois o ciclo for
percorre todo o array, inexoravelmente.
6/1/16 Algoritmos e Estruturas de Dados 12
Funo de teste
Eis uma funo de teste que aceita pares de ns
sucessivamente e mostra o estado do sistema a cada passo:
public static void testQuickFind(String[] args)
{
int size = 10;
if (args.length != 0)
size = Integer.parseInt(args[0]);
QuickFind qf = new QuickFind(size);
qf.show(-1, -1); // just to show the initial state
while (!StdIn.isEmpty()) private void show(int x, int y)
{ {
int x = StdIn.readInt(); StdOut.printf("%3d%3d ", x, y);
for (int z : id)
int y = StdIn.readInt(); StdOut.printf("%3d", z);
qf.union(x, y); StdOut.println(" / " + count);
qf.show(x, y); }
} public static void main(String[] args)
} {
testQuickFind(args);
}
6/1/16 Algoritmos e Estruturas de Dados 13
Experimentando
Na consola:
$ java -cp ../../stdlib.jar:. QuickFind
-1 -1 0 1 2 3 4 5 6 7 8 9 / 10
5 7
5 7 0 1 2 3 4 7 6 7 8 9 / 9
3 2
3 2 0 1 2 2 4 7 6 7 8 9 / 8
9 2
9 2 0 1 2 2 4 7 6 7 8 2 / 7
9 5
9 5 0 1 7 7 4 7 6 7 8 7 / 6
3 5
3 5 0 1 7 7 4 7 6 7 8 7 / 6
1 0
1 0 0 0 7 7 4 7 6 7 8 7 / 5
1 4
1 4 4 4 7 7 4 7 6 7 8 7 / 4
0 2
0 2 7 7 7 7 7 7 6 7 8 7 / 3 Confira, com ateno.
8 6
8 6 7 7 7 7 7 7 6 7 6 7 / 2
2 8
2 8 6 6 6 6 6 6 6 6 6 6 / 1

6/1/16 Algoritmos e Estruturas de Dados 14


Outra visualizao
Talvez ajude, observar as componentes na forma de rvores,
em que a raiz de cada rvore o lder da componente:
2 liga a 5

7 liga a 1
8 liga a 2

2 liga a 7

6/1/16 Algoritmos e Estruturas de Dados 15


(Continuao)
4 liga a 9

0 liga a 4

6 liga a 3

6 liga a 2

2 liga a 0

6/1/16 Algoritmos e Estruturas de Dados 16


Filme de uma experincia
Observe:

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/23_aed_1415/movies/quick_find.m4v.

6/1/16 Algoritmos e Estruturas de Dados 17


Anlise
Cada chamada de union faz no mnimo N+3 acessos ao
array id e no mximo 2*N+1 acessos, de cada vez que
efetivamente combina duas componentes.
Em cada chamada de union, o nmero de componentes
diminui de uma unidade.
Logo, se o processo continuar at haver s uma
componente, teremos quando muito Repare:
(2*N+1)+2*(N-1)+1+...+2*2+1 =
(N+3)*(N-1) ~N2 acessos ao array. (((2*N+1)+5)/2)*(N-1) =
(N+3)*(N-1).
Conclumos que o QuickFind um algoritmo quadrtico.
Considere um computador que realiza mil milhes de operaes por segundo (1
gigahertz). Se um acesso ao array contar como uma operao e tivermos um milho de
ns, podemos estimar que o algoritmo demorar 1000 segundos. Por outro lado, se
usssemos um algoritmo lineartmico, com um milho de ns haveria cerca de 30 milhes
de operaes, o que quer dizer que o programa demoraria 0.03 segundos.

6/1/16 Algoritmos e Estruturas de Dados 18


Algoritmos e Estruturas de Dados

Lio n. 11
QuickUnion
Union-Find
QuickUnion.
QuickUnion Weighted.

6/1/16 Algoritmos e Estruturas de Dados 2


QuickUnion
Na estratgia QuickUnion, nem todos os no lderes
tero uma ligao direta ao lder da componente.
Agora, quando ligamos um n x a um n y, apenas o
lder da componente a que o n x pertence regista a
sua conexo ao lder da componente a que pertence
o n y. Podia ser ao contrrio, claro.

Note bem: s muda um n: o lder da componente a


que o n x pertence.
(No QuickFind, mudavam todos os ns da
componente a que x pertence.)
A ligao incua se x e y j estiverem conectados.
6/1/16 Algoritmos e Estruturas de Dados 3
Visualizando
O funcionamento do QuickUnion o que os seguintes
esquemas ilustram:

Liga do amarelo
para o verde.

Ler de cima para


baixo, da esquerda
para a direita.

6/1/16 Algoritmos e Estruturas de Dados 4


(Continuao)

Liga do amarelo
para o verde.

Isto fica muito diferente do


6/1/16
QuickFind. Qual ser melhor?
Algoritmos e Estruturas de Dados 5
Classe QuickUnion
Os membros sero o array dos lderes e o nmero de
componentes, como no QuickFind:
public class QuickUnion extends UnionFind
{
private int[] id;
private int count;
...
}
No construtor, indicamos o nmero de ns e assinalamos que
inicialmente cada n lder de si prprio, como no QuickFind:
public QuickUnion(int n)
{
count = n;
id = new int[n];
for (int i = 0; i < n; i++)
id[i] = i;
}
6/1/16 Algoritmos e Estruturas de Dados 6
Funes count e union
O mtodo count muito simples:
public int count()
{
return count;
}

O mtodo union tambm:


public void union(int x, int y)
{
int ix = find(x); Parece simples, mas falta ver
int iy = find(y); find.
if (ix != iy)
{
id[ix] = iy; Nem preciso programar, o
count--; mtodo connected porque
} fica acessvel por herana.
6/1/16 } Algoritmos e Estruturas de Dados 7
Funo find
preciso subir sucessivamente at chegar ao lder:
public int find(int x)
{
while (x != id[x])
x = id[x];
return x;
}

At parece simples, mas ateno ao ciclo while!

6/1/16 Algoritmos e Estruturas de Dados 8


Funo de teste
A funo de teste anloga do QuickFind:
public static void testQuickUnion(String[] args)
{
int size = 10;
if (args.length != 0)
size = Integer.parseInt(args[0]);
QuickUnion qu = new QuickUnion(size);
qu.show(-1, -1);
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
int y = StdIn.readInt();
if (!qu.connected(x, y))
qu.union(x, y);
qu.show(x, y);
}
}

6/1/16 Algoritmos e Estruturas de Dados 9


Experimentando
Na consola:
pedros-imac-6:bin pedro$ java -cp ../stdlib.jar:../core.jar:.
QuickUnion
-1 -1 0 1 2 3 4 5 6 7 8 9 / 10
2 4 Confira, com ateno.
2 4
7 6
0 1 4 3 4 5 6 7 8 9 / 9
a mesma sequncia
7 6 0 1 4 3 4 5 6 6 8 9 / 8 de ligaes do
9 4
9 4 0 1 4 3 4 5 6 6 8 4 / 7 exemplo com as
1 9
1 9 0 4 4 3 4 5 6 6 8 4 / 6 rvores.
2 6
2 6 0 4 4 3 6 5 6 6 8 4 / 5
0 8
0 8 8 4 4 3 6 5 6 6 8 4 / 4
8 3
8 3 8 4 4 3 6 5 6 6 3 4 / 3
0 5
0 5 8 4 4 5 6 5 6 6 3 4 / 2
8 4
8 4 8 4 4 5 6 6 6 6 3 4 / 1

6/1/16 Algoritmos e Estruturas de Dados 10


Anlise
Deve ser mais rpido do que o QuickFind, pois no precisa de
percorrer o array todo no mtodo union.
S num caso que o union visita todos os ns (por
intermdio do find): quando h s duas componentes com
todos os ns em filinha e mandamos unir os ns de baixo:

Aqui, o find(3) Depois a unio


sobe at ao 8 e o direta: o 8 (resultado
find(0) sobre at do find(3)) liga para o
ao 9. 9 (resultado do
find(0)).

6/1/16 Algoritmos e Estruturas de Dados 11


Anlise do pior caso
O pior caso seria um em que s h uma filinha, ao longo dos
clculos, obtida, por exemplo, ligando um dos ns a cada um
dos outros, por exemplo (5,0), (5,3), (5,9), (5,2), etc.
Neste caso o comportamento
quadrtico.
Repare: na primeira vez, o find(5)
Esta sequncia de visitou apenas o n 5; da segunda o
ligaes at nem muito n 5 e o n 0; da terceira o 5, o 0 e
esquisita, pois no? o 3; etc.
De cada vez, h ainda o find do
outro n, que s visita esse n.
Portanto, na i-sima ligao so
visitados i+1 ns. Ora, havendo N
ns, i varia de 1 a N-1.
Ao todo o nmero de ns visitados
2+3+4+...+N ~ N2/2.
6/1/16 Algoritmos e Estruturas de Dados 12
Altura da rvore
O caso anterior fabricado, mas plausvel.
Mostra que no convm que as rvores fiquem muito altas.
Podemos esperar que se as ligaes forem feitas de forma
aleatria, as alturas se distribuam melhor
Eis dois exemplos com 100 ns, ligados aleatoriamente:

Mesmo assim, a altura final


mais de 20 e tal, nos dois
casos.Veremos a seguir como
fazer muito melhor.
6/1/16 Algoritmos e Estruturas de Dados 13
Altura da rvore, antes do fim
Para referncia eis dois exemplos da situao de 100 ns
quando o nmero de componentes chegou a 12:

Tipicamente, perto do fim h


uma rvores muito grande e
o resto so rvores muito
pequenas, muitas delas com
um s n.

6/1/16 Algoritmos e Estruturas de Dados 14


Ligando ao contrrio
Em rigor, indiferente ligar x com y ou y com x.
Portanto, o melhor escolher a ligao que faa a altura da rvore
crescer menos.
Por exemplo, no exemplo da pgina 4, teria sido prefervel ligar o 6
ao 2, em vez de ligar o 2 ao 6, na fase em que havia seis
componentes: Liga do amarelo
para o verde.

Aqui o 6 liga ao 2.
Aqui o 2 liga ao 6.

Aqui a altura mdia


Aqui a altura mdia (0+1+1+1+1+2)/6 = 1.0
(0+1+1+2+2+2)/6 = 1.333
6/1/16 Algoritmos e Estruturas de Dados 15
Weighted QuickUnion
A estratgia Weighted QuickUnion como a
QuickUnion, mas em cada ligao, o lder da
componente menor regista a sua ligao ao
lder da componente maior.
Maior ou menor diz respeito ao nmero de
elementos (e no altura).
Portanto, convm controlar o tamanho de
cada componente, ao longo do processo.

6/1/16 Algoritmos e Estruturas de Dados 16


Classe QuickUnionWeighted
Alm do array dos lderes e do nmero de componentes, existe
o array dos tamanhos:
public class QuickUnionWeighted extends UnionFind
{
private int[] id;
private int count;
private int[] size;
...
}

O construtor tem de inicializar tambm o array dos tamanhos:


public QuickUnionWeighted(int n)
{
count = n;
id = new int[n];
for (int i = 0; i < n; i++)
id[i] = i;
size = new int[n];
for (int i = 0; i < n; i++)
size[i] = 1;
}
6/1/16 Algoritmos e Estruturas de Dados 17
Funo union, na classe pesada
mais comprida, mas simples:
public void union(int x, int y)
{
int ix = find(x);
int iy = find(y);
if (ix != iy)
{
if (size[ix] < size[iy])
{
id[ix] = iy;
size[iy] += size[ix]; Aqui liga do x para o y.
}
else
{
id[iy] = ix;
Aqui liga do y para o x.
size[ix] += size[iy];
}
count--;
}
}

6/1/16 Algoritmos e Estruturas de Dados 18


As outras funes
As funes count, find e connected so como em
QuickUnion.
Acrescentamos uma funo para a tamanho:
// the size of the component to which x belongs.
public int size(int x)
{
return size[find(x)];
}

6/1/16 Algoritmos e Estruturas de Dados 19


rvores do QuickUnion Weighted
Eis dois exemplos aleatrios, no final:

E mais dois, com 12 componentes:

Estas rvores so nitidamente melhores que as do


QuickUnion.
6/1/16 Algoritmos e Estruturas de Dados 20
Anlise do caso mais desfavorvel
Se houver 2 ns, a configurao final s pode ser uma:

Se houver 3 ns, a configurao final s pode ser a seguinte:

Se houver 4 ns, s h duas hipteses:

6/1/16 Algoritmos e Estruturas de Dados 21


4+4
Se juntarmos uma rvore de 4 ns com uma rvore de 1 n,
dar uma rvore com a mesma altura que a de quatro ns.
Se juntarmos uma rvore de 4 ns com uma de 2 ns ou com
uma de 3 ns, a altura no ficar maior que 2, certamente.
S se juntarmos duas rvores de 4 ns das desequilibradas
que a altura aumenta, para 3: Note que todas as rvore com 2 ns tm
altura 1 e que todas as rvores com 3 ns
no QuickUnionWeighted tero tambm
altura 1.

6/1/16 Algoritmos e Estruturas de Dados 22


Comportamento logartmico do find
Generalizando a observao anterior, conclumos
que a altura de uma rvore com 2N ns ser sempre
menor ou igual a N.
Ou, inversamente, que a altura de uma rvore com N
ns menor ou igual a floor(log2 N).
Por conseguinte, o find no QuickUnion Weighted
logartmico.
Havendo N ns, o nmero de operaes necessrias
conectar os ns todos proporcional a N log N
(excluindo o tempo perdido com ligaes de ns j
ligados...)

6/1/16 Algoritmos e Estruturas de Dados 23


Moral da histria
notvel que uma ideia simples mais engenhosa
permita melhorar to drasticamente o desempenho
do algoritmo.
Na prtica, usaremos o QuickUnion Weighted e no
se fala mais nisso. Os outros tm interesse terico apenas.

Existe uma variante chamada QuickUnion pesado


com compresso de caminho, a qual ainda mais
eficiente.
Consiste em acrescentar um segundo ciclo no find,
ligando cada n diretamente ao lder.
Assim teremos rvores planas (como no
QuickUnion), ou quase planas, quase sempre.

6/1/16 Algoritmos e Estruturas de Dados 24


Algoritmos e Estruturas de Dados

Lio n. 12
Ordenao de arrays
Ordenao de arrays
Preliminares.
Classes para os algoritmos de ordenao.
Animao algortmica.
Algoritmos quadrticos:
Bubblesort.
Selectionsort.
Inserstionsort.

6/1/16 Algoritmos e Estruturas de Dados 2


Ordenao de arrays
Ordenar um array reorganiz-lo de maneira a que os
elementos sejam colocados numa sequncia que nos
convenha.
Convm-nos ordenar para depois encontrar mais
depressa.
Convm-nos ordenar para que elementos parecidos
fiquem prximos uns dos outros.
Convm-nos ordenar para os melhores aparecerem
primeiro.
Na prtica, para ordenar, usamos funes de biblioteca.
Mesmo assim, conhecer os principais algoritmos de
ordenao faz parte da bagagem tcnica dos
programadores. Provrbio de programao: se no sabes o que fazer, ordena.
6/1/16 Algoritmos e Estruturas de Dados 3
Regras
Ordenaremos arrays com elementos de um tipo T,
qualquer, desde que T seja comparvel, isto , desde
que implemente a interface Comparable<T>.
Nos algoritmos, usaremos uma funo less para
comparar dois elementos do array. Esta funo
implementada em termos do mtodo compareTo, da
interface Comparable<T>.
Em muitos algoritmos, a ordenao consegue-se por
uma sucesso de trocas de pares de elementos no
array. Essa troca operada por meio de uma funo
exchange.

6/1/16 Algoritmos e Estruturas de Dados 4


Classe abstrata Sort
As classes que implementam os diversos algoritmos que
vamos estudar, estendem a classe Sort, a qual rene os
recursos comuns e deixa abstrato o mtodo sort , o qual ser
depois implementado em cada uma das classes derivadas:
public abstract class Sort<T extends Comparable<T>>
{
public abstract void sort(T[] a);

public boolean less(T x, T y)


{
return x.compareTo(y) < 0;
}

public void exchange(T[] a, int x, int y)


{
T m = a[x];
a[x] = a[y];
a[y] = m;
}
...
6/1/16 Algoritmos e Estruturas de Dados 5
Funes isSorted
Acrescentamos uma funo para verificar se
um array se est ordenado:
public boolean isSorted(T[] a)
{
for (int i = 1; i < a.length; i++)
if (less(a[i], a[i-1]))
return false;
return true;
}
Tipicamente usa-se a posteriori, para
asseverar que a ordenao funcionou.

6/1/16 Algoritmos e Estruturas de Dados 6


Funes de teste
Equipamos a classe Sort com duas funes de teste,
para arrays de Integer e para arrays de String:
public static void testIntegers(Sort<Integer> s)
{
Integer[] a = integersFromInts(StdIn.readAllInts());
s.sort(a);
StdOut.println(stringOf(a, ,"));
}

public static void testStrings(Sort<String> s)


{
String[] a = StdIn.readAllStrings();
s.sort(a); As funes integersFromInts e
StdOut.println(stringOf(a, ;")); stringOf vm na pgina seguinte.
}

Note bem: a nossa construo no serve para ordenar


arrays de int, int[], pois o tipo int no uma classe.
6/1/16 Algoritmos e Estruturas de Dados 7
Funes de apoio
A funo integersFromInts transforma um array de
int em um array de Integer:
public static Integer[] integersFromInts(int[] a)
{
Integer[] result = new Integer[a.length];
for (int i = 0; i < a.length; i++)
result[i] = a[i];
return result;
}

A funo intsFromIntegers faz o contrrio:


public static int[] intsFromIntegers(Integer[] a)
{
int[] result = new int[a.length];
for (int i = 0; i < a.length; i++)
result[i] = a[i];
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 8


Funes de apoio, mais uma
A funo stringOf constri uma representao
simples do array, para inspeo rpida:
public static <T> String stringOf(T[] a, String separator)
{
String result = "";
int n = a.length;
if (n > 0)
{
result += a[0].toString();
for (int i = 1; i < n; i++)
result += separator + a[i];
}
return result;
} J conhecemos este esquema
h muito tempo...

6/1/16 Algoritmos e Estruturas de Dados 9


Classe Bubblesort
Experimentemos, com uma classe para o
bubblesort:
public class Bubblesort<T extends Comparable<T>>
extends Sort<T>
{
public void sort(T[] a)
{
int n = a.length;
for (int i = 1; i < n; i++)
for (int j = n-1; j >= i; j--)
if (less(a[j], a[j-1]))
exchange(a, j-1, j);
}
Haver uma classe destas
... para cada algoritmo.
6/1/16 Algoritmos e Estruturas de Dados 10
Testando o bubblesort
Podemos testar usando as funes de teste herdadas:
public static void main(String[] args)
{
char choice = 'A';
if (args.length >= 1)
choice = args[0].charAt(0);
if (choice == 'A')
testIntegers(new Bubblesort<Integer>());
else if (choice == 'B')
testStrings(new Bubblesort<String>());
else
StdOut.printf("Illegal choice: %s\n", args[0]);
}
$ java -ea -cp ../../*:. Bubblesort A
64 12 89 5 52 81 62 10 99 18 12 55
5,10,12,12,18,52,55,62,64,81,89,99
$ java -ea -cp ../../*:. Bubblesort B
uuu rrr yyy qqq aaa yyy rrr iii ooo qqq aaa qqq ooo
aaa;aaa;iii;ooo;ooo;qqq;qqq;qqq;rrr;rrr;uuu;yyy;yyy
$
6/1/16 Algoritmos e Estruturas de Dados 11
Bubblesort
Percorrer sucessivamente o array da direita
para a esquerda trocando os elementos
adjacentes que estejam fora de ordem.
um algoritmo bom para programar.
No difcil de entender.
muito instrutivo.
De todos os algoritmos elementares de
ordenao o menos eficiente.

6/1/16 Algoritmos e Estruturas de Dados 12


Classe Bubblesort
J sabemos:
public class Bubblesort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
int n = a.length;
for (int i = 1; i < n; i++)
for (int j = n-1; j >= i; j--)
if (less(a[j], a[j-1]))
exchange(a, j-1, j);
}

...

Nitidamente, o nmero de comparaes (n-1) + (n-2) + ... +


1 ~ n2 / 2.
O nmero de trocas depende dos dados. Se o array estiver
ordenado, no haver trocas. Se estiver por ordem inversa,
haver ~n2/2 trocas. Por um argumento de simetria, em mdia
haver ~n2/4 trocas.
6/1/16 Algoritmos e Estruturas de Dados 13
Ensaios de razo dobrada
Desenhemos uma classe SortDoubleRatio para
ensaios de razo dobrada com algoritmos de
ordenao:
Seguimos o modelo usado no problema da soma
tripla, com adaptaes:
public class SortDoubleRatio
{
private static double tMin = 0.5;
// minimum duration of a test in the double ratio experiment

public static double timing(Sort<Integer> s, Integer[] a)


{
Stopwatch timer = new Stopwatch();
s.sort(a);
double result = timer.elapsedTime();
assert s.isSorted(a); // just in case something went wrong...
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 14
Classe SortDoubleRatio, continuao
public static double[] doubleRatio(Sort<Integer> s, int size, int max, int times)
{
double[] result = new double[times];
for (int i = 0; i < times; i++, size *= 2, max *= 2)
{
double t = 0.0;
int n = 0;
while (t < tMin)
{
Integer[] a = Sort.IntegersFromInts(RandomArrays.uniform(size, max));
t += timing(s, a);
n++;
}
result[i] = t / n;
}
return result;
}

private static void printTable(int size, double[] t)


{
double t0 = 0.0;
for (int i = 0; i < t.length; i++, size *= 2)
{
StdOut.printf("%d %.4f", size, t[i]);
if (t0 > 0.0) // for i == 0 and also in case t[i] == 0
StdOut.printf(" %.2f", t[i] / t0);
StdOut.println();
t0 = t[i];
}
}

public static void test(Sort<Integer> s, int size, int max, int times)
{
double z[] = doubleRatio(s, size, max, times);
printTable(size, z);
}
6/1/16 Algoritmos e Estruturas de Dados 15
}
Razo dobrada, Bubblesort
Acrescentamos um teste na funo main:
public static void main(String[] args)
{
...
else if (choice == 'C')
testDoubleRatio(new Bubblesort<Integer>(), 1000, 10000, 7)
...
}

$ java -cp ../../*:. Bubblesort C


1000 0.0024
2000 0.0086 3.64 Observamos comportamento
4000 0.0358 4.16 quadrtico, tal como espervamos.
8000 0.1697 4.74
16000 0.8360 4.93
32000 3.9060 4.67 Regra prtica: com o bubblesort ordenamos
64000 16.6370 4.26 cerca de 20000 Integers num segundo.
$

6/1/16 Algoritmos e Estruturas de Dados 16


Comparao com arrays de ints
Qual ser a penalizao em tempo por usarmos arrays de
Integer em vez de arrays de int?
Experimentemos, com uma classe ad hoc:
public final class BubblesortInts Note bem: a classe
{ SortDoubleRatio no aplicvel
public static void sort(int[] a)
aqui, porque a classe
{
int n = a.length; BubblesortInts no deriva de
for (int i = 1; i < n; i++) Sort<T>. preciso repetir tudo!
for (int j = n-1; j >= i; j--)
$ java ... BubblesortInts C
if (a[j] < a[j-1])
1000 0.0014
{
2000 0.0053 3.87
int m = a[j-1];
4000 0.0220 4.16
a[j-1] = a[j];
8000 0.0928 4.22
a[j] = m;
16000 0.3855 4.15
}
32000 1.6080 4.17
}
64000 6.5060 4.05
...

Conclumos que os tempos com este bubblesort com ints esto consistentemente
abaixo de metade dos tempos do anterior bubblesort com Integers.

6/1/16 Algoritmos e Estruturas de Dados 17


Overhead de memria
Observmos que ordenar arrays de Integer leva mais tempo
que ordenar arrays de int.
E tambm gasta mais memria.
De facto, em Java, um int ocupa 4 bytes; um Integer ocupa 24
bytes.
Em geral, num objeto, h a memria dos membros (instance
variables), mais 16 bytes de overhead (referncia para a classe,
informao para a garbage collection e informao para
sincronizao); alm disso, a memria arredondada para
cima, para um mltiplo de 8 bytes.
Num Integer, h um membro, de tipo int, ocupando 4 bytes;
com o overhead chegamos a 20 bytes e com o
arredondamento chegamos a 24.

6/1/16 Algoritmos e Estruturas de Dados 18


Filme do bubblesort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/bubblesort.m4v

6/1/16 Algoritmos e Estruturas de Dados 19


Selection sort
Percorrer o array da esquerda para a direita
trocando cada elemento com o menor
elemento existente da para a direita.
talvez o algoritmo mais intuitivo.
Baseia-se no algoritmo do mnimo de um
array.

6/1/16 Algoritmos e Estruturas de Dados 20


Classe Selectionsort
A funo sort exprime o algoritmo:
public class Selectionsort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
int n = a.length;
for (int i = 0; i < n; i++)
{
int m = i; No final do ciclo for interno, a varivel
for (int j = i + 1; j < n; j++) m contm o ndice do elemento
if (less(a[j], a[m])) mnimo no subarray correspondente
m = j;
ao intervalo de ndices [i..n[.
exchange(a, i, m);
}
}
...
Nitidamente, o nmero de comparaes (n-1) + (n-2) + ... +
1 ~ n2 / 2.
Haver n trocas, mesmo se o array estiver ordenado.
Logo, o tempo de execuo no depende dos valores no
array.
6/1/16 Algoritmos e Estruturas de Dados 21
Filme do selectionsort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/selectionsort.m4v

6/1/16 Algoritmos e Estruturas de Dados 22


Razo dobrada, Selectionsort
No meu computador:
$ java -cp ../../*:. Selectionsort C
1000 0.0011
2000 0.0042 3.75
4000 0.0168 3.96
8000 0.0663 3.95 Observamos que mais
16000 0.2625 3.96 de trs vezes mais rpido
32000 1.1050 4.21 do que o bubblesort,
64000 4.6990 4.25 com arrays aleatrios.
$
Comportamento quadrtico.

$ java -cp ../../*:. Bubblesort C


1000 0.0024
2000 0.0086 3.64
4000 0.0358 4.16
8000 0.1697 4.74
16000 0.8360 4.93
32000 3.9060 4.67
64000 16.6370 4.26
6/1/16 Algoritmos e Estruturas
$ de Dados 23
Insertion sort
Percorrer o array da esquerda para a direita
inserindo ordenadamente cada elemento no subarray
sua esquerda, o qual est ordenado.
D jeito para ordenar uma mo de cartas de jogar.
No nosso caso, a insero ordenada faz-se por
trocas sucessivas de dois elementos adjacentes, da
direita para a esquerda, enquanto os dois elementos
adjacentes estiverem fora de ordem.
Quer dizer, as trocas terminam ou quando os dois
elementos adjacentes estiverem por ordem ou
quando o percurso tiver chegado ao incio do array.

6/1/16 Algoritmos e Estruturas de Dados 24


Classe Insertionsort
A funo sort exprime o algoritmo:
public class Insertionsort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
int n = a.length;
for (int i = 1; i < n; i++)
for (int j = i; j >= 1 && less(a[j], a[j - 1]); j--)
exchange(a, j, j - 1);
}
...

No pior caso (com o array por ordem inversa) o nmero de


comparaes 1 + 2 + ... + (n-1) ~ n2 / 2; no melhor caso
(com o array ordenado) h n-1 comparaes.
Com arrays aleatrios, em mdia a condio less no ciclo for
anterior interrompe o ciclo a meio, entre i e 0.
Logo, com arrays aleatrios, em mdia o nmero de
comparaes ~n2/4.
6/1/16 Algoritmos e Estruturas de Dados 25
Filme do insertionsort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/insertionsort.m4v

6/1/16 Algoritmos e Estruturas de Dados 26


Ensaio de razo dobrada, insertionsort
No meu computador:
$ java -cp ../../*:. Insertionsort C
1000 0.0014
2000 0.0053 3.88
4000 0.0215 4.03
8000 0.0878 4.09
16000 0.3530 4.02 Observamos os tempos so
32000 1.5850 4.49 comparveis aos do Selectionsort,
64000 6.8030 4.29 ainda que ligeiramente piores.
$
Comportamento quadrtico,
tal como o bubblesort e o
$ java -cp ../../*:. Selectionsort C
selectionsort. 1000 0.0011
2000 0.0042 3.75
4000 0.0168 3.96
8000 0.0663 3.95
16000 0.2625 3.96
32000 1.1050 4.21
64000 4.6990 4.25
6/1/16 Algoritmos e $
Estruturas de Dados 27
Arrays parcialmente ordenados
Os arrays parcialmente ordenados do pouco
trabalho a ordenar com o insertionsort.
Exemplos de arrays parcialmente ordenados:
Um array em que cada elemento est perto da sua
posio final.
Um array com apenas poucos elementos fora de
ordem.
Um array formado por um pequeno array
concatenado no fim a um array grande que j est
ordenado.
Estas situaes so relativamente frequentes, na
prtica.
6/1/16 Algoritmos e Estruturas de Dados 28
Filme do insertionsort, especial1
Neste caso, cada elemento do array est perto da sua posio
final:

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/insertionsort_special1.m4v


6/1/16 Algoritmos e Estruturas de Dados 29
Filme do insertionsort, especial2
Neste caso, alguns (poucos) elementos esto fora de ordem:

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/insertionsort_special2.m4v


6/1/16 Algoritmos e Estruturas de Dados 30
Filme do insertionsort, especial3
Neste caso, foram acrescentados alguns (poucos) elementos a
um array
ordenado:

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/insertionsort_special3.m4v


6/1/16 Algoritmos e Estruturas de Dados 31
Algoritmos e Estruturas de Dados

Lio n. 13
Ordenaes subquadrticas
Ordenaes subquadrticas
Shellsort.
Mergesort.
Quicksort.

6/1/16 Algoritmos e Estruturas de Dados 2


Shellsort
Inventado por Donald Shell, em 1959.
Ideia: no insertionsort, cada elemento viaja para a sua posio final de uma
em uma posio.
Seria melhor que viajasse dando saltos maiores...
Pois bem: modifica-se o insertionsort para no ciclo interior comparar o
elemento na posio j com o elemento na posio j h, para um nmero
milagroso h.
Se for caso disso, troca-se o elemento na posio j com o elemento na
posio j h.
Desta forma, os dois elementos trocados do saltos grandes.
Comea-se com h grandes e vai-se diminuindo at 1.
Quando h for 1 temos um insertionsort normal, que ordena o que faltar.
Mas agora, depois das anteriores passagens com valores de h maiores,
estamos no caso especial em que os elementos esto perto da sua posio
final.
A arte est em escolher bem a sequncia dos h.
6/1/16 Algoritmos e Estruturas de Dados 3
Classe Shellsort
Este o Shellsort na sua forma mais habitual:
public class Shellsort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
int n = a.length;
int h = 1;
while (h < n/3) Este ciclo constri implicitamente a sequncia dos h. Por
h = 3*h+1; exemplo, se n for 1000, o valor inicial de h 364. Depois, no
while (h >= 1) segundo ciclo, h toma os valores 121, 40, 13, 4, 1 (e 0, que
{ faz parar o ciclo).
for (int i = h; i < n; i++)
for (int j = i; j >= h && less(a[j], a[j-h]); j-=h)
exchange(a, j, j-h);
h /= 3;
}
}
... Repare: ciclo triplo. Muito difcil!

6/1/16 Algoritmos e Estruturas de Dados 4


Shellsort explicado
Eis uma verso mais entendvel, sem o ciclo triplo:
public class ShellsortExplained<T extends Comparable<T>> extends Sort<T>
{

public void sort(T[] a)


{
int n = a.length;
int h = 1;
while (h < n/3)
h = 3*h+1;
while (h >= 1)
{ Substitumos os dois ciclos for
shell(a, n, h); por uma chamada da funo
h /= 3; shell.
}
}
Esta insere o valor x
public void extend(T[] a, int n, T x, int h) ordenadamente no subarray
{ inicial de a com n elementos,
for (int j = n; j >= h && less(a[j], a[j-h]); j-=h) dando saltos de h elementos
exchange(a, j, j-h);
de cada vez.
}

public void shell(T[] a, int n, int h)


{ Esta repete a anterior para
for (int i = h; i < n; i++) todos os elementos a partir da
extend(a, i, a[i], h); posio h.
}
...
6/1/16 Algoritmos e Estruturas de Dados 5
Filme do shellsort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/shellsort.m4v

6/1/16 Algoritmos e Estruturas de Dados 6


Ensaio de razo dobrada, Shellsort
No meu computador:
$ java -cp ../../*:. Shellsort C
10000 0.0025
20000 0.0057 2.30
40000 0.0136 2.39 O ensaio de razo dobrada indica que o algoritmo
80000 0.0324 2.38 subquadrtico. Knuth mostrou que com a
160000 0.0813 2.51 sequncia 1, 4, 13, 40, 121, 364, ..., a ordem de
crescimento do tempo de execuo no pior caso
320000 0.2575 3.17
N3/2, o que compatvel com as nossas
640000 0.7050 2.74 observaes, pois 23/2 = 2.82842712474619.
1280000 1.6690 2.37
2560000 4.4150 2.65 $ java -cp ../../*:. Insertionsort C
5120000 10.3830 2.35 1000 0.0014
2000 0.0053 3.88
$ 4000 0.0215 4.03
8000 0.0878 4.09
16000 0.3530 4.02
32000 1.5850 4.49
A melhoria em relao ao 64000 6.8030 4.29
insertionsort abismal! $
6/1/16 Algoritmos e Estruturas de Dados 7
Mergesort
Baseia-se na operao de fuso de arrays ordenados: dados dois
arrays ordenados, reunir todos os elementos num nico array,
tambm ordenado.
um exerccio de programao clssico.
Faz-se numa s passagem por cada um dos arrays.
Logo, a fuso de arrays ordenados um algoritmo linear.
O mergesort aplica a estratgia algortmica dividir para reinar (em
ingls, divide and conquer; em latim, divide et impera).
Explica-se facilmente, recursivamente:
Se o array tiver menos de dois elementos, j est ordenado.
Se no, faz-se assim:
Ordena-se a primeira metade do array. Aqui se divide o problema em
Ordena-se a segunda metade do array. subproblemas mais pequenos.
Fundem-se as duas metades, j ordenadas, assim obtendo um array
ordenado. Aqui se reina, combinando as solues dos subproblemas
6/1/16 pequenos para construir
Algoritmos e Estruturasa de
soluo
Dados do problema grande. 8
Mergesort funcional
Adotemos uma atitude funcional, no modificando os arrays
fornecidos, antes criando novos arrays para o resultado das
operaes.
A funo merge toma dois arrays ordenados como
argumentos e funde-os no resultado:
public class MergesortFunctional<T extends Comparable<T>> extends Sort<T>
{
public T[] merge(T[] a, T[] b)
{
T[] result = Utils.newArrayLike(a, a.length + b.length);
int i = 0;
int j = 0; Se o primeiro array j chegou ao fim,
for (int k = 0; k < result.length; k++) copia-se do segundo; se o segundo j
if (i == a.length) chegou ao fim, copia-se do primeiro; se
result[k] = b[j++]; nenhum dos dois chegou ao fim, copia-se
else if (j == b.length) de aquele cujo elemento corrente for
result[k] = a[i++]; menor.
else
result[k] = less(a[i], b[j]) ? a[i++] : b[j++];
return result;
} ...
Note bem: a funo cria um novo array
6/1/16 para conter o eresultado
Algoritmos daDados
Estruturas de fuso. 9
Nota tcnica
A funo merge precisa de criar um array com elementos do tipo genrico
T.
J sabemos que isso no se pode fazer de maneira limpa em Java.
Temos ultrapassado a questo usando uma converso:
T[] temp = (T[]) new Object[capacity];
Neste caso, essa tcnica no d, porque, para complicar, T um tipo
genrico restringido:T extends Comparable<T>.
Temos de usar uma artilharia mais pesada, recorrendo aos mecanismos de
introspeo do Java, que permitem consultar os tipos dos objetos em
tempo de execuo.
Dessa forma, conseguimos dinamicamente criar um array do mesmo tipo
de outro array pr-existente.
T[] result = Utils.newArrayLike(a, a.length + b.length);
A funo newArrayLike faz o trabalho sujo.
No exemplo, o array result fica com elementos do mesmo tipo que o array
a e com tamanho a.length + b.length. Note bem: esse tipo s determinado
6/1/16 em tempo de execuo.
Algoritmos e Estruturas de Dados 10
Classe Utils
Introduzimos uma classe Utils, para agrupar funes
teis:
public final class Utils
{
private Utils() { }

public static Integer[] integersFromInts(int[] a)


{
...
}

public static int[] intsFromIntegers(Integer[] a)


{
...
}

public static <T> String stringOf(T[] a, String separator)


{
...
} Estas funes, e outras do mesmo gnero, tm andado dispersas,
... pelas classes onde surge a necessidade delas. A partir de agora,
usaremos esta classe Utils, e arrumamo-las aqui.
6/1/16 Algoritmos e Estruturas de Dados 11
Funo newArrayLike
A funo newInstance , do classe Array (esta do pacote
java.lang.reflect), cria um array com elementos do tipo dos
elementos do array indicado no primeiro argumento e com o
tamanho indicado no segundo:
public final class Utils
{
...
@SuppressWarnings("unchecked")
public static <T> T[] newArrayLike(T[] a, int n)
{
T[] result = (T[]) java.lang.reflect.Array.newInstance
(a.getClass().getComponentType(), n);
return result;
}
...

O mtodo getClass, da classe Object, retorna o tipo do


objeto, em tempo de execuo (neste caso, array do que for).
O mtodo getComponentType, da classe Class, retorna o tipo
dos elementos do array.
6/1/16
Ambas as funes, getClass e getComponentType,
Algoritmos e Estruturas de Dados 12
retornam objetos de tipo Class.
Mergesort funcional, ordenao
A ordenao funcional por fuso estupidamente
simples:
public class MergesortFunctional<T extends Comparable<T>> extends Sort<T>
{
public T[] sortf(T[] a)
{ Palavras para qu?
return (a.length <= 1) ? a : merge(
sortf(Utils.slice(a, 0, a.length / 2)),
sortf(Utils.slice(a, a.length / 2, a.length - a.length / 2)));
}
...

A funo utilitria slice(a, start, n), cria um novo array


com os n elementos a partir de a[start]: a[start],
a[start+1], ..., a[start+n-1]:
public static <T> T[] slice(T[] a, int start, int n)
{
T[] result = newArrayLike(a, n);
for (int i = 0; i < n; i++)
Esta fica na
result[i] = a[start+i]; classe Utils.
return result;
6/1/16 } Algoritmos e Estruturas de Dados 13
Funo sort do mergesort funcional
Para compatibilidade com as outras classes de ordenao,
temos de fornecer tambm a funo sort que modifica o array
que lhe passado:
public class MergesortFunctional<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
Utils.copyFrom(a, sortf(a));
}
...

A funo utilitria copyFrom copia do segundo argumento


para o primeiro:
public static <T> void copyFrom(T[] a, T[] b)
{
assert a.length == b.length;
for (int i = 0; i < a.length; i++)
a[i] = b[i];
}

Note que no estaria bem fazer apenas a = sortf(a) pois nesse caso estaramos a mudar a
cpia local da referncia para o array. O array passado em argumento ficaria na mesma!
6/1/16 Algoritmos e Estruturas de Dados 14
Ensaio de razo dobrada, mergesort funcional
No meu computador:
$ java ... MergesortFunctional C
10000 0.0027
20000 0.0042 1.60
40000 0.0088 2.06
80000 0.0193 2.21 Notamos que os quocientes so acima de 2
160000 0.0447 2.31 (quando os tempos deixam de ser diminutos) mas
320000 0.0905 2.03 abaixo dos do shellsort. Estes valores so um
640000 0.2183 2.41 sintoma de complexidade N log N.
1280000 0.5040 2.31
2560000 1.9450 3.86 $ java ... Shellsort C
10000 0.0025
5120000 3.8270 1.97 20000 0.0057 2.30
$ 40000 0.0136 2.39
80000 0.0324 2.38
160000 0.0813 2.51
320000 0.2575 3.17
O mergesort funcional mais de duas 640000 0.7050 2.74
1280000 1.6690 2.37
vezes mais rpido do que Shellsort, 2560000 4.4150 2.65
para arrays grandes. 5120000 10.3830 2.35
$
6/1/16 Algoritmos e Estruturas de Dados 15
Mergesort tradicional
Normalmente, queremos fazer o mergesort usando s dois arrays:
o array a ordenar, o qual vai receber o resultado de cada uma das
fuses, e um array auxiliar, para onde copiamos, justapostas, as duas
metades do array, j ordenadas, em cada chamada recursiva, para
depois serem fundidas de volta para o array dado.
Normalmente esse array auxiliar seria declarado na funo merge e
copiaramos para ele o array cujas metades ordenadas queremos
fundir, assim, em esquema:
public void merge(T[] a, ...)
{
T[] aux = (T[]) Array.newInstance(a.getClass().getComponentType(), ...);
...
}

No entanto, esta soluo envolveria a criao de um novo array em


cada chamada recursiva, o que causa um overhead que ser melhor
evitar. Recorde que, em Java, a criao de um array uma operao linear, uma vez
que todas as referncias presentes no array tm de ser anuladas na criao.
6/1/16 Algoritmos e Estruturas de Dados 16
Classe Mergesort
Declaramos o array auxiliar no mtodo sort e passamo-lo por
argumento s outras funes:
public class Mergesort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
T[] aux = (T[]) Utils.newArrayLike(a, a.length);
sort(a, 0, a.length, aux);
}

private void sort(T[] a, int start, int n, T[] aux)


{
Esta funo corresponde
if (n <= 1)
return; ordenao da fatia de a que
sort(a, start, n/2, aux); comea na posio start e tem n
sort(a, start+n/2, n-n/2, aux); elementos, usando o array aux.
merge(a, start, n/2, n-n/2, aux);
}
Ver a funo merge na pgina seguinte.
...

6/1/16 Algoritmos e Estruturas de Dados 17


Funo merge
Esta a nica que trabalha realmente:
public void merge(T[] a, int start, int n, int m, T[] aux)
{
for (int i = 0; i < n+m; i++)
aux[i] = a[start+i];
int p = 0;
int q = n;
for (int i = 0; i < n+m; i++)
if (p == n)
a[start+i] = aux[q++];
else if (q == n+m)
a[start+i] = aux[p++];
else
a[start+i] = less(aux[p], aux[q]) ? aux[p++] : aux[q++];
}
...
Funde o subarray de a que comea na posio start e tem n
elementos (e que j est ordenado) com o subarray que comea
na posio start+n e tem m elementos (e que tambm j est
ordenado). De incio copia os dois subarrays para o incio do
array aux e depois faz a fuso do array aux para o array a.
6/1/16 Algoritmos e Estruturas de Dados 18
Ensaio de razo dobrada, mergesort
No meu computador:
$ java ... Mergesort C
10000 0.0023
20000 0.0038 1.68
40000 0.0083 2.16
80000 0.0183 2.20 Estes quocientes parecem mais regulares do que
160000 0.0395 2.16 os do mergesort funcional.Talvez seja porque aqui
320000 0.0883 2.24 ocorrer menos a interveno inesperada da
640000 0.2023 2.29 garbage collection.
1280000 0.4630 2.29
$ java ... MergesortFunctional C
2560000 1.0580 2.29 10000 0.0027
5120000 2.3960 2.26 20000 0.0042 1.60
40000 0.0088 2.06
$ 80000 0.0193 2.21
160000 0.0447 2.31
320000 0.0905 2.03
640000 0.2183 2.41
O mergesort normal marginalmente 1280000 0.5040 2.31
2560000 1.9450 3.86
mais rpido do que o mergesort funcional. 5120000 3.8270 1.97
$
6/1/16 Algoritmos e Estruturas de Dados 19
Filme do mergesort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/mergesort.m4v

6/1/16 Algoritmos e Estruturas de Dados 20


Algoritmos e Estruturas de Dados

Lio n. 14
Complexidade da ordenao
Complexidade da ordenao
Limites da complexidade da ordenao.
Mergesort bottom-up.

6/1/16 Algoritmos e Estruturas de Dados 2


Perguntas metafsicas
Quantas comparaes precisamos de fazer
para ordenar um array?
Ou, alternativamente: perante um array a,
desconhecido, com N elementos, quantas
perguntas do estilo a[i] menor que a[j]?
tenho de fazer para garantidamente descobrir
por que ordem esto os elementos de a?
32, 5, 91, 95, 21, 70, 82, 14

6/1/16 Algoritmos e Estruturas de Dados 3


Perguntas metafsicas, ainda
Ou ainda, perante um array a que contm uma
permutao dos nmeros de 0 a N-1, quantas
perguntas como a anterior tenho de fazer para
descobrir a permutao presente em a?
Esta pergunta equivalente anterior, considerando
que o array a o array dos rankings dos elementos
do array que queremos ordenar.
Com efeito, descobrir o array dos rankings dos
elementos de um array equivale a ordenar o array.
Array: Array dos rankings:
32, 5, 91, 95, 21, 70, 82, 14 3, 0, 6, 7, 2, 4, 5, 1

6/1/16 Algoritmos e Estruturas de Dados 4


PermutationMind
Imaginemos um jogo estilo MasterMind, onde em vez
de peas coloridas temos N peas, cada uma com um
nmero, de 0 a N-1.
Como no MasterMind, h dois jogadores, A e B.
O jogador A fica com as peas e dispe-nas sequencialmente,
escondendo-as do jogador B.
O objetivo do jogador B descobrir a sequncia das peas
escondidas.
Para descobrir, B faz perguntas est a pea x esquerda da
pea y?, s quais A responder sim ou no.
Quantas perguntas tem B de fazer para descobrir?
Ou, por outras palavras, que sequncia de perguntas deve B
fazer para minimizar o nmero de perguntas?
6/1/16 Algoritmos e Estruturas de Dados 5
Estratgia fora bruta, oito peas
Uma estratgia vitoriosa consiste em fazer a seguinte
sequncia de perguntas:
Est a pea 0 antes da pea 1?
Est a pea 0 antes da pea 2?
...
Est a pea 0 antes da pea 7?
Est a pea 1 antes da pea 2?
Est a pea 1 antes da pea 3?
Por exemplo, o pea x tal que a
... resposta s perguntas est a pea x
Est a pea 1 antes da pea 7? antes da pea y seja sim e a
resposta s perguntas est a pea y
... antes da pea x? seja no, essa pea
Est a pea 6 antes da pea 7? a primeira.

Com as respostas, facilmente se estabelece a ordenao, no


?
6/1/16 Algoritmos e Estruturas de Dados 6
A fora bruta quadrtica
A estratgia da fora bruta, com 8 peas usa 28
perguntas e garante a vitria. (Note bem: 28 = 7 + 6 + ...
+ 1 + 0 = (82 - 8) / 2).
Esta , essencialmente a estratgia do selection sort (e
tambm dos outros algoritmos elementares, cujo nmero
de comparaes, (N2-N)/2, no pior caso.
Ser que existe uma estratgia que garanta a vitria com
27 perguntas? E com 26? E com 25? ... E com 0?
Com 0 no h certamente. Com 28 j vimos que sim.
Qual ser ento o menor nmero X para o qual existe
uma estratgia que resolve o problema com X
perguntas? Usamos aqui 8 peas, para concretizar a
discusso, mas o problema o mesmo
com qualquer nmero de peas, claro.
6/1/16 Algoritmos e Estruturas de Dados 7
PermutationMind computacional
Joguemos o PermutationMind, mas com ajuda
computacional, para o jogador B.
Antes de formular cada pergunta, o jogador B analisa,
com a ajuda do programa, o progresso que far com cada
uma das perguntas vlidas, quando a resposta seja sim, e
quando a resposta for no.
O progresso medido pelo nmero de configuraes
ainda possveis, considerando as respostas dadas s
perguntas at altura.
Logo, o jogador B pode optar pela estratgia de em cada
passo escolher a pergunta que garante a mxima reduo
no nmero de configuraes possveis.

6/1/16 Algoritmos e Estruturas de Dados 8


Exemplo: PermutationMind com 4 peas
A configurao secreta 3, 0, 1, 2, mas o jogador B no sabe.
$ java ... Permutations 4 Questions
Initial permutations: 24 0 1 12 12
Aqui todas as Questions
Aqui, se B
0 1 12 0
0 1 2 3 0 2 12 12 perguntas 0 2 8 4 escolher 2 3
0 1 3 2 0 3 12 12 asseguram o 0 3 8 4 garante que a
0 2 1 3 1 2 12 12
0 2 3 1 1 3 12 12
mesmo 1 2 4 8
seguir restaro 6
1 3 4 8
0 3 1 2 2 3 12 12 progresso: 12. 2 3 6 6 permutaes.
0 3 2 1 ------ ------
1 0 2 3 ->1 ->2
1 0 3 2 0 1 2 3
1 2 0 3 s n
1 2 3 0 Remaining: 12 Remaining: 6
1 3 0 2 0 1 2 3 0 1 3 2
1 3 2 0 0 1 3 2 0 3 1 2
2 0 1 3 0 2 1 3 0 3 2 1
2 0 3 1 0 2 3 1 3 0 1 2
2 1 0 3 0 3 1 2 3 0 2 1
2 1 3 0 0 3 2 1 3 2 0 1
2 3 0 1 2 0 1 3
2 3 1 0 2 0 3 1
3 0 1 2 2 3 0 1 Note bem: se B tivesse
3 0 2 1 3 0 1 2 escolhido 0 2, por exemplo,
3 1 0 2 3 0 2 1
3 1 2 0 3 2 0 1
com sorte ficaria com 4
3 2 0 1 permutaes, mas com azar
3 2 1 0 ficaria com 8!
6/1/16 Algoritmos e Estruturas de Dados 9
(Continuao)
Questions Questions Questions
0 1 6 0 Aqui, se B escolher 0 1 3 0 Aqui, se B escolher 0 1 2 0 Aqui, s faz
0 2 5 1 0 3 ou 1 2 garante 0 2 2 1 0 2 ou 1 2 garante 0 2 2 0 sentido escolher 1
0 3 3 3 que a seguir 0 3 0 3 que a seguir 0 3 0 2 2, que garante que
1 2 3 3 restaro 3 1 2 1 2 restaro 2 1 2 1 1 s restar uma
1 3 1 5 permutaes. 1 3 0 3 permutaes. 1 3 0 2 permutao
2 3 0 6 2 3 0 3 2 3 0 2
------ ------ ------
->3 ->4 ->5
0 3 0 2 1 2
n s s
Remaining: 3 Remaining: 2 Remaining: 1
3 0 1 2 3 0 1 2 3 0 1 2
3 0 2 1 3 0 2 1
3 2 0 1 Est encontrada a
configurao inicial!
Note bem: ao escolher 0 2 ou
1 2, o jogador B garante que
no restaro mais do que 2
permutaes mas, com sorte,
restar s uma.

6/1/16 Algoritmos e Estruturas de Dados 10


Anlise
Comemos com 24 permutaes possveis.
Aps cada pergunta, o nmero de permutaes restantes
diminui.
Mas, qualquer que fosse a pergunta usada, B nunca conseguiria
fazer reduzir o nmero de permutaes para menos de
metade.
Logo, se, por hiptese, B s tivesse direito a 4 perguntas, no
haveria uma estratgia ganhadora, pois aps 4 perguntas, B s
poderamos garantir duas permutaes: 2412632.
Isto no quer dizer que, com sorte, B no pudesse reduzir o
nmero de permutaes a 1, com 4 perguntas.
Mas no era garantido.

6/1/16 Algoritmos e Estruturas de Dados 11


Generalizao
Se tivermos N nmeros, haver N! (fatorial de N) permutaes e
N*(N-1)/2 perguntas diferentes.
Cada pergunta no pode garantir um reduo do nmero de
permutaes restantes para menos de metade.
Portanto, impossvel garantir que a sequncia do nmero de
permutaes restantes seja melhor (isto , mais favorvel para B)
do que N!, N!/2, N!/4, ..., 1.
Em rigor, aqui o operador / representa o arredondamento para
cima do quociente exato.
Em geral, uma sequncia X, X/2, X/4, ..., 1 tem ceiling(log2(X)) + 1
elementos. Por exemplo.: 24, 12, 6, 3, 2, 1 tem 6 elementos, log224 = 4.58, ceiling (4.58) = 5.
Logo, a sequncia N!, N!/2, N!/4, ..., 1tem ceiling(log2(N!)) + 1
elementos.
Ou seja, nenhuma estratgia ganhadora no PermutationMind pode
ter menos do que ceiling(log2(N!)) perguntas.
6/1/16 Algoritmos e Estruturas de Dados 12
Quanto vale log2(N!)?
log2(N!) = log2(1) + log2(2) + log2(3) + ... + log2(N)
Aproximamos este somatrio com a aproximao de Stirling:

Isto tirado do Wolfram Alpha.

Conclumos que a soma dos


logaritmos N * log2N N
~ N * log2N.

Note bem: a aproximao de Striling do Wolfram


Alpha para o logaritmo natural, mas tambm os
mesmos clculos tambm se aplicam aos
logaritmos binrios.

6/1/16 Algoritmos e Estruturas de Dados 13


Moral da histria
No existe nenhuma estratgia ganhadora para o
PermutationMind com menos de ~N*log2N
perguntas.
E tambm, j que jogar o PermutationMind
essencialmente equivalente a ordenar o array inicial:
No existe nenhum algoritmo de ordenao baseado
exclusivamente em comparaes que use menos de
~N*log2N comparaes, para ordenar qualquer array
com N elementos.
Por conseguinte, no existe nenhum algoritmo
melhor que o mergesort! Dizemos que o
mergesort timo.
6/1/16 Algoritmos e Estruturas de Dados 14
Filme falado do mergesort
No mergesort normal de um array a com 8 elementos, por exemplo, h
duas chamadas de sort para subarrays com 4 elementos: a[0..3] e a[4..7]; a
primeira destas chamadas d origem a duas chamadas de sort para
subarrays com 2 elementos: a[0..1] e a[2..3]; a primeira destas chamadas d
origem a duas chamadas para subarrays com 1 elemento: a[0..0] e a[1..1],
que retornam logo.
Segue-se a fuso desses dois arrays com 1 elemento, que fica guardada no
subarray a[0..1].
Depois ocorre a segunda chamada de sort para subarrays com dois
elementos, agora para o subarray a[2..3], a qual d origem a duas chamadas
para subarrays de um elemento a[2..2] e a[3..3], as quais retornam logo.
Segue-se a fuso desses dois arrays com 1 elemento, que fica guardada no
subarray a[2..3].
Segue-se a fuso dos subarrays (j ordenados) a[0..1] e a[2..3], que fica
guardada no subarray a[0..3].

6/1/16 Algoritmos e Estruturas de Dados 15


Filme falado do mergesort (segunda parte)
Depois ocorre a segunda chamada de sort para subarrays com 4
elementos, neste caso respeitante ao subarray a[4..7].
Por um processo anlogo ao do subarray a[0..3], essa chamada terminar
com um subarray ordenado guardado em a[4..7].
Segue-se a fuso dos dois subarrays ordenados a[0..3] e a[4..7], ficando o
resultado a ocupar o espao do array inicial e o algoritmo termina.
H portanto 4 fuses 1+1, 2 fuses 2+2 e uma fuso 4+4.
Constatamos que em cada fuso i+i h no mnimo i comparaes e no
mximo 2*i-1 comparaes.
Generalizando, conclumos que comeando com um array com n
elementos, para cada tipo de fuso o nmero de comparaes ~n, no
pior caso.
O nmero de tipos de fuso (1 a 1, 2 a 2, etc.) log2 n.
Logo, o nmero de comparaes no pior caso ~n log2 n.
O nmero mnimo de comparaes na fuso ocorre quando o ltimo elemento de um dos subarrays
ordenados menor que o primeiro elemento do outro subarray. O nmero mximo ocorre quando o
ltimo elemento de cada um dos arrays maior que o penltimo do outro. Este o pior caso.
6/1/16 Algoritmos e Estruturas de Dados 16
Mergesort bottom up
Retomando o exemplo das pginas anteriores, constatamos que as
chamadas recursivas ocorrem pela seguinte ordem: S8, S4, S2, S1, S1,
F1+1, S2, S1, S1, F1+1, F2+2, S4, S2, S1, S1, F1+1, S2, S1, S1, F1+1,
F2+2, F4+4.
Ora, em vez de recursivamente ordenar e depois fundir arrays cada
vez mais pequenos (com metade do tamanho), comeando pelo
array original, de cima para baixo, podemos proceder de baixo
para cima, fundindo primeiro, numa s passagem, todos os pares
de subarrays com 1 elemento,; depois, fundindo noutra passagem
todos os pares de subarrays com 2 elementos no array resultantes
da fuso 1 a 1, os quais estaro ordenados; depois fundindo numa
terceira passagem todos pares de subarrays com 4 elementos no
array resultantes da fuso 2 a 2, os quais estaro ordenados; etc.
Desta maneira ascendente conseguimos o mesmo efeito, sem a
maada das chamadas recursivas.
6/1/16 Algoritmos e Estruturas de Dados 17
Classe MergesortBottomUp
simples mas denso:
public class MergesortBottomUp<T extends Comparable<T>>
extends Mergesort<T>
{
public void sort(T[] a)
{ Note que no h chamadas recursivas.
T[] aux = (T[]) Utilities.newArrayLike(a, a.length);
int n = a.length;
for (int z = 1; z < n; z += z)
for (int i = 0; i < n - z; i += 2*z)
merge(a, i, z, Math.min(z, n - (i+z)), aux);
}
As fuses so as mesmas que no mergesort top-down, mas
...
realizam-se por outra ordem.
Fazemos esta classe derivar de Mergesort<T> para poder
usar a funo merge por herana.

6/1/16 Algoritmos e Estruturas de Dados 18


Ensaio de razo dobrada, mergesort bottom-up
No meu computador:
$ java ... MergesortBottomUp C
10000 0.0023
20000 0.0041 1.80
40000 0.0088 2.18
80000 0.0191 2.16
160000 0.0416 2.18
320000 0.0993 2.39
640000 0.2333 2.35
1280000 0.5320 2.28 $ java ... Mergesort C
2560000 1.2060 2.27 10000 0.0023
20000 0.0038 1.68
5120000 2.6940 2.23 40000 0.0083 2.16
80000 0.0183 2.20
$ 160000 0.0395 2.16
320000 0.0883 2.24
640000 0.2023 2.29
Os valores so semelhantes aos do 1280000 0.4630 2.29
2560000 1.0580 2.29
mergesort top-down, ainda que, nesta 5120000 2.3960 2.26
experincia, ligeiramente menos bons. $
6/1/16 Algoritmos e Estruturas de Dados 19
Filme do mergesort bottom-up

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/mergesort_bottomup.m4v

6/1/16 Algoritmos e Estruturas de Dados 20


Algoritmos e Estruturas de Dados

Lio n. 15
Quicksort
Quicksort
Quicksort clssico.
Complexidade do quicksort.
Vulnerabilidade do quicksort.

6/1/16 Algoritmos e Estruturas de Dados 2


Quicksort
Inventado por Hoare, em 1962.
Ideia: reorganizar o array, partindo-o em dois
subarrays justapostos, de tal maneira que cada um
dos elementos do primeiro subarray menor (ou
igual) que cada um dos elementos do segundo
subarray.
Depois, reorganizar da mesma maneira,
recursivamente, cada um dos subarrays, isto ,
partir cada um deles em dois subsubarrays
justapostos, etc.
No final, o array estar ordenado.
A arte est na partio!
6/1/16 Algoritmos e Estruturas de Dados 3
Funo partition
Teremos na classe Quicksort<T> uma funo
milagrosa, partition, para realizar a partio:
public class Quicksort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
...
}

...

public int partition(T[] a, int start, int n)


{
... Nesta funo, a o array, start o deslocamento do subarray que
} nos interessa e n o nmero de elementos desse subarray. Em
conjunto, os trs argumentos representam o subarray
... a[start..start+n-1]. Sendo p o valor retornado pela funo, ento,
aps a funo, milagrosamente, cada um dos elementos do subarray
a[start..start+p-1] menor ou igual a cada um dos elemento do
subarray a[start+p..start+n-1] e nenhum destes subarrays vazio.
6/1/16 Algoritmos e Estruturas de Dados 4
Funo quicksort
J podemos programar o quicksort, dado o
array, a posio inicial do subarray e o nmero
de elementos:
public class Quicksort ...
{
...
public void quicksort(T[] a, int start, int n)
{
int p = partition(a, start, n);
if (p > 1)
quicksort(a, start, p);
if (n - p > 1)
quicksort(a, start+p, n-p);
}
... Palavras para qu?
6/1/16 Algoritmos e Estruturas de Dados 5
Funo sort
Implementamos a funo sort em termos da
funo quicksort:
public class Quicksort ...
{
public void sort(T[] a)
{
quicksort(a, 0, a.length);
}
... A primeira chamada diz
respeito ao array todo, claro.

6/1/16 Algoritmos e Estruturas de Dados 6


Como se faz a partio?
H vrias tcnicas. A seguinte a partio clssica, usada por
Hoare:
1. Selecionamos o elemento de ndice mdio, chamado piv.
2. Percorremos o array da esquerda para a direita at
encontrar um elemento maior ou igual ao piv.
3. Percorremos o array da direita para a esquerda at
encontrar um elemento menor ou igual ao piv.
4. Trocamos os elementos selecionados nos passos 2 e 3, se o
primeiro ainda estiver esquerda do segundo, no array.
5. Repetimos a partir de 2, recomeando os percursos nas
posies imediatamente a seguir s dos elementos que foram
trocados (no sentido respetivo), at os percursos se
cruzarem.
Agora s programar isto!
6/1/16 Algoritmos e Estruturas de Dados 7
Partio de Hoare
public class Quicksort ...
{
...
public int partition(T[] a, int start, int n)
{
int i = start; Durante a reorganizao do subarray
int j = start + n - 1; a[start.. start+n-1], invariantemente, todas
T p = a[start+n/2]; as posies esquerda de i tm
do elementos menores ou iguais ao piv e
{ todas as posies direita de j tm
while (less(a[i], p)) elementos maiores ou iguais ao piv.
i++; Logo, no fim do ciclo, quando j < i, no
while (less(p, a[j])) existe direita de j nenhum elemento
j--; menor que o piv, nem existe esquerda
if (i <= j) de i nenhum elemento maior que o piv.
exchange(a, i++, j--); Portanto, cada elemento de a[start.. j]
} menor ou igual a cada elemento de a[j+1..
while (i <= j); start+n-1]. Logo, o nmero de elementos
return j+1 - start; do primeiro subarray resultante da
} partio j+1-start.
6/1/16 ... Algoritmos e Estruturas de Dados 8
Exemplo
Observe:
17 4 16 6 2 18 12 0 13 14 8 29 15
17 4 16 6 2 18 12 0 13 14 8 29 15 A vermelho, o
piv; a azul, os
8 4 16 6 2 18 12 0 13 14 17 29 15
elementos que
8 4 0 6 2 18 12 16 13 14 17 29 15 vo ser trocados.
8 4 0 6 2 12 18 16 13 14 17 29 15
Nesta altura, j vale 5 e i vale 6.
O primeiro subarray :
8 4 0 6 2 12
E o segundo subarray :
18 16 13 14 17 29 15

6/1/16 Algoritmos e Estruturas de Dados 9


Filme do quicksort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/quicksort.m4v

6/1/16 Algoritmos e Estruturas de Dados 10


Ensaio de razo dobrada, quicksort
No meu computador:
$ java ... Quicksort C
10000 0.0018
20000 0.0035 1.96
40000 0.0077 2.22
80000 0.0165 2.15
160000 0.0364 2.21
320000 0.0900 2.47
640000 0.2313 2.57
1280000 0.5490 2.37 $ java ... Mergesort C
2560000 1.2410 2.26 10000 0.0023
20000 0.0038 1.68
5120000 2.8320 2.28 40000 0.0083 2.16
80000 0.0183 2.20
160000 0.0395 2.16
320000 0.0883 2.24
640000 0.2023 2.29
Constatamos que o mergesort um pouco 1280000 0.4630 2.29
2560000 1.0580 2.29
mais rpido que o quicksort, para arrays de 5120000 2.3960 2.26
Integer aleatrios. $
6/1/16 Algoritmos e Estruturas de Dados 11
Complexidade do quicksort
O desempenho do quicksort depende de as parties serem
equilibradas.
Seja um array com N elementos:
Na primeira partio so visitados os N elementos.
Seguem-se duas chamadas recursivas de primeiro nvel, as quais em
conjunto visitam todos os N elementos.
Cada uma delas d origem a duas chamadas recursivas de segundo
nvel.
H, portanto, quatro chamadas recursivas de segundo nvel, as quais em
conjunto visitam os N elementos.
E assim por diante.
Em cada nvel so visitados os N elementos, at que, por os intervalos
se tornarem unitrios, algumas das chamadas recursivas deixem de se
fazer.
Nessa altura, em cada nvel visitam-se menos de N elementos.
6/1/16 Algoritmos e Estruturas de Dados 12
Quicksort com sorte
Num quicksort com sorte, todas as parties seriam
perfeitamente equilibradas: em cada chamada recursiva da
funo quicksort, o intervalo seria dividido ao meio.
Nesse caso, todas as chamadas recursivas terminam ao
mesmo nvel (mais ou menos 1).
Se N fosse uma potncia de 2 e parties fossem equilibradas,
o nmero mximo de chamadas recursivas da funo
quicksort empilhadas na pilha de execuo seria log2N.
Logo, com parties equilibradas, o procedimento exige
N log2N acessos aos elementos do array.
Esta a melhor situao possvel: o tempo de execuo
proporcional a N log N.

6/1/16 Algoritmos e Estruturas de Dados 13


Quicksort com azar
Num quicksort com azar, a partio completamente
desequilibrada: em cada nvel um dos subarrays fica com 1
elemento e o outro com os restantes.
Neste caso haver N nveis de recursividade, ainda que em
cada nvel haja apenas uma chamada recursiva, para a parte
no unitria.
No nvel k so visitados N-k elementos.
Ao todo, o procedimento desequilibrado exige N + (N-1) + ...
+ 1 ~ N2/2 acessos ao elementos do array.
Aqui o desempenho quadrtico!
Este o caso mais desfavorvel.
Para arrays grandes, haver stack overflow.

6/1/16 Algoritmos e Estruturas de Dados 14


Piv central
Escolher para piv o elemento central tem como
vantagem que um array ordenado d origem a uma
partio perfeitamente equilibrada.
Podemos admitir que acontece por vezes
ordenarmos arrays que afinal j estavam ordenados.
Se tivssemos escolhido para piv o primeiro
elemento, um array ordenado provocaria uma
partio completamente desequilibrada.
Nestas condies, o piv central prefervel.
Mas mesmo com piv central, h casos patolgicos
que provocam parties completamente
desequilibradas.
6/1/16 Algoritmos e Estruturas de Dados 15
Atacando o quicksort
O quicksort vulnervel a ataques.
Isto , podemos fabricar um array que faa o
quicksort agachar-se, descambando em
comportamento quadrtico.
Considere o seguinte array, com 10 elementos:
1 3 5 7 9 10 6 4 8 2
Apesar do seu aspeto inocente, este array uma
bomba para o quicksort, pois s d parties
desequilibradas, j que de cada vez o piv o
maior dos elementos restantes.

6/1/16 Algoritmos e Estruturas de Dados 16


Filme do ataque
Observe:
1 3 5 7 9 10 6 4 8 2 De cada vez o i avana at ao piv
e o j fica com o valor inicial. Segue-
1 3 5 7 9 2 6 4 8 10 se a troca, que atira o piv para a
ltima posio. No passo seguinte
1 3 5 7 8 2 6 4 9 10 do ciclo exterior da partio, o i
avana de novo at ao piv (que
1 3 5 7 4 2 6 8 9 10 agora est na ltima posio) e
no h mais trocas. O j ter ficado
1 3 5 6 4 2 7 8 9 10 na posio esquerda da final.

1 3 5 2 4 6 7 8 9 10
1 3 4 2 5 6 7 8 9 10
Como evitar o ataque?
1 3 2 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
6/1/16 Algoritmos e Estruturas de Dados 17
Evitando o ataque
Evita-se o ataque escolhendo um piv que no
seja nem o maior elemento do array nem o
menor.
H vrias tcnicas para fazer isso.
A mais prtica baralhar aleatoriamente o array
antes de ordenar.
Assim, fica impossvel fabricar bombas
deliberadamente.
A probabilidade de a baralhao aleatria de um
array grande produzir uma bomba nfima.

6/1/16 Algoritmos e Estruturas de Dados 18


Protegendo o quicksort
Recorremos funo StdRandom.shuffle() da
biblioteca algs4.jar.
public class Quicksort ...
{
public void sort(T[] a)
{
StdRandom.shuffle(a);
quicksort(a, 0, a.length);
}
...

Quer dizer, antes de ordenar, desordena-se!

6/1/16 Algoritmos e Estruturas de Dados 19


Baralhao de Knuth
A funo StdRandom.shuffle() usa o algoritmo da
baralhao de Knuth, que consiste em trocar
sucessivamente cada elemento do array com outro
escolhido numa posio aleatria da para a frente.
Para arrays de ints, est programada assim:
public static void shuffle(int[] a)
{
int N = a.length; Note bem: a baralhao
for (int i = 0; i < N; i++) tem complexidade linear.
{
int r = i + uniform(N - i); // between i and N-1
int temp = a[i];
a[i] = a[r];
a[r] = temp; Este algoritmo baralha e no enviesado. Quer
} dizer, gera todas as permutaes do array com
} igual probabilidade.
6/1/16 Algoritmos e Estruturas de Dados 20
Curiosidade: quicksort funcional
Usando a artilharia da nossa classe ArrayBag, rapidamente
construmos um quicksort funcional, em que a partio se faz
base de filtrao, usando como piv o primeiro elemento do
arraybag a ser ordenado:
public class QuicksortFunctional<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
if (a.length > 1)
Utils.copyFrom(a, quicksort(new ArrayBag<>(a)).toArray());
}

public ArrayBag<T> quicksort(ArrayBag<T> a)


{
ArrayBag<T> result = new ArrayBag<>();
if (!a.isEmpty())
{
T p = a.get(a.size() / 2);
ArrayBag<T> aLess = quicksort(a.filter(x -> less(x, p)));
ArrayBag<T> aEqual = a.filter(x -> equal(x, p));
ArrayBag<T> aGreater = quicksort(a.filter(x -> greater(x, p)));
result.add(aLess);
result.add(aEqual);
result.add(aGreater);
}
return result;
}
6/1/16... Algoritmos e Estruturas de Dados 21
Ensaio de razo dobrada, quicksort funcional
No meu computador:
$ java ... QuicksortFunctional C
10000 0.0125
20000 0.0169 1.35
40000 0.0368 2.18
80000 0.0688 1.87
160000 0.1478 2.15
320000 0.3765 2.55
640000 0.7620 2.02
$ java ... Quicksort C
1280000 1.7950 2.36 10000 0.0018
2560000 4.8460 2.70 20000 0.0035 1.96
5120000 10.3400 2.13 40000 0.0077 2.22
80000 0.0165 2.15
160000 0.0364 2.21
320000 0.0900 2.47
Constatamos que o nosso quicksort 640000 0.2313 2.57
1280000 0.5490 2.37
funcional quase quatro vezes mais
2560000 1.2410 2.26
lento do que o quicksort clssico. 5120000 2.8320 2.28
$
6/1/16 Algoritmos e Estruturas de Dados 22
Algoritmos e Estruturas de Dados

Lio n. 16
Quicksorts especializados
Quicksorts especializados
Quicksort com cutoff.
Quicksort tripartido.
Quicksort com duplo piv.

6/1/16 Algoritmos e Estruturas de Dados 2


Quicksort com cutoff
Como no quicksort h muitas chamadas recursivas
para arrays pequenos, poupar-se- algum trabalho,
parando a recursividade antes dessas chamadas.
Isto , em vez de:
if (p > 1)
quicksort(a, f, p);
teremos:
if (p > cutoff)
quicksort(a, f, p);

Claro que assim o array no ficar ordenado.


Mas ficar quase ordenado.
Ento faz-se uma passagem final com o insertionsort,
que muito eficiente para esse tipo de arrays.
6/1/16 Algoritmos e Estruturas de Dados 3
Classe QuicksortCutoff
public class QuicksortCutoff<T extends Comparable<T>> extends Quicksort<T>
{
public static final int CUT_OFF = 5;
O valor ideal para o cutoff tem
private int cutoff = CUT_OFF; de ser afinado em cada sistema.
private Insertionsort<T> isort = new Insertionsort<T>();

public QuicksortCutoff(int x)
{
cutoff = x;
}

public void sort(T[] a)


{
StdRandom.shuffle(a);
quicksort(a, 0, a.length);
isort.sort(a);
}

public void quicksort(T[] a, int x, int n)


{
int p = partition(a, x, n);
if (p > cutoff)
quicksort(a, x, p);
if (n - p > cutoff)
quicksort(a, x + p, n - p);
}
6/1/16 Algoritmos e Estruturas de Dados 4
Caso dos arrays com muitos duplicados
Na prtica, muitas vezes os arrays que queremos ordenar tm muitos
duplicados, isto , elementos iguais, ou, mais geralmente, elementos com a
mesma chave de ordenao.
O mergesort no tira partido dessa circunstncia, porque faz sempre a
mesma sequncia de comparaes, independentemente dos valores no
array.
O quicksort tambm no, porque os elementos iguais ao piv tambm so
trocados e, portanto, no caso em que h muitos duplicados teremos
muitas trocas de elementos iguais, inutilmente.
Eis dois filmes que ilustram esta situao:
Mergesort Quicksort

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/mergesort_duplicates.m4v


6/1/16 Algoritmos e Estruturas de Dados
e http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/quicksort_duplicates.m4v 5
Caso dos arrays com dois valores
De facto, para ordenar um array com dois valores, basta fazer uma
partio.
Em vez da partio de Hoare, podemos fazer um s varrimento, da
esquerda para a direita e trocar cada elemento maior que o piv com o
ltimo elemento do array, dos que ainda no foram objeto de troca.
Veja a demonstrao, em que os valores so verde e vermelho e queremos
colocar todos os verdes antes de todos os vermelhos:

Este o problema da bandeira portuguesa... (Cf. pgina seguinte.)

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/partition_2.m4v

Neste caso simples, com dois valores conhecidos


(verde e vermelho), como se fixssemos logo que o
piv era o valor mnimo (neste caso, o verde).
6/1/16 Algoritmos e Estruturas de Dados 6
Caso dos arrays com trs valores
Se o array tiver apenas trs valores diferentes tambm conseguimos ordenar
numa s passagem.
Por exemplo, os valores so azul, e vermelho, e queremos todos azuis
antes de todos os brancos antes de todos os vermelhos.
Em cada momento, teremos um grupo de azuis, depois um grupo de brancos,
depois um grupo de desconhecidos, depois um grupo de vermelhos.
Varremos o array da esquerda para a direita, observando o primeiro dos
desconhecidos:
Se for azul, trocamos com o primeiro dos brancos (se houver).
Se for branco, deixamos onde est.
Se for vermelho, trocamos com o ltimo dos desconhecidos (e no avanamos).

Este o famoso problema da bandeira holandesa, de


Dijkstra, ainda que aqui parea mais a bandeira francesa.

Veja
6/1/16 online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/partition_3.m4v
Algoritmos e Estruturas de Dados 7
Quicksort tripartido
Faz uma partio ternria, anloga da bandeira holandesa.
Os azuis so os elementos menores que o piv; os brancos so os iguais
ao piv; os vermelhos so os maiores que o piv:
public void partitionTri(T[] a, int d, int n)
{
int i = 1; // number of elements less than pivot, so far, plus 1 for the pivot
int j = 0; // number of elements equal to pivot, so far;
int k = 0; // number of elements greater than pivot, so far;
T p = a[d];
while (i+j+k < n) Note bem: o piv o primeiro elemento,
{ que fica de fora das comparaes; no fim
T x = a[d+i+j];
if (less(x, p)) ser trocado para a posio certa.
{
exchange(a, d+i, d+i+j);
i++; Azuis
}
else if (less(p, x))
{
exchange(a, d+n-1-k, d+i+j); Vermelhos
k++;
} OK, mas os valores
else
Brancos finais de i, j e k tm
j++;
} de ser devolvidos
exchange(a, d, d+i-1); // move pivot to its sorted position pela funo. Como
i--; fazer?
}
6/1/16 Algoritmos e Estruturas de Dados 8
Longos, para pares
A funo partitionTri tem de devolver os valores finais de i, j e k, para
serem usados nas chamadas recursivas subsequentes.
Na verdade, basta devolver dois deles, por exemplo, i e k, pois ento j = n
(i+k).
Mas as funes s podem devolver um valor!
Agrupemos ento dois int num long, e devolvamos um long.
Eis a infraestrutura:
public static long pair(int x, int y)
{
long xl = x;
long yl = y; public static int first(long x)
xl = xl << 32; {
yl = yl & 0x0FFFFFFFFL; long result = x >> 32;
return xl | yl; return (int) result;
} }

public static int second(long x)


{
return (int) x;
}

6/1/16 Algoritmos e Estruturas de Dados 9


Partio ternria
Fica assim, usando a tcnica dos longos:
public long partitionTri(T[] a, int d, int n)
{
int i = 1; // ...
int j = 0; // ...
int k = 0; // ...
T p = a[d];
while (i+j+k < n)
{
...
}
exchange(a, d, d+i-1); // ...
i--;
return pair(i, k);
}

6/1/16 Algoritmos e Estruturas de Dados 10


Classe QuicksortTripartite
public class QuicksortTripartite<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
StdRandom.shuffle(a); Baralhao, para evitar ataques!
quicksort(a, 0, a.length);
}

public long partitionTri(T[] a, int d, int n, T p)


{
...
}

public void quicksort(T[] a, int x, int n)


{
long p = partitionTri(a, x, n);
int n1 = first(p);
int n2 = second(p);
if (n1 > 1)
quicksort(a, x, n1);
if (n2 > 1)
quicksort(a, x+n-n2, n2);
}
...

6/1/16 Algoritmos e Estruturas de Dados 11


Filme do quicksort tripartido

Veja online em http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/quicksort_tripartite.m4v

6/1/16 Algoritmos e Estruturas de Dados 12


Atacando o quicksort tripartido
Se no fosse a baralhao, o quicksort tripartido com
piv central agachar-se-ia com um ficheiro em rgo
de igreja: Um ficheiro em rgo de igreja um ficheiro em que a
primeira metade crescente e a segunda metade
Observe: simtrica da primeira, por exemplo, [1 2 3 4 5 4 3 2 1].

Neste caso, nunca h elementos


vermelhos (maiores do que o piv) e
o nmero de azuis (menores do que o
piv) decresce linearmente, de 2 em 2.
Isto configura comportamento
quadrtico!

Na verdade, no exemplo, h um caso


de 6 elementos iguais, que, quando
apanhado, faz o intervalo de ordenao
decrescer 6 unidades de uma vez.

Veja online em
http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/quicksort_tripartite_organ.m4v
6/1/16 Algoritmos e Estruturas de Dados 13
Caso de haver muitos duplicados
O quicksort tripartido porta-se bem no caso de haver muitos
duplicados, pois todos os elementos iguais ao piv ficam logo
arrumados, de uma vez.
Com efeito, havendo muitos duplicados, haver muitos
elementos iguais ao piv.
Observe:

Veja online em
http://w3.ualg.pt/~pjguerreiro/sites/20_aed_1314/movies/quicksort_tripartite_lots_duplicates.m4v
6/1/16 Algoritmos e Estruturas de Dados 14
Quicksort com piv duplo
uma variante do tripartido, usando dois pivs. Assim, os
azuis so os menores que o primeiro piv, os vermelhos os
maiores que segundo piv e os outros so brancos.
O piv da esquerda menor ou igual ao piv da direita:
private long partitionDual(T[] a, int d, int n)
{
T p = a[d]; // left pivot
T q = a[d+n-1]; // right pivot
int i = 1; // number of elements < p, so far, plus 1 for a[0];
int j = 0; // number of elements >= p and <= q, so far;
int k = 1; // number of elements > q so far, plus 1 for a[n-1];
while (i+j+k < n)
{
T x = a[d+i+j];
if (less(x, p)) Azuis
...
else if (less(q, x)) Vermelhos
...
else
...; Brancos
}
return pair(i, k);
}
6/1/16 Algoritmos e Estruturas de Dados 15
The devil is in the details
private long partitionDual(T[] a, int d, int n)
{
if (less(a[d+n-1], a[d])) Trocamos os candidatos a pivs, se for preciso.
exchange(a, d, d+n-1);
T p = a[d]; // left pivot
T q = a[d+n-1]; // right pivot
int i = 1; // number of elements < p, so far, plus 1 for a[0];
int j = 0; // number of elements >= p and <= q, so far;
int k = 1; // number of elements > q so far, plus 1 for a[n-1];
while (i+j+k < n) De incio, no mexemos nos pivs. Fica para o fim.
{
...
}
// exchange left pivot with last element less than left pivot.
exchange(a, d, d+i-1);
i--;
// j++; OK, but not necessary
// exchange right pivot with first element greater than right pivot.
exchange(a, d+n-1, d+n-k);
k--; No fim, trocamos os pivs, que estavam nas pontas, para as
// j++; OK, but not necessary suas posies ordenadas. Note que estes elementos
return pair(i, k); atingiram as suas posies finais e no entraro em mais
} operaes.

6/1/16 Algoritmos e Estruturas de Dados 16


Classe QuicksortDualPivot
public class QuicksortDualPivot<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
StdRandom.shuffle(a);
quicksort(a, 0, a.length);
}

public void quicksort(T[] a, int x, int n)


{
long k = partitionDual(a, x, n);
int n1 = first(k);
int n2 = second(k);
if (n1 > 1)
quicksort(a, x, n1);
// the 2 pivots have reached their final positions
// a[x+n1] is the left pivot, a[x+n-1-r2] is the right pivot
if ((n-2) - (n1+n2) > 1)
if (less(a[x+n1], a[x+n-1-n2])) quicksort(a, x+n1+1, n - (n1+n2) - 2);
if (n2 > 1)
quicksort(a, x+n-n2, n2); Em geral haver trs
}
chamadas recursivas.
...

6/1/16 Algoritmos e Estruturas de Dados 17


Comparando os quatro quicksorts, pelo tempo
No meu computador:
$ java ... Quicksort C $ java ... QuicksortCutoff C
10000 0.0017 10000 0.0015
20000 0.0034 1.98 20000 0.0033 2.17
40000 0.0074 2.16 40000 0.0074 2.23
80000 0.0162 2.18 80000 0.0161 2.18
160000 0.0357 2.20 160000 0.0355 2.21
320000 0.0917 2.57 320000 0.0902 2.54
640000 0.2333 2.55 640000 0.2383 2.64
1280000 0.5450 2.34 1280000 0.5700 2.39
2560000 1.2370 2.27 2560000 1.3130 2.30
5120000 2.9260 2.37 5120000 3.0880 2.35
$ $

$ java ... QuicksortTripartite C $ java ... QuicksortDualPivot C


10000 0.0022 10000 0.0015
20000 0.0044 2.02 20000 0.0030 2.06
40000 0.0101 2.29 40000 0.0067 2.20
80000 0.0222 2.20 80000 0.0145 2.17
Note bem, estes
160000 0.0492 2.22 160000 0.0329 2.26 ensaios so com
320000 0.1202 2.44
640000 0.3310 2.75
320000 0.0823 2.50
640000 0.2183 2.65
arrays aleatrios.
1280000 0.7630 2.31 1280000 0.5120 2.35
2560000 1.7940 2.35 2560000 1.2010 2.35
5120000 4.1690 2.32 5120000 2.7070 2.25
6/1/16
$ Algoritmos e Estruturas
$ de Dados 18
Algoritmos e Estruturas de Dados

Lio n. 17
Outros algoritmos de ordenao
Outros algoritmos de ordenao
Timsort.
Ordenaes de arrays de nmeros.
Bucketsort.
Countsort.
Bitsort.

6/1/16 Algoritmos e Estruturas de Dados 2


Diz-se que um algoritmo de ordenao Diz-se que um algoritmo
Timsort adaptativo se tira partido da existncia
de troos ordenados.
de ordenao estvel se
no troca elemento iguais.

O Timsort um mergesort natural, adaptativo, estvel.


Para arrays parcialmente ordenados tem comportamento
supernatural, isto , faz menos que log2N! comparaes.
Usa uma memria auxiliar de N/2, no mximo.
Foi inventado por Tim Peters, recentemente:
http://svn.python.org/projects/python/trunk/Objects/listsort.txt.
Programado para Java por Joshua Bloch:
http://cr.openjdk.java.net/~martin/webrevs/openjdk7/tims
ort/src/share/classes/java/util/TimSort.java.html.
usado desde o Java 7 para ordenar arrays de objetos.
Ainda no percebi completamente, mas muito
interessante.

6/1/16 Algoritmos e Estruturas de Dados 3


Sorts do Java 6
public static void sort(int[] a)
The sorting algorithm is a tuned quicksort, adapted from
Jon L. Bentley and M. Douglas McIlroy's "Engineering a Sort
Function", Software-Practice and Experience, Vol. 23(11) P.
1249-1265 (November 1993). This algorithm offers
n*log(n) performance on many data sets that cause other
quicksorts to degrade to quadratic performance.
public static void sort(Object[] a)
The sorting algorithm is a modified mergesort (in which
the merge is omitted if the highest element in the low
sublist is less than the lowest element in the high sublist).
This algorithm offers guaranteed n*log(n) performance.

6/1/16 Algoritmos e Estruturas de Dados 4


Sorts do Java 7 e tambm do Java 8
public static void sort(int[] a)
The sorting algorithm is a Dual-Pivot Quicksort by
Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch. This
algorithm offers O(n log(n)) performance on many data
sets that cause other quicksorts to degrade to quadratic
performance, and is typically faster than traditional (one-
pivot) Quicksort implementations.
public static void sort(Object[] a)
The implementation was adapted from Tim Peters's list
sort for Python ( TimSort). It uses techiques from Peter
McIlroy's "Optimistic Sorting and Information Theoretic
Complexity", in Proceedings of the Fourth Annual ACM-
SIAM Symposium on Discrete Algorithms, pp 467-474,
January 1993.

6/1/16 Algoritmos e Estruturas de Dados 5


Introduo ao Timsort
O Timsort um mergesort natural.
Quer dizer que identifica os troos ordenados no array.
Na verdade, identifica os troos ascendentes e tambm os
troos estritamente descendentes.
Estes so logo invertidos localmente, tornando-se troos
ascendentes.
A fuso d-se entre o antepenltimo e o penltimo ou entre
o penltimo e ltimos troos identificados ou previamente
fundidos, de acordo com um critrio muito bem apanhado.
Para fazer a fuso, apenas o mais curto dos dois troos a
fundir copiado para a memria auxiliar.
Copia-se o mais curto porque d menos trabalho. Depois faz-se a fuso
entre o troo que ficou no array e o que foi para a memria auxiliar,
deixando o resultado no array. Mas ateno: se o troo mais curto for
o primeiro, a fuso faz-se da direita para a esquerda.

6/1/16 Algoritmos e Estruturas de Dados 6


Estratgia de fuso
Sejam A, B e C os trs ltimos troos no array, neste
momento e tambm os respetivos comprimentos.
Se A <= B+C ento funde-se A e B. Note bem: os comprimentos
Se no, se B <= C, ento funde-se B e C. dos troos so guardados
numa pilha.
Se no, vai-se buscar o prximo troo.
Nos casos em que tenha havido fuso, repete-se a tentativa de
fuso.
Este esquema garante que temos sempre A > B+C e B > C,
em cada passo do algoritmo.
Isto implica que a sequncia dos tamanhos dos troos, do fim
par o princpio cresce mais depressa do que os nmeros de
Fibonacci.
Logo, nunca haver muitos troos pendentes.
Por exemplo, Fibonacci(47) = 2971215073, que j mais do que 231 -1

6/1/16 Algoritmos e Estruturas de Dados 7


Classe MyTimsort, preliminar...
public class MyTimsort<T extends Comparable<T>> extends Sort<T>
{
public void sort(T[] a)
{
collapse(a);
}

public void collapse(T[] a) // preliminary


{
int[] stack = new int[64];
int r = 0;
int n = a.length;
int z = 0;
while (z < n) Enquanto houver mais troos...
{
int x = runTim(a, z);
stack[r++] = x;
z += x;
if (r >= 3)
r = mergeCollapse(a, z, stack, r);
}
while (r >= 2) No fim, fundir todos os troos pendentes, do fim para o princpio.
{
int b = stack[r - 2];
int c = stack[r - 1];
mergeAdjacentRuns(a, n - (b + c), b, c);
stack[r - 2] += c;
r--;
}
}
6/1/16... Algoritmos e Estruturas de Dados 8
Funo mergeCollapse
Esta o cerne da questo:
public int mergeCollapse(T[] t, int z, int[] stack, int r)
{
int result = r; Aqui t o array, z o nmero de elementos j processados
int a = stack[r - 3]; no array, stack a pilha dos comprimentos dos troos
int b = stack[r - 2]; pendentes e r o comprimento da pilha.
int c = stack[r - 1]; A funo retorna o comprimento da pilha depois das fuses
if (a <= b + c) que tiverem ocorrido.
{
mergeAdjacentRuns(t, z - (a + b + c), a, b);
stack[r - 3] += b;
stack[r - 2] = c;
result--;
if (result >= 3)
result = mergeCollapse(t, z, stack, result);
}
else if (b <= c)
{
mergeAdjacentRuns(t, z - (b + c), b, c);
stack[r - 2] += c;
result--;
if (result >= 3)
result = mergeCollapse(t, z, stack, result);
}
return result;
6/1/16 } Algoritmos e Estruturas de Dados 9
Outras funes
// compute length of ascending run starting at x
// after having reversed the run in case it was a
// strictly descending run.
public int runTim(T[] a, int x)
{
...
}

// merge runs a[x..x+n[ and a[x+n..x+n+m[ semi-locally.


public void mergeAdjacentRuns(T[] a, int x, int n, int m)
{
...
}

Outras funes surgiro na implementao destas.

6/1/16 Algoritmos e Estruturas de Dados 10


Ensaio de razo dobrada, MyTimsort
No meu computador:
$ java ... MyTimsort C
10000 0.0021
20000 0.0037 1.75
40000 0.0079 2.15
80000 0.0172 2.17
160000 0.0373 2.17
320000 0.0816 2.19
$ java ... Mergesort C
640000 0.1873 2.30 10000 0.0021
1280000 0.4325 2.31 20000 0.0040 1.88
2560000 1.0080 2.33 40000 0.0087 2.15
5120000 2.5070 2.49 80000 0.0187 2.17
160000 0.0405 2.16
320000 0.0910 2.25
640000 0.2050 2.25
1280000 0.4685 2.29
Note bem, estes ensaios so 2560000 1.0710 2.29
com arrays aleatrios. 5120000 2.6320 2.46

6/1/16 Algoritmos e Estruturas de Dados 11


Observando a pilha
R: 2 R: 27 14 4 3 2 R: 54 33 4 2 2 C: 127 10 5 R: 127 38 14 8 3 2 2
R: 2 2 C: 27 14 7 2 C: 54 33 6 2 R: 127 10 5 4 C: 127 38 14 8 5 2
R: 2 2 2 R: 27 14 7 2 2 R: 54 33 6 2 3 R: 127 10 5 4 4 R: 127 38 14 8 5 2 2
C: 4 2 C: 27 14 7 4 C: 54 33 6 5 C: 127 10 9 4 C: 127 38 14 8 5 4
R: 4 2 2 R: 27 14 7 4 2 R: 54 33 6 5 4 C: 127 19 4 C: 127 38 14 13 4
C: 6 2 R: 27 14 7 4 2 2 C: 54 33 11 4 R: 127 19 4 3 C: 127 38 27 4
R: 6 2 4 C: 27 14 7 6 2 R: 54 33 11 4 2 R: 127 19 4 3 2 R: 127 38 27 4 2
C: 8 4 C: 27 14 13 2 R: 54 33 11 4 2 2 C: 127 19 7 2 R: 127 38 27 4 2 2
R: 8 4 3 C: 27 27 2 C: 54 33 11 6 2 R: 127 19 7 2 2 C: 127 38 27 6 2
R: 8 4 3 2 C: 54 2 R: 54 33 11 6 2 2 C: 127 19 7 4 F: 127 38 27 8
C: 8 7 2 R: 54 2 2 C: 54 33 11 6 4 R: 127 19 7 4 3 F: 127 38 35
C: 15 2 C: 54 4 R: 54 33 11 6 4 3 C: 127 19 11 3 F: 127 73
R: 15 2 2 R: 54 4 2 C: 54 33 11 10 3 R: 127 19 11 3 2 F: 200
C: 15 4 R: 54 4 2 2 C: 54 33 21 3 R: 127 19 11 3 2 3
R: 15 4 3 C: 54 6 2 R: 54 33 21 3 3 C: 127 19 11 5 3
R: 15 4 3 3 R: 54 6 2 3 C: 54 33 21 6 R: 127 19 11 5 3 2 Trata-se de um
C:
R:
15 7 3
15 7 3 2
C:
R:
54
54
6 5
6 5 4
R: 54 33 21 6 4
R: 54 33 21 6 4 3
C:
R:
127
127
19
19
11 8 2
11 8 2 3 array aleatrio
R:
C:
15 7 3 2 3
15 7 5 3
C:
R:
54
54
11 4
11 4 2
C: 54 33 21 10 3
R: 54 33 21 10 3 2
C:
C:
127
127
19
19
11 8 5
19 5
com tamanho
C: 15 12 3 R: 54 11 4 2 2 R: 54 33 21 10 3 2 2 C: 127 38 5 200. O
C: 27 3 C: 54 11 6 2 C: 54 33 21 10 5 2 R: 127 38 5 3
R: 27 3 2 R: 54 11 6 2 3 R: 54 33 21 10 5 2 2 R: 127 38 5 3 2 resultado tem
R:
C:
27 3 2 2
27 5 2
C:
C:
54
54
11 6 5
17 5
C: 54 33 21 10 5 4
R: 54 33 21 10 5 4 2
C:
R:
127
127
38
38
8 2
8 2 2
157 linhas.
R: 27 5 2 2 R: 54 17 5 2 C: 54 33 21 10 9 2 C: 127 38 8 4
C: 27 5 4 R: 54 17 5 2 2 C: 54 33 21 19 2 R: 127 38 8 4 2
R: 27 5 4 3 C: 54 17 5 4 C: 54 33 40 2 R: 127 38 8 4 2 3
C: 27 9 3 R: 54 17 5 4 2 C: 54 73 2 C: 127 38 8 6 3
R: 27 9 3 2 C: 54 17 9 2 C: 127 2 C: 127 38 14 3
R: 27 9 3 2 2 R: 54 17 9 2 5 R: 127 2 2 R: 127 38 14 3 2
C: 27 9 5 2 C: 54 17 9 7 C: 127 4 R: 127 38 14 3 2 3
R: 27 9 5 2 2 R: 54 17 9 7 4 R: 127 4 3 C: 127 38 14 5 3
C: 27 9 5 4 C: 54 17 16 4 R: 127 4 3 3 R: 127 38 14 5 3 3
C: 27 14 4 C: 54 33 4 C: 127 7 3 C: 127 38 14 8 3
R: 6/1/16
27 14 4 3 R: 54 33 4 2 Algoritmos
R: 127 e7Estruturas
3 5 de Dados
R: 127 38 14 8 3 2 12
Ensaio triplo de Timsorts
MyTimsort Timsort Arrays.sort
10000 0.0021 10000 0.0047 10000 0.0026
20000 0.0037 1.74 20000 0.0032 0.69 20000 0.0031 1.21
40000 0.0079 2.14 40000 0.0069 2.15 40000 0.0068 2.18
80000 0.0171 2.17 80000 0.0150 2.19 80000 0.0145 2.15
160000 0.0373 2.18 160000 0.0331 2.20 160000 0.0325 2.23
320000 0.0833 2.23 320000 0.0739 2.23 320000 0.0719 2.21
640000 0.1880 2.26 640000 0.1693 2.29 640000 0.1643 2.29
1280000 0.4305 2.29 1280000 0.3785 2.24 1280000 0.3740 2.28
2560000 0.9780 2.27 2560000 0.8610 2.27 2560000 0.8480 2.27
5120000 2.2210 2.27 5120000 1.9430 2.26 5120000 1.9860 2.34

Quicksort duplo piv, 10000 0.0016

para referncia. 20000 0.0030 1.94 TimSort, programado


40000 0.0068 2.24
80000 0.0147 2.16 por Joshua Bloch.
160000 0.0339 2.31
320000 0.0837 2.47
640000 0.2140 2.56
Note bem, estes ensaios so 1280000 0.5010 2.34
todos com arrays aleatrios. 2560000 1.1680 2.33
5120000 2.7170 2.33
6/1/16 Algoritmos e Estruturas de Dados 13
Concluso
Os nmeros do TimSort e do Arrays.sort so muito
parecidos, o que est de acordo com o facto de o
Arrays.sort ser um TimSort.
O Quicksort com duplo piv, que o melhor do
quicksorts, menos rpido que o Timsort.
O Timsort adaptativo, e mais vantajoso ainda com
arrays parcialmente ordenados.
A diferena observada entre o Timsort e o
Quicksort com duplo piv com arrays aleatrios no
muito significativa e pode ser devida a causas
fortuitas (carga da mquina) ou a fatores no
algortmicos (gesto da memria).
6/1/16 Algoritmos e Estruturas de Dados 14
Ensaios em Java e em C
size shell quick cutoff tripart dual merge mergebu my_tim bloch arrays natural
1000 0.001 0.002 0.002 0.002 0.002 0.003 0.003 0.002 0.002 0.002 0.002
2000 0.000 0.002 0.001 0.001 0.001 0.004 0.002 0.001 0.002 0.004 0.003
4000 0.001 0.001 0.001 0.001 0.001 0.002 0.000 0.003 0.004 0.007 0.006
8000 0.001 0.002 0.001 0.002 0.002 0.003 0.001 0.006 0.007 0.010 0.004
Java

16000 0.003 0.004 0.002 0.003 0.002 0.003 0.002 0.006 0.009 0.002 0.004
32000 0.008 0.006 0.006 0.006 0.006 0.006 0.005 0.007 0.005 0.005 0.009
64000 0.018 0.012 0.012 0.014 0.011 0.012 0.010 0.014 0.011 0.010 0.019
128000 0.043 0.027 0.027 0.030 0.025 0.027 0.022 0.031 0.023 0.023 0.042
256000 0.117 0.065 0.067 0.072 0.059 0.062 0.055 0.068 0.053 0.053 0.105
512000 0.351 0.167 0.174 0.191 0.153 0.147 0.136 0.160 0.123 0.126 0.290
1024000 0.968 0.411 0.435 0.487 0.380 0.348 0.307 0.375 0.291 0.282 0.948
2048000 2.432 0.930 1.007 1.123 0.862 0.777 0.706 0.864 0.650 0.634 1.448

size shell quick cutoff tripart dual merge mergebu my_tim qsort
1000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
2000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
4000 0.001 0.001 0.000 0.001 0.001 0.001 0.001 0.001 0.000
8000 0.002 0.001 0.001 0.002 0.001 0.001 0.001 0.001 0.001 Note bem: em
16000 0.005 0.002 0.002 0.004 0.002 0.003 0.003 0.002 0.002 Java com arrays
C

32000 0.011 0.005 0.005 0.008 0.005 0.007 0.006 0.005 0.005
de Integer; em
64000 0.024 0.010 0.010 0.016 0.011 0.014 0.013 0.011 0.010
128000 0.054 0.021 0.020 0.035 0.022 0.030 0.027 0.023 0.021 C com arrays
256000 0.124 0.046 0.042 0.074 0.048 0.063 0.058 0.049 0.044 de int.
512000 0.283 0.094 0.088 0.155 0.100 0.133 0.122 0.103 0.092
1024000 0.646 0.196 0.190 0.327 0.211 0.279 0.256 0.216 0.196
2048000 1.460 0.417 0.398 0.705 0.454 0.583 0.541 0.451 0.408
4096000 3.388 0.906 0.862 1.498 0.978 1.216 1.143 0.945 0.843
6/1/16 Algoritmos e Estruturas de Dados 15
Eplogo: o bug do Timsort
Um grupo de cientistas descobriu em 2015 que o Timsort est errado.
No entanto, o erro s se manifestaria em arrays muito maiores do que os
que existem atualmente.
O erro est na ideia base da fuso:
Se A <= B+C ento funde-se A e B.
Se no, se B <= C, ento funde-se B e C.
Se no, vai-se buscar o prximo troo.
Nos casos em que tenha havido fuso, repete-se a tentativa de fuso.
Este esquema garante que temos sempre A > B+C e B > C, em cada passo do
algoritmo.
Ora, se os troos forem 120, 80, 25, 20, 30, temos 120 > 80+25 e 80 > 25;
tambm 80 > 25+20 e 25 > 20; mas 25 <= 20+30. Logo, funde-se 25 e 20.
Obtm-se 120, 80, 45, 30.
Isto significa que os comprimentos dos troos podem crescer mais
devagar do que se pensava e causar overflow no array dos comprimentos
dos troos.
Ver http://envisage-project.eu/proving-android-java-and-python-sorting-
algorithm-is-broken-and-how-to-fix-it/.
6/1/16 Algoritmos e Estruturas de Dados 16
Ainda outros algoritmos
Heapsort.
Relacionado com as filas com prioridade.
NlogN, sempre.
Introsort.
um quicksort introspetivo: quando nota que a
recursividade est a ficar muito profunda, muda
para Heapsort.
Assim, garante NLogN, sempre.

6/1/16 Algoritmos e Estruturas de Dados 17


Bucketsort
Serve para ordenar arrays de nmeros inteiros
no negativos, uniformemente distribudos
num intervalo da ordem de grandeza do
tamanho do array.
Nesse caso, tem complexidade linear.
Usa memria adicional, proporcional ao
tamanho do array.
No faz comparaes, exceto residualmente,
na fase final.

6/1/16 Algoritmos e Estruturas de Dados 18


Explicao do Bucketsort
Para ordenar um array de tamanho N, usa-se um
array de N bags.
Seja M o mximo do array, mais 1.
Percorre-se o array, colocando cada elemento x no
saco de ndice x * N / M.
Se os elementos estiverem bem distribudos, cada
saco fica com poucos elementos.
Depois, copiam-se os elementos dos sucessivos
sacos de novo para o array.
O array estar quase ordenado.
O insertionsort completa a operao.

6/1/16 Algoritmos e Estruturas de Dados 19


Classe Bucketsort
public class Bucketsort extends Sort<Integer>
{
public void sort(Integer[] a)
{
Integer top = max(a)+1;
int n = a.length;
Bag<Integer>[] buckets = Utilities.newArray(new Bag<Integer>(), n);
for (int i = 0; i < n; i++)
buckets[i] = new Bag<Integer>();
for (int i = 0; i < n; i++)
{
long p = (long) a[i] * n; Usamos long para evitar
int z = (int)(p / top);
overflow com tamanhos grandes.
buckets[z].add(a[i]);
}
int k = 0;
for (int i = 0; i < n; i++) private static int max(Integer[] a)
{ {
for (Integer x: buckets[i]) int result = Integer.MIN_VALUE;
a[k++] = x; for (int i = 0; i < a.length; i++)
{
} if (result < a[i])
new Insertionsort<Integer>().sort(a); result = a[i];
} }
... return result;
} }

6/1/16 Algoritmos e Estruturas de Dados 20


Countsort
Serve para ordenar arrays de nmeros inteiros no
negativos.
Tem complexidade linear.
No faz comparaes.
Usa um array auxiliar cujo tamanho igual ao
mximo do array a ordenar mais um.
Este array auxiliar contm, de forma acumulada, o
nmero de ocorrncias de cada valor no array a
ordenar.
Internamente, ordena copiando valores do array a
ordenar para um segundo array, guiando-se pelas
contagens presentes no array auxiliar.
No fim, copia o array auxiliar para o array original.
6/1/16 Algoritmos e Estruturas de Dados 21
Explicao do Countsort
O countsort baseia-se na seguinte observao:
Se no array a, desordenado, houver n elementos
com valor menor ou igual a a[k], ento o
elemento a[k] deve ocupar a posio de ndice n-
1 no array auxiliar, isto admitindo que no h no
array a outros elementos com o mesmo valor
que a[k].
Se houver um segundo elemento com o valor
igual a a[k], ento uma cpia do elemento a[k]
deve ocupar a posio de ndice n-2 no array
auxiliar.
E analogamente, se houver mais do que 2.
6/1/16 Algoritmos e Estruturas de Dados 22
Classe Countsort
public class Countsort extends Sort<Integer>
{
public void sort(Integer[] a)
{
Utilities.copyFrom(a, sorted(a));
}

public Integer[] sorted(Integer[] a)


{
int n = a.length;
Primeiro, usamos o array c para
Integer[] result = new Integer[n];
int m = max(a) + 1; contar as ocorrncias de cada
int[] c = new int[m]; valor do array a. Depois,
for (int i = 0; i < n; i++) acumulamos as contagens, usando
c[a[i]]++; o mesmo array.
for (int j = 1; j < m; j++)
c[j] += c[j-1];
for (int i = n-1; i >= 0; i--) O ltimo ciclo for deveras subtil.
result[c[a[i]]-- -1] = a[i]; Veja com ateno.
return result;
}
...
}
6/1/16 Algoritmos e Estruturas de Dados 23
Bitsort
O bitsort uma simplificao do countsort,
aplicvel a arrays sem duplicados.
Usa um array auxiliar, para registar a
ocorrncia de cada valor presente no array a
ordenar.
Depois, com uma passagem no array das
ocorrncias, recolhemos para o array,
ordenadamente, os valores dos elementos que
ocorrem.

6/1/16 Algoritmos e Estruturas de Dados 24


Classe Bitsort
public class Bitsort extends Sort<Integer>
{

public void sort(Integer[] a)


{
int n = a.length;
int m = max(a) + 1;
int[] c = new int[m];
for (int i = 0; i < n; i++)
Registo das ocorrncias.
c[a[i]]++;
int k = 0;;
for (int j = 0; j < m; j++)
{
assert c[j] <= 1;
if (c[j] == 1) Recolha dos valores.
a[k++] = j;
}
}
...
}
6/1/16 Algoritmos e Estruturas de Dados 25
Observaes sobre os algoritmos lineares
Os valores obtidos em testes de razo dupla no
so significativos, pois dependem muitos das
caractersticas dos arrays e so penalizados por
estarmos a usar arrays de objetos.
Com arrays grandes, a vantagem computacional
que se esperaria esbate-se devido ao consumo de
memria.
O consumo de memria ainda mais violento
numa linguagem como Java (e com arrays de
objetos).
E, com arrays pequenos, tanto faz...
6/1/16 Algoritmos e Estruturas de Dados 26
Algoritmos e Estruturas de Dados

Lio n. 18
Tabelas
Tabelas
Tabelas naturais.
Tabelas sequenciais.
Tabelas de disperso.
Tabelas de encadeamentos separados.
Tabelas de endereamento aberto.

6/1/16 Algoritmos e Estruturas de Dados 2


Interface Table<K, V>
Eis uma interface Table tpica, para tabelas sem
chaves duplicadas:
public interface Table<K, V> extends Iterable<K>
{ K o tipo das chaves;
public V get(K key); V o tipo dos valores.
public void put(K key, V value);
public void delete(K key);
public boolean has(K key);
public boolean isEmpty();
public int size();
}
A funo get a funo de acesso, por chave; a funo put acrescenta um par tabela; a funo
delete remove da tabela o par com a chave dada; a funo has indica se existe na tabela um par
com a chave dada; a funo isEmpty indica se a tabela est vazia, isto , se no tem elementos; a
funo size calcula o nmero de pares na tabela. As classes que implementarem esta interface
devem fornecer um mtodo iterator, que ser o iterador das chaves.

6/1/16 Algoritmos e Estruturas de Dados 3


Tabelas naturais, classe NaturalTable<T>
Nas tabelas naturais, as chaves so nmeros
inteiros no negativos.
Os valores so guardados num array.
O array ser redimensionado quando for
preciso.
public class NaturalTable<T>
implements Table<Integer, T>
{
private static final int DEFAULT_CAPACITY = 16;
private int size;
private T[] values;

...
}
6/1/16 Algoritmos e Estruturas de Dados 4
Construtores
Oferecemos dois construtores: um que
permite indicar a capacidade inicial, outro que
usa a capacidade fixada por defeito:
@SuppressWarnings("unchecked")
public NaturalTable(int capacity)
{
this.size = 0;
values = (T[]) new Object[capacity];
}

public NaturalTable()
{
this(DEFAULT_CAPACITY);
}

6/1/16 Algoritmos e Estruturas de Dados 5


Funo get
A funo get usa a chave passada em argumento
como ndice no array, se der; se no der, retorna null,
significando que no existe valor para aquela chave:
public T get(Integer key)
{
assert key >= 0;
T result = null;
if (key < values.length)
result = values[key];
return result;
}
Note bem: qualquer argumento no negativo vlido. Pelo
contrrio, um argumento negativo invlido sempre e causar uma
exceo. Cabe funo que chama garantir que o argumento no
negativo.
6/1/16 Algoritmos e Estruturas de Dados 6
Funo put
A funo put guarda o valor na posio do array
especificada pela chave, fazendo o array crescer, se
necessrio.
Tambm atualiza a contagem:
public void put(Integer key, T value)
{
assert key >= 0;
if (key >= values.length)
resize(Math.max(2 * values.length, key+1));
if (values[key] == null)
size++;
values[key] = value;
}
Se o valor da chave for demasiado grande haver uma exceo, por falta
de memria.
6/1/16 Algoritmos e Estruturas de Dados 7
Funo delete
A funo delete coloca null na posio indicada pela
chave e atualiza a contagem, se necessrio:
public void delete(Integer key)
{
assert key >= 0;
if (key < values.length && values[key] != null)
{
size--;
values[key] = null;
}
}

Note que a funo delete no faz o array encolher.

6/1/16 Algoritmos e Estruturas de Dados 8


Funo has, size e isEmpty
Estas trs so muito simples:
public boolean has(Integer key)
{
return get(key) != null;
}

public int size()


{
return size;
}

public boolean isEmpty()


{
return size == 0;
}

6/1/16 Algoritmos e Estruturas de Dados 9


O iterador das chaves
private class Keys implements Iterator<Integer>

Usamos a tcnica do {
private int i;
costume, mas observe public Keys()
a utilizao da funo {
i = advance(0);
advance: }

private int advance(int x)


{
int result = x;
while (result < values.length && values[result] == null)
result++;
return result;
}

public boolean hasNext()


{
return i < values.length;
}

public Integer next()


{
int result = i++;
i = advance(i);
public Iterator<Integer> iterator() return result;
{ }
return new Keys();
} public void remove()
{
throw new UnsupportedOperationException();
}
}
6/1/16 Algoritmos e Estruturas de Dados 10
Funo toString
Definimos a funo toString, para depois
podermos observar a tabela, nos testes:
public String toString()
{
String result = "";
for (int i = 0; i < values.length; i++)
if (values[i] != null)
result += i + ": " + values[i] + newLine;
result += "capacity: " + values.length;
return result;
}

A funo mostra as chaves e os valores presentes e


no fim indica a capacidade do array.

6/1/16 Algoritmos e Estruturas de Dados 11


public static void testNaturalTable(int d)

Funo de teste {
NaturalTable<String> table = new NaturalTable<String>(d);
while (!StdIn.isEmpty())
$ java ... NaturalTable 10 {
p 4 uuu String cmd = StdIn.readString();
p 7 fff if ("p".equals(cmd))
s {
4: uuu int x = StdIn.readInt();
7: fff String s = StdIn.readString();
capacity: 10 table.put(x, s);
size: 2 }
isEmpty? false else if ("g".equals(cmd))
p 100 aaa {
s int x = StdIn.readInt();
4: uuu String s = table.get(x);
7: fff StdOut.println(s);
100: aaa }
capacity: 101 else if ("d".equals(cmd))
size: 3 {
isEmpty? false int x = StdIn.readInt();
d 100 table.delete(x);
p 40 hhh }
i else if ("i".equals(cmd))
4 7 40 {
g 30 for (int x : table)
null StdOut.print(" "+ x);
g 1000 StdOut.println();
null }
g 40 else if ("s".equals(cmd))
hhh {
s StdOut.println(table);
4: uuu StdOut.println("size: " + table.size());
7: fff StdOut.println("isEmpty? " + table.isEmpty());
40: hhh }
capacity: 101 }
size: 3 }
isEmpty? false
6/1/16 Algoritmos e Estruturas de Dados 12
Tabelas sequenciais
Nas tabelas sequenciais, os pares chave-valor
so guardados numa lista.
Na nossa implementao, cada novo par
colocado cabea da lista.
A memria usada dinamicamente, justa.
Inseres e buscas tm complexidade linear.
Logo, no recomendvel que estas tabelas
sejam muito grandes.
Usaremos listas imutveis.

6/1/16 Algoritmos e Estruturas de Dados 13


Classe List<T>
List<T> a nossa classe de listas imutveis, inspirada nas listas
da programao funcional.
List<T> uma classe abstrata, da qual derivam as classes
Empty<T> e Cons<T>.
A classe Empty<T> representa listas vazias.
A classe Cons<T> representa listas no vazias.
public abstract class List<T>
{
...
}
Para no atravancar a nossa diretoria
class Empty<T> extends List<T>
{
src, colocamos as trs classes no
... mesmo ficheiro .java.
}

class Cons<T> extends List<T>


{
...
6/1/16} Algoritmos e Estruturas de Dados 14
Funes abstratas
Na classe List<T> haver funes abstratas, que sero
implementas nas classes de baixo e funes no
abstratas, programadas em termos das funes abstratas:
public abstract class List<T> A funo cons constri uma nova lista,
{ em que o primeiro elemento vale x e o
public abstract List<T> cons(T x); resto a lista objeto; a funo head
public abstract T head(); devolve objeto que est na primeira
public abstract List<T> tail(); posio; a funo tail devolve uma lista
public abstract boolean isEmpty(); formada por todos os elementos da
public abstract int size(); lista objeto, menos o primeiro.

public abstract String join(String separator);

public String toString() A funo join constri uma cadeia


{ forma pelas representaes textuais
return "[" + join(",") + "]"; dos elementos da lista separadas pelo
} separador indicado.

...
}
6/1/16 Algoritmos e Estruturas de Dados 15
Classe Empty<T> class Empty<T> extends List<T>
{
public int size()
Observe: {
return 0;
}

public T head()
{
throw new NoSuchElementException();
}

public List<T> tail()


{
throw new NoSuchElementException();
}
Claro: o nmero de elementos de
uma lista vazia zero; no tem public List<T> cons(T x)
cabea nem cauda; acrescentando {
return new Cons<T>(x, this);
um elemento, cria-se uma lista no }
vazia que tem s esse elemento; e
verdade que vazia! public boolean isEmpty()
{
return true;
}

...
6/1/16 Algoritmos e Estruturas de Dados 16
Classe Cons<T>
Esta que trabalha verdadeiramente:
class Cons<T> extends List<T>
{
private final T value;
private final List<T> next;

public Cons(T x, List<T> w)


{
value = x;
next = w;
}
O membro value, de tipo T, representa
o valor do primeiro elemento da lista,
... e o membro next, de tipo List<T>,
representa o resto da lista.

6/1/16 Algoritmos e Estruturas de Dados 17


Funes size, head, tail, isEmpty, cons
Tambm so simples:
public int size()
{ Repare nesta funo recursiva: quando tail()
return 1 + next.size(); for a lista vazia, a funo size() chamada
} ser a da classe Empty<T>, retornando 0 e
public T head()
terminando a recursividade.
{
return value;
}

public List<T> tail()


{
return next;
}

public boolean isEmpty()


{
return false;
}
Uma vez que a programao da funo
public List<T> cons(T x) cons a mesma das duas classes de
{ baixo, podamos t-la colocado na classe
return new Cons<>(x, this); de base.
}
6/1/16 Algoritmos e Estruturas de Dados 18
Funo join
Na classe Empty<T>:
public String join(String separator)
{
return "";
}

Na classe Cons<T>:
public String join(String separator)
{
String result = value.toString();
if (!next.isEmpty())
result += separator + next.join(separator);
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 19


Exerccio: funo append
Na classe List<T>:
public abstract List<T> append(List<T> other);

Na classe Empty<T>:
public List<T> append(List<T> other)
{
return other;
}

Na classe Cons<T>:
public List<T> append(List<T> other)
{
return next.append(other).cons(value);
}

6/1/16 Algoritmos e Estruturas de Dados 20


Funes has e delete
Na classe List<T>:
public abstract boolean has(T x);
public abstract List<T> delete(T x);
Na classe Empty<T>:
public boolean has(T x)
{
return false;
}

public List<T> delete(T x)


{
return this;
}

Na classe Cons<T>:
public boolean has(T x)
{ Repare que ao apagar um elemento
return value.equals(x) || next.has(x); que no existe, a funo delete
}
constri uma lista nova, igual lista
public List<T> delete(T x) objeto.
{
return value.equals(x) ? next : next.delete(x).cons(value);
}
6/1/16 Algoritmos e Estruturas de Dados 21
Interface Iterable<T>
Convm que a lista tenha um iterador:
public abstract class List<T> implements Iterable<T>
{
...
}

As classes de baixo devem fornecer a funo


iterator, a qual devolve um objeto de uma
classe privada:
...

private class Items implements Iterator<T>


{
...
}

public Iterator<T> iterator()


{
return new Items();
}

6/1/16
... Algoritmos e Estruturas de Dados 22
Classes privadas
Na classe Empty<T>: Na classe Cons<T>:
private class Items implements Iterator<T> private Cons<T> self()
{ {
return this;
public boolean hasNext() }
{
return false; private class Items implements Iterator<T>
} {
private List<T> current = self();
public T next()
{ public boolean hasNext()
throw new UnsupportedOperationException(); {
} return !current.isEmpty();
}
public void remove()
{ public T next()
throw new UnsupportedOperationException(); {
} T result = current.head();
} current = current.tail();
return result;
}

public void remove()


{
throw new UnsupportedOperationException();
}
}
Repare na tcnica usada para nos referirmos
ao objeto da classe exterior na classe
interna, por meio da funo privada self.
6/1/16 Algoritmos e Estruturas de Dados 23
Funes de teste
public static void testListInteger() public static void testListString()
{ {
List<Integer> w = new Empty<Integer>(); List<String> w = new Empty<String>();
while (!StdIn.isEmpty()) while (!StdIn.isEmpty())
{ {
String cmd = StdIn.readString(); String cmd = StdIn.readString();
if ("p".equals(cmd)) if ("p".equals(cmd))
{ {
int x = StdIn.readInt(); String x = StdIn.readString();
w = w.cons(x); w = w.cons(x);
} }
else if ("d".equals(cmd)) else if ("d".equals(cmd))
{ {
int x = StdIn.readInt(); String x = StdIn.readString();
w = w.delete(x); w = w.delete(x);
} }
else if ("h".equals(cmd)) else if ("h".equals(cmd))
{ {
int x = StdIn.readInt(); String x = StdIn.readString();
boolean b = w.has(x); boolean b = w.has(x);
StdOut.println(b); StdOut.println(b);
} }
else if ("i".equals(cmd)) else if ("i".equals(cmd))
{ {
for (int x : w) for (String x : w)
StdOut.print(" "+ x); StdOut.print(" "+ x);
StdOut.println(); StdOut.println();
} }
else if ("s".equals(cmd)) else if ("s".equals(cmd))
{ {
StdOut.println(w); StdOut.println(w);
StdOut.println("size: " + w.size()); StdOut.println("size: " + w.size());
StdOut.println("isEmpty? " + w.isEmpty()); StdOut.println("isEmpty? " + w.isEmpty());
} }
} }
} 6/1/16 } de Dados
Algoritmos e Estruturas 24
Classe ListTable<K,V>
As tabelas sequenciais guardam pares chave-valor.
Por isso, a classe List<T> no aplicvel diretamente.
Precisamos de uma classe ListTable<K, V>, imutvel:
public abstract class ListTable<K, V> implements Iterable<K>
{
public abstract ListTable<K, V> cons(K k, V v);
public abstract K head();
public abstract ListTable<K, V> tail(); A funo get devolve o valor
public abstract V get(K k); associado chave dada, ou null, se
public abstract ListTable<K, V> delete(K k); a chave no existir.
public abstract boolean isEmpty();
public abstract int size();

public boolean has(K key)


{
return get(key) != null;
}

public abstract String join(String separator);

public String toString()


{
return "[" + join(",") + "]";
}
6/1/16 Algoritmos e Estruturas de Dados 25
...
Classe ListTableEmpty<K,V>
Usamos o mesmo esquema, com uma classe derivada para
listas vazias e outra para listas no vazias:
class ListTableEmpty<K, V> extends ListTable<K, V> ...
{
public int size() public ListTable<K, V> delete(K k)
{ {
return 0; return this;
} }

public K head() public V get(K k)


{ {
throw new NoSuchElementException(); return null;
} }

public ListTable<K, V> tail() public Iterator<K> iterator()


{ {
throw new NoSuchElementException(); return new Keys();
} }

public ListTable<K, V> cons(K k, V v) private class Keys implements Iterator<K>


{ {
return new ListTableCons<>
(k, v, new ListTableEmpty<>()); public boolean hasNext()
} {
return false;
public boolean isEmpty() }
{
return true; public K next()
} {
throw new UnsupportedOperationException();
public String join(String separator) }
{
return ""; public void remove()
} {
throw new UnsupportedOperationException();
... }
}
6/1/16 Algoritmos e Estruturas
}
de Dados 26
Classe ListTableCons<K, V>
class ListTableCons<K, V> extends ListTable<K, V> ...
{
private final K key; public ListTable<K, V> delete(K k)
private final V value; {
private final ListTable<K, V> next; return key.equals(k) ? tail() : next.delete(k).cons(key, value);
}
public ListTableCons(K key, V value, ListTable<K, V> w)
{ public V get(K k)
this.key = key; {
this.value = value; return key.equals(k) ? value : next.get(k);
this.next = w; }
}
public Iterator<K> iterator()
public int size() {
{ return new Keys();
return 1 + next.size(); }
}
private ListTableCons<K, V> self()
public K head() {
{ return this;
return key; }
}
private class Keys implements Iterator<K>
public ListTable<K, V> tail() {
{ private ListTable<K, V> current = self();
return next;
} public boolean hasNext()
{
public boolean isEmpty() return !current.isEmpty();
{ }
return false;
} public K next()
{
public ListTable<K, V> cons(K k, V v) K result = current.head();
{ current = current.tail();
return new ListTableCons<>(k, v, this); return result;
} }

public String join(String separator) public void remove()


{ {
String result = "<" + key.toString() + " " + throw new UnsupportedOperationException();
value.toString() + ">"; }
if (!next.isEmpty()) }
result += separator + next.join(separator);
return result; }
}
...
6/1/16 Algoritmos e Estruturas de Dados 27
Classe SequentialTable<K, V>
A classe ListTable<K, T> quase uma tabela
sequencial.
Na verdade, a classe SequentialTable<K,V>
contm um membro de dados de tipo
ListTable<K, V> e ajusta a interface de maneira
a respeitar Table<K, V>:
public class SequentialTable<K, V> implements Table<K, V>
{
private ListTable<K, V> items = new ListTableEmpty<>();
private int size = 0;
Acrescentamos um membro de dados
... para o tamanho da tabela, para evitar o
} clculo da funo size da lista, que tem
complexidade linear.

6/1/16 Algoritmos e Estruturas de Dados 28


Funes get, has, isEmpty, toString e iterator
Estas apenas chamam as funes homnimas da lista:
public V get(K key)
{
return items.get(key);
}

public boolean has(K key)


{
return items.has(key);
}

public boolean isEmpty()


{
return items.isEmpty();
}

public String toString()


{
return items.toString();
}

public Iterator<K> iterator()


{
return items.iterator();
6/1/16
} Algoritmos e Estruturas de Dados 29
Funes put, delete e size
public void put(K key, V value)
{
delete(key);
items = items.cons(key, value);
size++; Para evitar duplicados, antes de
} acrescentar, a funo put apaga o par que
tem a chave dada, se houver.
public int size()
{ A funo size devolve o valor do
return size; respetivo membro de dados.
}

public void delete(K key)


{
if (items.has(key)) Na funo delete, se a chave no existir,
{ fica tudo na mesma.
items = items.delete(key);
size--;
}
}
6/1/16 Algoritmos e Estruturas de Dados 30
Funo de teste
anloga s anteriores:
public static void testSequentialTable() ...
{ else if ("h".equals(cmd))
SequentialTable<Integer, String> t = {
new SequentialTable<>(); int x = StdIn.readInt();
while (!StdIn.isEmpty()) boolean b = t.has(x);
{ StdOut.println(b);
String cmd = StdIn.readString(); }
if ("p".equals(cmd)) else if ("i".equals(cmd))
{ {
int x = StdIn.readInt(); for (int x : t)
String s = StdIn.readString(); StdOut.print(" "+ x);
t.put(x, s); StdOut.println();
} }
else if ("g".equals(cmd)) else if ("s".equals(cmd))
{ {
int x = StdIn.readInt(); StdOut.println(t);
String s = t.get(x); StdOut.println("size: " + t.size());
StdOut.println(s); StdOut.println("isEmpty? " + t.isEmpty());
} }
else if ("d".equals(cmd)) }
{ }
int x = StdIn.readInt();
t.delete(x);
}
...

6/1/16 Algoritmos e Estruturas de Dados 31


Classe SequentialTable<T> (alternativa)
Mais convencionalmente, usaramos uma lista ligada,
mutvel, para guardar os pares chave-valor:
public class SequentialTable<K, V>
extends Table<K, V> A lista representada por uma
implements Iterable<K>
{ referncia para o primeiro n.
private Node head = null;
private int size = 0;
A lista mantm a contagem do
private class Node nmero de elementos.
{
private final K key;
private V value; A classe interna Node representa
private Node next; os ns da lista ligada.
public Node(K key, V value, Node next)
{
this.key = key; Note bem: o construtor constri
this.value = value; uma nova lista a partir de um par
this.next = next;
} chave-valor e de um n, o qual
} normalmente ser a cabea de
...
}
uma lista pr-existente.
6/1/16 Algoritmos e Estruturas de Dados 32
Funo get (alt.)
A funo get realiza uma busca linear na lista.
Eis a formulao iterativa padro:
public V get(K key)
{
V result = null;
Node x = head;
while (x != null && !key.equals(x.key))
x = x.next;
if (x != null)
result = x.value;
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 33


Funo delete (alt.)
Remover um n de uma lista ligada um clssico da
programao:
public void delete(K key)
{
if (head != null) Se a lista for vazia, nada a fazer.
{
if (key.equals(head.key)) Se a chave estiver cabea...
{
head = head.next;
size--;
}
else Seno procura-se um n cujo seguinte
{
Node x = head;
tenha a chave dada.
while (x.next != null && !key.equals(x.next.key))
x = x.next;
if (x.next != null) Se tal n existir, liga-se ao seguinte do
{ seguinte, assim efetivamente eliminando
x.next = x.next.next;
o seguinte, que era o tal que tinha a
size--;
} chave dada.
} Se no existir, a lista fica na mesma.
}
}
6/1/16 Algoritmos e Estruturas de Dados 34
Funo put (alt.)
Como no queremos chaves duplicadas, temos de
procurar primeiro a chave, no v ela j estar
presente.
mais prtico apagar o elemento com a chave dada,
se houver, e depois acrescentar o novo par chave-
valor num n cabea:
public void put(K key, V value)
{
delete(key);
head = new Node(key, value, head);
size++;
} Repare como se faz para
acrescentar um n cabea.

6/1/16 Algoritmos e Estruturas de Dados 35


Funo has, size e isEmpty (alt.)
Estas trs no tm novidade:
public boolean has(K key)
{
return get(key) != null;
}

public int size()


{
return size;
}

public boolean isEmpty()


{
return head == null;
}

6/1/16 Algoritmos e Estruturas de Dados 36


O iterador das chaves (alt.)
O iterador guarda uma referncia para o n corrente:
private class KeysIterator implements Iterator<K>
{
private Node i = head;

public boolean hasNext()


{
return i != null; public Iterator<Integer> iterator()
} {
return new KeysIterator();
public K next() }
{
K result = i.key; public Iterable<Integer> keys()
{
i = i.next;
return this;
return result; }
}

public void remove()


{
throw new UnsupportedOperationException();
}
}
6/1/16 Algoritmos e Estruturas de Dados 37
Funo toString (alt.)
A lista aparece entre parntesis retos e cada para
aparece entre parntesis angulosos.
public String toString()
{
String result = "[";
Node x = head;
while (x != null)
{
result += "<" + x.key + " " + x.value + ">";
x = x.next;
}
result += "]";
return result; Neste caso, omitimos a vrgula entre cada
dois pares chave-valor consecutivos, o
}
que simplifica a programao...

6/1/16 Algoritmos e Estruturas de Dados 38


Algoritmos e Estruturas de Dados

Lio n. 19
Tabelas de disperso, encadeamentos separados
Tabelas de disperso
Tabelas naturais.
Tabelas sequenciais.
Tabelas de disperso
Tabelas de encadeamentos separados.
Tabelas de endereamento aberto.

6/1/16 Algoritmos e Estruturas de Dados 2


Tabelas de fantasia
Na verdade, tudo no computador so nmeros.
Portanto, nos casos em que as chaves no so do
tipo int ou Integer (ou de um outro tipo para
nmeros inteiros), poderamos facilmente
transform-las em nmeros inteiros, de maneira
injetiva, por meio de uma funo h, que, por exemplo,
calculasse o valor numrico da representao binria
da chave.
Essa funo h injetiva, pois duas chaves diferentes
tero representao binria diferente e, portanto,
imagem diferente pela funo h.
S que, em geral, rapidamente obteramos valores
numricos para as chaves que ultrapassam a
capacidade de memria dos computadores atuais.
6/1/16 Algoritmos e Estruturas de Dados 3
Tabelas de disperso
As tabelas de disperso so uma aproximao s tabelas de
fantasia, onde cada valor guardado numa posio calculada
por uma funo injetiva h do conjunto das chaves sobre o
conjuntos dos nmeros naturais.
A aproximao consiste em renunciar injetividade da funo
h, resolvendo ad hoc os casos em que surjam chaves com a
mesma imagem pela funo h.
Alis, na prtica, para os tipos fornecidos pelo Java, no temos
de programar a tal funo h fictcia, que transformaria a
representao binria da chave num nmero inteiro; em vez
disso usamos logo a funo hashCode, disponvel em todas as
classes (a qual no ser injetiva, em geral).
A partir da funo hashCode na classe da chave,
programaremos em cada caso a funo hash, que far as vezes
da fantasiosa funo h.
6/1/16 Algoritmos e Estruturas de Dados 4
Mtodo hashCode
Todas as classes do Java vm equipadas com o
mtodo hashCode.
Nas classes Integer e String, por exemplo, o mtodo
utilizvel diretamente.
Em classes definidas no programa, convm redefinir o
mtodo hashCode, porque a funo herdada da
classe Object apenas devolve o endereo do objeto,
o que normalmente no serve para as tabelas.
A funo hashCode redefinida deve ser programada
de maneira a dispersar bem as chaves.
Isso uma arte.
Ver http://stackoverflow.com/questions/113511/best-implementation-for-
hashcode-method, que descreve o mtodo proposto por Joshua Bloch, no livro
Effective Java.
6/1/16 Algoritmos e Estruturas de Dados 5
Funo de disperso
Cada tabela de disperso ter uma funo privada
hash, programada em termos da funo hashCode da
classe das chaves.
Ao acrescentar um par chave-valor, aplicamos a
funo hash chave, para calcular a posio em que o
par chave-valor ser guardado, num array interno da
tabela.
Para procurar um valor, dada a chave, aplicamos a
funo hash chave e vamos buscar o valor ao array,
na posio calculada.
Mas pode acontecer que ao acrescentar um par
chave-valor, a posio calculada j esteja ocupada,
porque anteriormente outra chave deu o mesmo
resultado com a funo hash.
6/1/16 Algoritmos e Estruturas de Dados 6
Colises
Uma coliso uma situao em que a posio
calculada pela funo hash para um novo par
chave-valor j est ocupada.
uma situao inevitvel, em geral.
H vrias tcnicas para resolver as colises.
Cada uma delas d origem a um tipo de
tabelas de disperso.
Vamos estudar duas dessas tcnicas:
Tabelas de encadeamentos separados.
Tabelas de endereamento aberto.

6/1/16 Algoritmos e Estruturas de Dados 7


Tabelas de encadeamentos separados
Nas tabelas de encadeamentos separados, os
pares chave-valor so guardados num array de
tabelas sequenciais.
Cada par guardado na tabela cujo ndice
calculado pela funo hash.
Idealmente, cada tabela s teria um par (ou
zero pares).
Mas, na presena de colises, ter vrios pares.
Havendo N tabelas sequenciais, a funo hash
meramente calcula o resto da diviso do
resultado do mtodo hashCode por N.
6/1/16 Algoritmos e Estruturas de Dados 8
Classe SeparateChaining<K, V>
Usaremos uma tabela de dimenso fixa,
estabelecida no construtor.
A dimenso o comprimento do array de
tabelas sequenciais.
public class SeparateChaining<K, V>
implements Table<K, V>
{
private static final int DEFAULT_DIMENSION = 997;
private int size;
private final int dimension;
private SequentialTable<K, V>[] st;
...
}

6/1/16 Algoritmos e Estruturas de Dados 9


Construtores
Programamos um construtor por defeito, que
usa a dimenso dada por defeito, e um outro,
que permite fixar a dimenso:
@SuppressWarnings("unchecked")
public SeparateChaining(int dimension)
{
this.dimension = dimension; Ateno!
this.size = 0;
st = (SequentialTable<K, V>[]) new SequentialTable[dimension];
for (int i = 0; i < dimension; i++)
st[i] = new SequentialTable<K, V>();
}

public SeparateChaining()
{
this(DEFAULT_DIMENSION);
}

6/1/16 Algoritmos e Estruturas de Dados 10


Funo hash
Observe:
private int hash(K k)
{
return (k.hashCode() & 0x7fffffff) % dimension;
}
Note que, em geral, a expresso x & 0x7fffffff
resulta num valor cuja representao binria
igual de x, exceto que o primeiro bit zero.
Com efeito, o valor 0x7fffffff tem como
representao binria um zero seguido de 31 uns.
Isto importante para garantir que o primeiro
operando do % no negativo!
6/1/16 Algoritmos e Estruturas de Dados 11
Funo get
simples mas sofisticada:
public V get(K key)
{
return st[hash(key)].get(key);
}

6/1/16 Algoritmos e Estruturas de Dados 12


Funo put
Tambm no complicada, mas trabalha um
pouco mais para acertar a contagem:
public void put(K key, V value)
{
int h = hash(key);
int s = st[h].size();
st[h].put(key, value);
size += st[h].size() - s;
}

6/1/16 Algoritmos e Estruturas de Dados 13


Funo delete
parecida:
public void delete(K key)
{
int h = hash(key);
int s = st[h].size();
st[h].delete(key);
size += st[h].size() - s;
}

6/1/16 Algoritmos e Estruturas de Dados 14


Funes has, size e isEmpty
Estas trs so deveras elementares:
public boolean has(K key)
{
return get(key) != null;
}

public int size()


{
return size;
}

public boolean isEmpty()


{
return size == 0;
}
6/1/16 Algoritmos e Estruturas de Dados 15
O iterador das chaves
Criamos uma bag com as chaves todas e
devolvemos o iterador da bag:
public Iterator<K> iterator()
{
Bag<K> b = new Bag<>();
for (int i = 0; i < dimension; i++)
for (K k : st[i])
b.add(k);
return b.iterator();
}

Exerccio: programa o iterador recorrendo diretamente


aos iteradores das tabelas sequenciais.

6/1/16 Algoritmos e Estruturas de Dados 16


Funo toString
O resultado uma sequncia de linhas, uma
para cada tabela sequencial:
public String toString()
{
String result = "";
for (int i = 0; i < dimension; i++)
result += i + ": " + st[i] + newLine;
return result;
}
Recorde:
private static String newLine = System.getProperty("line.separator");

6/1/16 Algoritmos e Estruturas de Dados 17


Funo de teste
semelhante s anteriores:
public static void testSeparateChaining(int d)
{
SeparateChaining<Integer, String> ht = new SeparateChaining<>(d);
while (!StdIn.isEmpty())
{ public static void main(String[] args)
String cmd = StdIn.readString(); {
if ("p".equals(cmd)) // put int d = 7;
{ if (args.length > 0)
... d = Integer.parseInt(args[0]);
} testSeparateChaining(d);
else if ("g".equals(cmd)) // get }
{
...
}
else if ("d".equals(cmd)) // delete
{
...
}
else if ("i".equals(cmd)) // iterator
{
...
}
else if ("s".equals(cmd)) // toString
{
...
}
}
6/1/16 } Algoritmos e Estruturas de Dados 18
Testando
p 701 yyy d 77
p 8 ttt g 77
p 40 mmm null
s d 22
$ java SeparateChaining 0: [<77 www>] g 22
s 1: [<8 ttt>,<701 yyy>,<22 ooo>] null
0: [] 2: [] s
1: [] 3: [] 0: []
2: [] 4: [] 1: [<8 ttt>,<701 yyy>]
3: [] 5: [<40 mmm>,<5 uuu>] 2: []
4: [] 6: [] 3: []
5: [] size: 6 4: []
6: [] isEmpty? false 5: [<40 mmm>,<5 uuu>]
size: 0 g 22 6: []
isEmpty? true ooo size: 4
p 5 uuu g 5 isEmpty? false
p 22 ooo uuu p 70000 kkk
p 77 www d 800 p 171 sss
s s p 2103 qqq
0: [<77 www>] 0: [<77 www>] s
1: [<22 ooo>] 1: [<8 ttt>,<701 yyy>,<22 ooo>] 0: [<70000 kkk>]
2: [] 2: [] 1: [<8 ttt>,<701 yyy>]
3: [] 3: [] 2: []
4: [] 4: [] 3: [<2103 qqq>,<171 sss>]
5: [<5 uuu>] 5: [<40 mmm>,<5 uuu>] 4: []
6: [] 6: [] 5: [<40 mmm>,<5 uuu>]
size: 3 size: 6 6: []
isEmpty? false isEmpty? false size: 7
i i isEmpty? false
77 22 5 77 8 701 22 40 5

6/1/16 Algoritmos e Estruturas de Dados 19


Observaes
Uma tabela de disperso de encadeamentos
separados com dimenso N N vezes mais
rpida do que a tabela sequencial correspon-
dente.
Em situaes tpicas, cada tabela sequencial na
tabela de disperso tem size / dimension
elementos, mais ou menos.
Se tivermos uma boa estimativa do tamanho
que a tabela atingir, podemos dimensionar o
array de tabelas sequenciais em conformidade,
de maneira a garantir inseres, buscas e
eliminaes muito rpidas.
6/1/16 Algoritmos e Estruturas de Dados 20
Valor da dimenso
Em geral, usamos nmeros primos para a dimenso
do array.
Isto porque a dimenso usada como divisor no
clculo da funo de hash.
Contraexemplo: a chave representa a hora do dia, no
formato hhmmss, por exemplo 142034. Se tivssemos
uma tabela com, por exemplo, dimenso 1000, as
tabelas sequenciais 60, 61, ..., 99, 160, ..., 199, 260, ...,
299 e muitas outras estavam condenadas a ficar
vazias.
Nesse caso seria prefervel usar 997, que um
nmero primo, em vez de 1000, que evita esse
fenmeno.
6/1/16 Algoritmos e Estruturas de Dados 21
Algoritmos e Estruturas de Dados

Lio n. 20
Tabelas de disperso, endereamento aberto
Tabelas de disperso, de novo
Tabelas naturais.
Tabelas sequenciais.
Tabelas de encadeamentos separados.
Tabelas de endereamento aberto.

6/1/16 Algoritmos e Estruturas de Dados 2


Tabelas de endereamento aberto
Nas tabelas de endereamento aberto, os pares chave-valor
so guardados em dois arrays paralelos, um para as chaves e
outro para os valores.
Cada chave guardada no array das chaves na posio cujo
ndice o resultado da funo hash.
Cada valor guardado no array dos valores, na posio
paralela da chave (isto , na posio com o mesmo ndice).
Sendo N o comprimento de cada um dos arrays, a funo
hash meramente calcula o resto da diviso do resultado do
mtodo hashCode por N.
A capacidade das tabelas de endereamento aberto est
limitada ao comprimento dos arrays.
Na prtica, no convm que o tamanho ultrapasse cerca de
75% do comprimento dos arrays.

6/1/16 Algoritmos e Estruturas de Dados 3


Classe Hashtable<T>
Usaremos uma tabela de redimensionvel, em que
em cada momento a capacidade ser um nmero
primo:
public class HashTable<K, V> implements Table<K, V>
{
private int size;
private int capacity;
private K[] keys;
private V[] values;

private static int[] primes = {


17, 37, 79, 163, 331,
673, 1361, 2729, 5471, 10949,
21911, 43853, 87719, 175447, 350899,
701819, 1403641, 2807303, 5614657,
11229331, 22458671, 44917381, 89834777, 179669557
};
...
}
6/1/16 Algoritmos e Estruturas de Dados 4
Construtores
O construtor pblico no tem argumentos e cria
uma tabela com capacidade 17; o construtor privado
usado internamente:
@SuppressWarnings("unchecked")
// Always use a prime number for the capacity
private HashTable(int x) // yes, this is private
{
this.capacity = primes[x]; O argumento x representa
this.size = 0; o nmero de vezes que a
keys = (K[]) new Object[capacity]; tabela ter crescido. De
values = (V[]) new Object[capacity]; cada vez, a nova capacidade
resizeCount = x; prximo nmero primo
} na tabela.

public HashTable()
{
this(0); // default capacity is primes[0], i.e., 17.
}
6/1/16 Algoritmos e Estruturas de Dados 5
Funo get
Procuramos a chave circularmente a partir da
posio de disperso (isto , a partir do ndice
calculado pela funo hash).
A busca quando chegamos a uma posio livre ou
quando encontramos a chave:
public V get(K key)
{
int i = hash(key);
while (keys[i] != null && !key.equals(keys[i]))
i = (i+1) % capacity;
return values[i];
}
Note bem: se houver bastantes posies livres e se estas estiverem bem
distribudas, as buscas sero curtas. por isso (isto , para garantir que as buscas
so curtas) que no convm deixar os arrays encher muito.
6/1/16 Algoritmos e Estruturas de Dados 6
Funo privada putBasic
Se no for preciso redimensionar, comea como a
funo get, procurando a chave.
Se encontrar, reafeta o valor; se no, acrescenta a
chave na posio livre e o valor na posio
correspondente:
private void putBasic(K key, V value)
{
int i = hash(key);
while (keys[i] != null && !key.equals(keys[i]))
i = (i+1) % capacity;
if (keys[i] == null)
{
size++;
keys[i] = key;
}
values[i] = value;
}
6/1/16 Algoritmos e Estruturas de Dados 7
Funo put
Ser preciso redimensionar quando o fator de
carga maior do que o mximo fator de carga
permitido:
public void put(K key, V value)
{
if (loadFactor() >= maxLoadFactor)
{
assert resizeCount + 1 < primes.length;
// if it fails, it fails...
resize(resizeCount + 1);
}
putBasic(key, value);
}
6/1/16 Algoritmos e Estruturas de Dados 8
Fator de carga
O fator de carga o quociente do tamanho
pela capacidade:
public double loadFactor()
{
return (double) size / capacity;
}
claro que quanto maior for o fator de carga,
mais trabalho ter a tabela, na funo get e na
funo put.

6/1/16 Algoritmos e Estruturas de Dados 9


Fator de carga mximo
Cada tabela conhece o seu fator de carga mximo:
public class HashTable<K, V> implements Table<K, V>
{
...
public final double DEFAULT_MAX_LOAD_FACTOR = 0.5;
private double maxLoadFactor = DEFAULT_MAX_LOAD_FACTOR;
...

Por defeito 50%, mas pode ser mudado e


consultado:
public double maxLoadFactor()
{
return maxLoadFactor;
}

public void setMaxLoadFactor(double x)


{
assert 0.1 <= x && x <= 0.9; No deixaremos que o fator de carga
maxLoadFactor = x; seja muito grande ou muito pequeno.
6/1/16 } Algoritmos e Estruturas de Dados 10
Funo delete
A nossa classe no permite eliminar, nesta fase.
Eliminar possvel, mas mais trabalhoso.
Note que no basta colocar a null a posio onde
est a chave a eliminar, pois isso poderia quebrar uma
sequncia de chaves colididas.
public void delete(K key)
{
throw new UnsupportedOperationException();
}

6/1/16 Algoritmos e Estruturas de Dados 11


Funes has, size, isEmpty, hash
So idnticas s da classe SeparateChaining:
public boolean has(K key)
{
return get(key) != null;
}

public int size()


{
return size;
}

public boolean isEmpty()


{
return size == 0;
}

private int hash(K k)


{
return (k.hashCode() & 0x7fffffff) % capacity;
}

6/1/16 Algoritmos e Estruturas de Dados 12


O iterador das chaves
Usamos a tcnica das tabelas naturais:
private class Keys implements Iterator<K>
{
private int i;

public Keys()
{
i = advance(0);
}

private int advance(int x)


{
int result = x;
while (result < keys.length && keys[result] == null)
result++;
return result;
}
public Iterator<K> iterator()
public boolean hasNext()
{ {
return i < keys.length; return new Keys();
} }
public K next()
{
K result = keys[i++];
i = advance(i);
return result;
}

...
6/1/16 } Algoritmos e Estruturas de Dados 13
O iterador dos valores
Por simetria, acrescentamos o iterador dos valores:
private class Values implements Iterator<V>
{
private int i;

public Values()
{ public Iterator<V> values()
i = advance(0); {
}
return new Values();
private int advance(int x) }
{
int result = x;
while (result < keys.length && keys[result] == null)
result++;
Para normalizar a nomenclatura,
return result; juntamos uma funo keys que
}
d o iterador da chaves (que
public boolean hasNext() tambm dado pela funo
{
return i < keys.length; iterator):
}
public Iterator<K> keys()
public V next()
{
{
V result = values[i++]; return new Keys();
i = advance(i); }
return result;
}

...
6/1/16
} Algoritmos e Estruturas de Dados 14
Funo toString
O resultado uma sequncia de linhas, uma para
cada posio nos dois arrays:
public String toString()
{
String result = "";
for (int i = 0; i < capacity; i++)
{
result += i + ":";
if (keys[i] != null)
result += " " + keys[i] + " " + values[i];
result += newLine;
}
return result;
}

Recorde:
private static String newLine = System.getProperty("line.separator");

6/1/16 Algoritmos e Estruturas de Dados 15


Funo de teste
semelhante s anteriores:
public static void testHashTable()
{
HashTable<Integer, String> ht = new HashTable<>();
while (!StdIn.isEmpty())
{
String cmd = StdIn.readString();
if ("p".equals(cmd))
...
else if ("g".equals(cmd))
...
// else if ("d".equals(cmd))
No h comando d porque a tabela no
// ... implementa o mtodo delete.
else if ("i".equals(cmd))
{ Aqui mostra o iterador das chave e o iterador
...
} dos valores.
else if ("s".equals(cmd))
{
StdOut.print(ht);
StdOut.println("size: " + ht.size());
StdOut.println("isEmpty? " + ht.isEmpty());
StdOut.printf("load factor: %.3f", ht.loadFactor());
StdOut.println();
}
}
}

6/1/16 Algoritmos e Estruturas de Dados 16


Testando
s i
0: 2 172 20 10
1: aaa sss vvv bbb
2: 2 aaa p 180 uuu
$ java ... HashTable A 3: 172 sss p 197 vvv
s 4: 20 vvv s
0: 5: 0:
1: 6: 1:
2: 7: 2: 2 aaa
3: 8: 3: 172 sss
4: 9: 4: 20 vvv
5: 10: 10 bbb 5:
6: 11: 6:
7: 12: 7:
8: 13: 8:
9: 14: 9:
10: 15: 10: 10 bbb
11: 16: 11: 180 uuu
12: size: 4 12: 197 vvv
13: isEmpty? false 13:
14: load factor: 0.235 14:
15: g 2 15:
16: aaa 16:
size: 0 g 172 size: 6
isEmpty? true sss isEmpty? false
load factor: 0.000 g 20 load factor: 0.353
p 2 aaa vvv i
p 10 bbb g 10 2 172 20 10 180 197
p 172 sss bbb aaa sss vvv bbb uuu vvv
p 20 vvv g 8
null
Observamos vrias colises mas ainda
no houve redimensionamento.
6/1/16 Algoritmos e Estruturas de Dados 17
Propriedade fundamental
do endereamento aberto
Numa tabela de disperso com endereamento
aberto, em que as chaves estejam bem
dispersas, o nmero mdio de passos na funo
get e na funo put depende apenas do fator de
carga.

Isto significa que o desempenho de uma tabela de disperso


de endereamento aberto no depende do tamanho da tabela,
o que uma circunstncia notvel. Depende apenas do
quociente do tamanho pela capacidade.

6/1/16 Algoritmos e Estruturas de Dados 18


Desempenho das tabelas de disperso
Se o fator de carga for f, o nmero mdio de passos
na funo get quando a chave no existe 1/(1-f).
12.00

10.00
nmero de passos

8.00

6.00

4.00

2.00

0.00
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1

fator de carga
Isto d uma medida do Claramente, no devemos
desempenho no pior caso. deixar que o fator de carga
ultrapasse 80%, no mximo.
6/1/16 Algoritmos e Estruturas de Dados 19
Redimensionamento
A funo put redimensiona automaticamente:
public void put(K key, V value)
{
if (loadFactor() >= maxLoadFactor)
{
assert resizeCount + 1 < primes.length;
// if it fails, it fails...
resize(resizeCount + 1);
}
putBasic(key, value);
}

Eis a funo resize:


private void resize(int m)
{
HashTable<K, V> t = new HashTable<>(m);
for (int i = 0; i < capacity; i++)
if (keys[i] != null)
t.put(keys[i], values[i]); Primeiro cria uma segunda tabela,
capacity = t.capacity; depois insere l tudo; finalmente
keys = t.keys; rouba-lhe os arrays. (A
values = t.values; capacidade igual a primes[m]).
resizeCount = m;
6/1/16
} Algoritmos e Estruturas de Dados 20
p 51 qqq

Testando o redimensionamento
s
0:
1:
2: 2 aaa
3:
4:
5:
Prosseguimos a p 1700 mmm
s
6:
7: 340 zzz
experincia anterior, 0: 340 zzz 8:
numa altura em que o 1: 1700 mmm 9:
10: 10 bbb
2: 2 aaa
tamanho era 6: 3: 172 sss 11:
4: 20 vvv 12: 197 vvv
5: 13:
p 23 ttt 14: 51 qqq
6: 23 ttt
p 340 zzz 15:
7: 16:
s
8: 17:
0: 340 zzz
9: 18:
1:
10: 10 bbb 19:
2: 2 aaa
11: 180 uuu 20: 20 vvv
3: 172 sss 21:
12: 197 vvv
4: 20 vvv 22:
13:
5: 23: 23 ttt
14:
6: 23 ttt 24: 172 sss
15:
7: 25:
16:
8: 26:
size: 9 27:
9:
isEmpty? false 28:
10: 10 bbb
load factor: 0.529 29:
11: 180 uuu
30:
12: 197 vvv
13: Aqui o fator de carga 31:
32: 180 uuu
14: ultrapassou o limite. No 33:
15:
16: prximo put haver 34:
35: 1700 mmm
size: 8 redimensionamento. 36:
isEmpty? false size: 10
load factor: 0.471 isEmpty? false
load factor: 0.270
6/1/16 Algoritmos e Estruturas de Dados 21
Aglomerao primria
Considere, como exemplo, uma tabela com capacidade 7,
inicialmente vazia. Ao chegar a primeira chave, a probabilidade
de ela vir a preencher a posio 3, por exemplo, igual de
ela preencher qualquer outra posio, ou seja 1/7.
Suponhamos que ela preenche a posio 3.
Agora chega a segunda chave. A probabilidade de esta vir a
preencher a posio 3 zero, porque a posio 3 j est
ocupada. Mas a probabilidade de vir a preencher a posio 4
2/7, a soma da probabilidade de a posio de disperso
calculada ser 3 com a probabilidade de ser 4.
Quer dizer, a probabilidade de uma nova chave ficar a seguir a
uma posio ocupada maior do que a probabilidade mdia.
Logo, h uma certa tendncia indesejvel para as chaves se
aglomerarem no vector das chaves, em vez de terem uma
distribuio uniforme.
6/1/16 Algoritmos e Estruturas de Dados 22
Dupla disperso
Evita-se a aglomerao primria usando a dupla disperso: em
vez de tentar apanhar uma posio livre linearmente na tabela
incrementa-se de um valor obtido por uma segunda funo de
disperso.
Agora, na funo get, por exemplo, avana-se no de 1 em 1
mas de h2 em h2, representando por h2 o resultado da
segunda funo de disperso:
public V get(K key)
{
int i = hash(key);
while (keys[i] != null && !key.equals(keys[i]))
i = (i+h2) % capacity;
return values[i];
}
O valor de h2 deve estar entre 2 e capacity-2 (inclusive). De facto, no convm
que seja 1 ou capacity-1, pois ento estaramos no caso normal; e no pode ser
0 ou capacity, porque assim a tabela no funcionaria. Qualquer outro valor serve,
admitindo que, como sempre, capacity um nmero primo. Se capacity no fosse
um nmero primo, nem todos os valores do intervalo [0..capacity[ seriam
apanhados, no ciclo da funo get, e a tabela no funcionaria.
6/1/16 Algoritmos e Estruturas de Dados 23
Paradoxo do dia de aniversrio
Qual a probabilidade de haver nesta sala duas pessoas ou mais
que fazem anos no mesmo dia?
Generalizando: qual a probabilidade de, escolhendo
sucessivamente K nmeros de um conjunto com N nmeros, haver
nos nmeros escolhidos dois ou mais nmeros iguais.
Bom, 1 menos a probabilidade no haver dois nmeros iguais,
entre os escolhidos.
H quantas sequncias com K elementos escolhidos entre os N?
NK.
H quantas sequncias com K elementos no repetidos escolhidos
entre os N? N!/(N-K)!
Todas as sequncias poder ser escolhidas com igual probabilidade.
Logo, se houver K pessoas na sala, a probabilidade de haver duas ou
mais com o mesmo dia de aniversrio :
Repare que para K = 0 ou para K = 1 a expresso
365!
vale 0. Realmente se houver zero pessoas ou uma (365 K )! s fazer as contas!
pessoa na sala, a probabilidade de haver duas que
1
365K
fazem anos no mesmo dia zero!
6/1/16 Algoritmos e Estruturas de Dados 24
Tabela paradoxal 0
1
0.000000
0.000000
2 0.002740

Feitas as contas, com BigInteger e


3 0.008204
4 0.016356
5 0.027136
BigDecimal, obtemos esta tabela: na 6
7
0.040462
0.056236

coluna da esquerda, o nmero de


8 0.074335
9 0.094624
10 0.116948
pessoas na sala; na coluna da direita a 11
12
0.141141
0.167025

probabilidade de haver duas ou mais


13 0.194410
14 0.223103
15 0.252901
pessoas com a mesma data de 16
17
0.283604
0.315008

aniversrio.
18 0.346911
19 0.379119
20 0.411438

Observamos, que a partir de 23, a


21 0.443688
22 0.475695
23 0.507297
probabilidade maior que 50%. 24
25
0.538344
0.568700
26 0.598241
Moral da histria: h mais colises do 27
28
0.626859
0.654461

que parece...
29 0.680969
30 0.706316

6/1/16 Algoritmos e Estruturas de Dados 25


Algoritmos e Estruturas de Dados

Lio n. 21
rvores
rvores
rvores.
rvores simples.
rvores binrias.
rvores mutveis e rvores imutveis.

6/1/16 Algoritmos e Estruturas de Dados 2


Dizemos que estas rvores so
rvores binrias, porque quando no so
vazias tm l dentro duas rvores.
Em programao, uma rvore ou uma estrutura vazia ou
uma estrutura que tem um valor, uma rvore dita rvore
esquerda e outra rvore dita rvore direita.
Podemos representar isto em Java por meio de uma classe
abstrata, para o tipo rvore, e duas classes no abstratas, uma
para rvores vazias e outra para rvores no vazias:
public abstract class Tree<T>
{
...
}

class Empty<T> extends Tree<T> class Cons<T> extends Tree<T>


{ {
... private T value;
} private Tree<T> left;
I think that I shall never see private Tree<T> right;
A poem lovely as a tree. ...
}
Joyce Kilmer (1913), citado em Knuth,
The Art of Computer Programming, vol. I,
6/1/16 Fundamental Algorithms. Algoritmos e Estruturas de Dados 3
Funes para as rvores simples
Equipemos as nossas rvores simples com funes
para contar o nmero de elementos, para ver se
existe um dado valor, e para representar uma rvore
por meio de uma cadeia de carateres.
Estas funes ficam abstratas na classe Tree<T>:
public abstract class Tree<T>
{
public abstract int size();
public abstract boolean has(T x);
... Falta aqui a funo toString, porque
} essa vem herdada da classe Object.

Agora temos de programar as trs funes em cada


uma das outras classes.
6/1/16 Algoritmos e Estruturas de Dados 4
Funes nas rvores vazias
Ficam muito simples:
class Empty<T> extends Tree<T>
{
public int size()
{
return 0;
}
Note que esta classe
public boolean has(T x) no tem membros de
{ dados.
return false;
}

public String toString()


{
return "()";
}
6/1/16 } Algoritmos e Estruturas de Dados 5
Funes nas rvores no vazias
class Cons<T> extends Tree<T>
{ Dizemos que esta rvore simples porque
private T value; tem apenas um membro de dados alm das
private Tree<T> left;
private Tree<T> right; referncias para as subrvores.
public Cons(T value, Tree<T> left, Tree<T> right)
{
this.value = value;
this.left = left; Neste caso, precisamos
this.right = right; tambm do construtor.
}
public int size()
{
return 1 + left.size() + right.size();
}
public boolean has(T x)
{
return value.equals(x) || left.has(x) || right.has(x);
}
public String toString()
{
return "(" + value + left.toString() + right.toString() + ")";
}
}
6/1/16 Algoritmos e Estruturas de Dados 6
Funes de teste para as rvores simples
Programamos na classe abstrata:
public abstract class Tree<T>
{
public abstract int size();
public abstract boolean has(T x);

public static void testTree()


{
Tree<Integer> empty = new Empty<>();
Tree<Integer> t1 = new Cons<>(6, empty, empty);
StdOut.println(t1);
StdOut.printf("%d %b %b\n", t1.size(), t1.has(6), t1.has(25));
Tree<Integer> t2 = new Cons<>(4, empty, empty);
StdOut.println(t2);
StdOut.printf("%d %b %b\n", t2.size(), t2.has(4), t2.has(6));
Tree<Integer> t3 = new Cons<>(15, t1, t2);
StdOut.println(t3);
StdOut.printf("%d %b %b\n", t3.size(), t3.has(4), t3.has(15));
} $ java ... Tree
(6()())
public static void main(String[] args) 1 true false
{ (4()())
testTree(); 1 true false
} (15(6()())(4()()))
}
6/1/16 Algoritmos e Estruturas de Dados 3 true true 7
rvores de ns
Convencionalmente, em Java, as rvores binrias
so representadas por um conjunto de ns, cada
um com valor e com duas referncias, uma para o
n que representa a subrvore esquerda e a
outra para a referncia que representa a
subrvore direita.
Um dos ns especial, pois representa a raiz da
rvore.
Cada n da rvore referenciado por outro n,
exceto a raiz, que referenciada por um membro
de dados.
Quando a rvore vazia, esse membro de dados
vale null.
6/1/16 Algoritmos e Estruturas de Dados 8
Classe Tree<T>, alternativa
um clssico: No confunda. Esta classe tem
public class Tree<T> o mesmo nome que a outra
{ classe abstrata, mas no a
private Node root; mesma. Existe num universo
diferente. Mostramo-la aqui
private class Node
s para comparar.
{
private T value;
private Node left;
private Node right;

public Node(T value, Node left, Node right)


{
this.value = value;
this.left = left;
this.right = right;
}
}

6/1/16 ... Algoritmos e Estruturas de Dados 9


Funes da classe Tree<T> alternativa
Dois construtores:
public Tree() Este constri uma rvore vazia.
{
root = null;
} Este constri uma rvore no vazia.
public Tree(T value, Tree<T> left, Tree<T> right)
{
root = new Node(value, left.root, right.root);
}

A funo size:
Repare: o mtodo pblico invoca uma
public int size() funo privada, recursiva, que trabalha
{
return size(root); sobre os ns. Ainda que tenham o
} mesmo nome (por convenincia), so
funes diferentes.
private int size(Node p)
{
return p == null ? 0 : 1 + size(p.left) + size(p.right);
}
6/1/16 Algoritmos e Estruturas de Dados 10
Mais funes da classe Tree<T> alternativa
Funo has:
public boolean has(T x)
{
return has(root, x);
}

private boolean has(Node p, T x)


{
return p != null && (x.equals(p.value) || has(p.left, x) || has(p.right, x));
}

Funo toString:
public String toString()
{
sempre o mesmo esquema: o
return stringOf(root); mtodo pblico e a funo recursiva
} privada.
private String stringOf(Node p)
{
return "(" + (p == null ? "" : p.value.toString() + stringOf(p.left) +
stringOf(p.right)) + ")";
}
6/1/16 Algoritmos e Estruturas de Dados 11
Funes de teste para a classe alternativa
Programamos na classe abstrata:
public abstract class Tree<T>
{
...

public static void testTree()


{
Tree<Integer> empty = new Tree<>();
Tree<Integer> t1 = new Tree<>(6, empty, empty);
System.out.println(t1);
StdOut.printf("%d %b %b\n", t1.size(), t1.has(6), t1.has(25));
Tree<Integer> t2 = new Tree<Integer>(4, empty, empty);
StdOut.println(t2);
StdOut.printf("%d %b %b\n", t2.size(), t2.has(4), t2.has(6));
Tree<Integer> t3 = new Tree<Integer>(15, t1, t2);
StdOut.println(t3);
StdOut.printf("%d %b %b\n", t3.size(), t3.has(4), t3.has(15));
}
$ java ... Tree
public static void main(String[] args) (6()())
{ 1 true false
testTree(); (4()())
}} 1 true false
(15(6()())(4()()))
6/1/16 D o mesmoAlgoritmos
que a outra, claro.
e Estruturas de Dados 3 true true 12
Exerccio: juntar um valor arvore
Queremos uma funo para acrescentar um valor
rvore, do lado mais pequeno, recursivamente.
O lado mais pequeno , por definio, o lado cujo
tamanho (calculado pela funo size) menor.
Em caso de empate, acrescenta-se do lado esquerdo.
O recursivamente significa que, se a rvore no for
vazia, acrescenta na subrvore mais pequena, usando
a mesma regra, isto na subrvore mais pequena da
subrvore mais pequena, e assim sucessivamente.
Se a rvore for vazia, o resultado uma rvore s
com o elemento acrescentado.

6/1/16 Algoritmos e Estruturas de Dados 13


Juntar, na classe abstrata
Primeiro programamos na classe Tree original
(aquela que tem a classe abstrata com duas
classes derivadas, no abstratas).
Ateno: neste caso, acrescentar um valor
quer dizer construir uma nova rvore igual
anterior mas com mais um elemento inserido:
public abstract class Tree<T>
{
...

public abstract Tree<T> add(T x);


Repare: a funo devolve uma rvore.
...
}
6/1/16 Algoritmos e Estruturas de Dados 14
Juntar, nas classes Empty e Cons
Na classe Empty, simples.
public Tree<T> add(T x)
{
return new Cons<>(x, this, this);
}

Na classe Cons, tambm:


public Tree<T> add(T x)
{
return left.size() <= right.size() ?
new Cons<>(value, left.add(x), right) :
new Cons<>(value, left, right.add(x));
}
Palavras para qu?

6/1/16 Algoritmos e Estruturas de Dados 15


Testando a funo add
public static void testAdd()
{
Tree<Integer> t = new Empty<>();
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
t = t.add(x);
$ ls
StdOut.println(t); Cons.class
} Empty.class
} Tree.class
$ java -ea -cp .:../../* Tree B
7
(7()())
1000
(7(1000()())())
32
Juntmos 7, 1000, 32, 90, 55, 2 e (7(1000()())(32()()))
256. Ficmos com uma rvore 90
com 7 elementos, perfeitamente (7(1000(90()())())(32()()))
equilibrada: no primeiro nvel 55
est a raiz, 7; no segundo nvel (7(1000(90()())())(32(55()())()))
esto 1000 e 32; no terceiro 42
(7(1000(90()())(42()()))(32(55()())()))
nvel esto 90, 55, 42 3 256.
256
6/1/16 (7(1000(90()())(42()()))(32(55()())(256()())))
Algoritmos e Estruturas de Dados 16
Agora na classe Tree<T> alternativa
De facto, as funes das
Observe, mais complicado: rvores com ns so mais
public class Tree<T> complicadas do que as das
{ outras rvores.
...

public void add(T x)


{ Repare: a funo modifica a rvore,
root = add(x, root); em vez de criar uma rvore nova!
}

private Node add(T x, Node p)


{
Node result = p;
if (p == null) Repare: exceto no caso em
result = new Node(x, null, null); que o p vale null, o resultado
else p. Entretanto, no sendo p
if (size(p.left) <= size(p.right))
null, um dos descendentes
p.left = add(x, p.left);
else
de p deixar de ser null,
p.right = add(x, p.right); ficando a apontar para um
return result; novo n que contm x.
6/1/16
} Algoritmos e Estruturas de Dados 17
Testando a funo add, classe alternativa
parecida com a outra, mas repare nas
diferenas.
public static void testAdd()
{
Tree<Integer> t = new Tree<>();
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
t.add(x);
Repare: a funo add um modificador:
StdOut.println(t); modifica o objeto sobre o qual se aplica.
}
} $ ls
Tree$Node.class Tree.class
$ java -ea -cp .:../../* Tree B
78
(78()())
2
(78(2()())())
999
(78(2()())(999()()))
23
(78(2(23()())())(999()()))
6/1/16 Algoritmos e Estruturas de Dados 18
rvores mutveis e rvores imutveis
As rvores com ns so mutveis: ao acrescentarmos
um elemento, modificamos o objeto.
As rvores abstratas so imutveis: ao
acrescentarmos um n criamos uma rvore nova,
com os mesmos valores da outra, mais um.
Note os valores da rvore original no so
duplicados para irem para a rvore nova, com poucas
excees.
Na verdade, a memria reaproveitada: algumas
posies de memria so partilhadas pela rvore
original e pela rvore nova.
Usaremos preferencialmente as rvores abstratas.
Usaremos as expresses rvores abstratas e rvores com ns
6/1/16
informalmente, para distinguir as duas classes, na linguagem corrente.
Algoritmos e Estruturas de Dados 19
Problema clssico: somar a rvore
Somar a rvore abuso de linguagem;
queremos dizer somar todos os valores
presentes na rvore.
No podemos fazer isso diretamente na
classe, pois os elementos so de tipo T, e no
sabemos nada sobre o tipo T.
Mas podemos funcionalmente aplicar uma
operao binria todos os elementos da
rvore, sucessivamente.
E como veremos, at ganhamos com isso.
As nicas operaes que podemos fazer com um objeto de tipo T na classe
Tree<T> compar-lo com outro para ver se so iguais, com o mtodo equals, e
transform-lo numa cadeia de carateres, com o mtodo toString. No podemos
6/1/16 somar um valor de tipo T comAlgoritmos
outro! e Estruturas de Dados 20
Dobrando a rvore
A funo que aplica sucessivamente uma operao
binria a um elemento de uma estrutura e ao
resultado anterior, comeando por um dado valor,
chama-se fold:
public abstract T fold(BinaryOperator<T> f, T zero);

public T fold(BinaryOperator<T> f, T zero)


{
return zero;
}

public T fold(BinaryOperator<T> f, T zero)


{
return f.apply(f.apply
(value, left.fold(f, zero)), right.fold(f, zero));
}

6/1/16 Algoritmos e Estruturas de Dados 21


Testando a funo fold
$ java -ea -cp .:../../* Tree D
85
(85()())
Como exemplo, somamos 85
85
e achamos o mnimo da 32
(85(32()())())
rvore, usando fold: 117
32
100
public static void testFold() (85(32()())(100()()))
{ 217
Tree<Integer> t = new Empty<>(); 32
while (!StdIn.isEmpty()) 20
{ (85(32(20()())())(100()()))
int x = StdIn.readInt(); 237
20
t = t.add(x);
StdOut.println(t);
int sum = t.fold((a, b) -> a+b, 0);
StdOut.println(sum);
int min = t.fold((a, b) -> Math.min(a, b), Integer.MAX_VALUE);
StdOut.println(min);
}
}

6/1/16 Algoritmos e Estruturas de Dados 22


Algoritmos e Estruturas de Dados

Lio n. 22
Exerccios com rvores imutveis
Exerccios com rvores imutveis
Escrever as rvores.
Ler as rvores.
Desenhar as rvores.

6/1/16 Algoritmos e Estruturas de Dados 2


Escrevendo a rvore
Para escrever rvores, preferimos a escrita
parenttica, atravs da funo toString():
public String toString()
{
return "()"; Na classe Empty<T>.
}

public String toString() Na classe Cons<T>.


{
return "(" + value + left.toString() + right.toString() + ")";
}
$ java -cp .:../../*: Tree B

Experimentamos assim: 12
(12()())
public static void testAdd() 88
(12(88()())())
{
Tree<Integer> t = new Empty<Integer>(); 97
(12(88()())(97()()))
while (!StdIn.isEmpty())
5
{
int x = StdIn.readInt(); (12(88(5()())())(97()()))
2016
t = t.add(x);
StdOut.println(t); (12(88(5()())())(97(2016()())()))
-45
}
(12(88(5()())(-45()()))(97(2016()())()))
}
6/1/16 Algoritmos e Estruturas de Dados 3
Lendo as rvores
Com rvores de inteiros, a escrita parenttica
identifica univocamente a rvore. Logo deve ser
invertvel.
Quer dizer, dada uma cadeia parenttica que
representa uma rvore de inteiros, devemos
conseguir construir a rvore.
Faremos isso usando um mtodo de fbrica, isto ,
uma funo esttica que devolve um objeto do tipo
da classe:
public static Tree<Integer> fromString(String s)
{
...
}

6/1/16 Algoritmos e Estruturas de Dados 4


Expresses regulares
A primeira tarefa obter os tquenes que
constituem a cadeia.
Por exemplo, na cadeia (15(6()())(4()(5()())))
os tquenes so os seguintes (separados por vrgula):
(,15,(,6,(,),(,),),(,4,(,),(,5,(,),(,),),),)

Usamos expresses regulares para obter os


tquenes: um tquene , ou um nmero inteiro, ou
um parntesis a abrir, ou um parntesis a fechar; um
nmero inteiro , ou zero, ou um sinal menos
seguido de um ou mais algarismos:
public static final String REGEX_TOKEN = "\\-?\\d+|\\(|\\)";

"\\-?\\d+|\\(|\\)"
6/1/16 Algoritmos e Estruturas de Dados 5
Toquenizando a cadeia
Usamos a funo groups da classe RegularExpressions:
public static ArrayBag<String> groups(String s, String regex)
{
ArrayBag<String> result = new ArrayBag<>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
while (matcher.find())
{
String z = matcher.group();
result.add(z);
}
return result;
}

A funo groups devolve um ArrayBag, mas o que nos convm


uma Queue:
public static Tree<Integer> fromString(String s)
{
ArrayBag<String> tokens = RegularExpressions.groups(s, REGEX_TOKEN);
Queue<String> queue = new Queue<>();
tokens.visit(x -> queue.enqueue(x));
...
}
6/1/16 Algoritmos e Estruturas de Dados 6
rvore a partir de Fila
Transformar uma fila de tquenes numa
rvore um exerccio simples, mas sofisticado:
public static Tree<Integer> fromQueue(Queue<String> q)
{
Tree<Integer> result = new Empty<Integer>();
assert "(".equals(q.front());
q.dequeue();
if (! ")".equals(q.front()))
{
int x = Integer.parseInt(q.dequeue());
Tree<Integer> left = fromQueue(q);
Tree<Integer> right = fromQueue(q);
result = new Cons<Integer>(x, left, right);
}
assert ")".equals(q.front());
q.dequeue();
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 7
Escrevendo indentadamente
Se escrevermos a rvore de forma indentada, percebe-se
melhor a estrutura.
A ideia escrever a raiz e depois a subrvore esquerda e
depois a subrvore direita, ambas um pouco desviadas
para direita na linha, isto , com uma margem maior.
Cada nova subrvore vem numa nova linha.
As subrvores vazias so representadas por um hfen,
convenientemente indentado.
A funo que escreve tem como argumentos a margem
corrente e a tabulao.
Na verdade, a funo no escreve; cria sim uma cadeia de
carateres que h de ser escrita.
A tabulao indica quanto avanamos na linha, ao passar
para a subrvore.
6/1/16 Algoritmos e Estruturas de Dados 8
Funo indent
declarada na classe abstrata:
public abstract String indent(String margin, String tab);
... e implementada assim na classe Empty<T>:
public String indent(String margin, String tab)
{
return margin + "-" + newLine;
}
... e assim na classe Cons<T>:
public String indent(String margin, String tab)
{
String result = "";
result += margin + root + newLine;
result += left.indent(margin + tab, tab);
result += right.indent(margin + tab, tab);
return result;
}
A varivel newLine representa o fim de linha de maneira portvel, e vem declarada
na classe abstrata:
protected static String newLine = System.getProperty("line.separator");
6/1/16 Algoritmos e Estruturas de Dados 9
Experimentando fromString e indent
public static void testReadIndent()
{
while (StdIn.hasNextLine())
{
String line = StdIn.readLine();
Tree<Integer> t = Tree.fromString(line);
StdOut.println(t.indent("", " "));
}
} (5(-7(10(666()())())(-100()()))(6(17()())(88()())))
$ java -cp .:../../*: Tree E 5
() -7
- 10
666
(89()()) -
89 -
- -
- -100
-
(15(6()())(4()(5()()))) -
15 6
6 17
- -
- -
4 88
- -
5 -
-
-
6/1/16 Algoritmos e Estruturas de Dados 10
rvore diagramtica
Se na indentao escrevermos primeiro a subrvore
direita, depois a raiz e depois a subrvore esquerda e
rodarmos 90 para a direita, teremos um diagrama
arborescente!
Eis as funes: Na classe Tree<T>
public abstract String diagram(String margin, String tab);

public String diagram(String margin, String tab)


{
return margin + "-" + newLine; Na classe Empty<T>
}

public String diagram(String margin, String tab)


{
String result = "";
result += right.diagram(margin + tab, tab);
result += margin + root + newLine;
result += left.diagram(margin + tab, tab);
return result;
} Na classe Cons<T>
6/1/16 Algoritmos e Estruturas de Dados 11
Experimentando a funo diagram
public static void testDiagram()
{
Tree<Integer> t = new Empty<Integer>();
while (!StdIn.isEmpty())
{
int x = StdIn.readInt();
t = t.add(x);
}
StdOut.println(t.diagram("", " "));
}

$ java ... Tree


54 23 18 58 90 2 53 18 77 40 23 77 39 45 60 80 7 15 41 97

54

-
23

18
58

90

53
18

77

40

45

77

39

23

60
80

97

15

41

-
-

-
6/1/16 Algoritmos e Estruturas de Dados 12
Desenhando com o Processing
Ganhando inspirao na funo diagram, abalancemo-nos
a desenhar rvores no Processing.
Cada rvore ser desenhada num retngulo cujo canto
superior esquerdo o ponto <x0, y0>.
Cada n ocupa um retngulo de largura w e altura h,
num quadriculado de retngulos destes.
O n um crculo de raio r, centrado horizontalmente
no retngulo do n e tangente por baixo ao lado
superior do retngulo.
Os arcos entre ns partem do centro do crculo e
chegam ao ponto mdio do lado superior do retngulo
do outro n.
Os arcos para rvores vazias tm altura igual a h/2 e vo
at ao lado correspondente do retngulo.
6/1/16 Algoritmos e Estruturas de Dados 13
Exemplos
15

11 23

4 12 18 28

6 Quando desenharmos, no desenharemos o


quadriculado e as setas (que sero apenas
segmentos) ficaro por trs das bolas.

6/1/16 Algoritmos e Estruturas de Dados 14


Desenhando as bolas
A bola correspondente raiz da rvore desenhada
na quadrcula da primeiro linha que corresponde ao
tamanho da subrvore esquerda, considerando o
quadriculado que comea no ponto <x0, y0>.
O seu centro o ponto <xc, yc>, com xc =
x0+sz*w+w/2 e yc = y0+r, onde sz o tamanho da
subrvore esquerda.
A subrvore esquerda desenhada depois no
quadriculado que comea no ponto <x0, y0+h>.
A subrvore direita desenhada depois no
quadriculado que comea no ponto <x0 + (sz+1)*w>.

6/1/16 Algoritmos e Estruturas de Dados 15


Desenhando os arcos
Do centro de cada bola saem dois arcos, uma para cada
subrvore.
Se a subrvore no for vazia, o outro extremo do arco o
ponto mdio do lado superior quadrcula da subrvore.
Tratando-se da subrvore esquerda, esse extremo ser o
ponto <xc (szr+1)*w, y0+h>, onde szr o tamanho da
subrvore direita da subrvore esquerda (recordando que
esta no vazia).
Tratando-se da subrvore direita, as contas so anlogas.
Se a subrvore for vazia, ento, tratando-se da subrvore
esquerda, o outro extremo do arco estar no ponto <xc
w/2, yc+h/2> e tratando-se da subrvore direita, no ponto <xc
+ w/2, yc+h/2>.

6/1/16 Algoritmos e Estruturas de Dados 16


Funo draw
comprida, mas sistemtica:
public void draw(PApplet p, double x0, double y0, double w,
double h, double r)
{
double x = x0 + left.size() * w;
double y = y0;
double xc = x + w/2; Isto a definio na classe
double yc = y + r; Cons<T>. Na classe Empty<T>,
a funo no faz nada.
// draw the arcs
...
// draw the nodes
...

// continue recursively
left.draw(p, x0, y0 + h, w, h, r);
right.draw(p, x+w, y0 + h, w, h, r);
}

6/1/16 Algoritmos e Estruturas de Dados 17


Funo draw, desenhando os arcos
Esquerda, direita, vazio, no vazio:
public void draw(PApplet p, double x0, double y0, double w,
double h, double r)
{
... public final int COLOR_LINES = Colors.DARKRED;
// draw the arcs
p.stroke(COLOR_LINES);
p.fill(COLOR_LINES);
if (left.isEmpty())
p.line((float)xc, (float)yc,
(float)x, (float)(yc+h/2));
else
p.line((float)xc, (float)yc,
(float)(xc-w*(1+left.right().size())), (float)(y+h));

if (right.isEmpty())
p.line((float)xc, (float)yc,
s acertar a aritmtica...
(float)(x+w), (float)(yc+h/2));
else
p.line((float)xc, (float)yc,
(float)(xc+w*(1+right.left().size())), (float)(y+h));
...
} NB: as funes da classe PApplet esperam floats, no doubles.
6/1/16 Por isso, convertemos
Algoritmos eno momento
Estruturas de Dadosda chamada. 18
Funo draw, desenhando as bolas
Esquerda, direita, vazio, no vazio:
public void draw(PApplet p, double x0, double y0, double w,
double h, double r)
{
... public final int COLOR_VALUES = Colors.BLACK;
public final int COLOR_TEXT = Colors.YELLOW;
// draw the nodes
p.fill(COLOR_VALUES);
p.stroke(COLOR_VALUES);
p.ellipseMode(PApplet.CENTER);
p.ellipse((float)xc, (float)yc, 2*(float)r, 2*(float)r);
p.textAlign(PApplet.CENTER, PApplet.CENTER);
p.fill(COLOR_TEXT);
p.text(value.toString(), (float)xc, (float)yc); ...
}

6/1/16 Algoritmos e Estruturas de Dados 19


O sketch
Em esquema:
public class TreeSketch extends PApplet
{
public static final int FONT_SIZE = 18;
public static final String FONT_NAME = "Helvetica";
public static final int WIDTH = 1000;
public static final int HEIGHT = 600;
public static final int SIZE = 32; // size of tree

public static final int LEFT_MARGIN = 10;


public static void getArgs(String[] args)
public static final int TOP_MARGIN = 10;
public static final int GRID_WIDTH = 12; {
if (args.length > 0)
public static final int GRID_HEIGHT = 48;
public static final int NODE_RADIUS = 10; size = Integer.parseInt(args[0]);
if (args.length > 1)
gridWidth = Integer.parseInt(args[2]);
private int fontSize = FONT_SIZE;
private int myWidth = WIDTH; if (args.length > 2)
gridHeight = Integer.parseInt(args[3]);
private int myHeight = HEIGHT;
private static int size = SIZE; if (args.length > 3)
nodeRadius = Integer.parseInt(args[4]);
}
private static int gridWidth = GRID_WIDTH;
private static int gridHeight = GRID_HEIGHT;
private static int nodeRadius = NODE_RADIUS;

public static void main(String[] args)


{
getArgs(args);
PApplet.main(new String[] { TreeSketch.class.getName() });
6/1/16
} Algoritmos e Estruturas de Dados 20
O sketch, settings, setup, draw e update
public void settings()
{
myWidth = 2 * LEFT_MARGIN + size * gridWidth;
size(myWidth, myHeight);
}

public void setup()


{
tree = new Empty<>();
fontSize = 18*nodeRadius/10;
textFont(createFont(FONT_NAME, fontSize));
background(Colors.ALICEBLUE);
tree.draw(this, LEFT_MARGIN, TOP_MARGIN, gridWidth, gridHeight, nodeRadius);
}

public void draw()


{
background(Colors.ALICEBLUE); O fundo azul muito clarinho.
update();
tree.draw(this, LEFT_MARGIN, TOP_MARGIN, gridWidth, gridHeight, nodeRadius);
}

private void update()


{
int x = StdIn.readInt();
tree = tree.add(x);
6/1/16
} Algoritmos e Estruturas de Dados 21
Testando
$ java -cp .:../../*: TreeSketch
23
8
4
9
61 Nota: este exemplo usa uma janela
73 mais pequena do que a indicada nas
8 pginas anteriores.Alm disso,
3 mostra a retngulo que
45 corresponde a cada n da rvore.

6/1/16 Algoritmos e Estruturas de Dados 22


Funes left e right
A funo draw na classe Cons<T> usa funes left() e
right(), e no apenas os membros de dados left e right,
para aceder subrvores das suas subrvores:
public void draw(PApplet p, double x0, double y0, double w,
double h, double r)
{
...
if (left.isEmpty())
...
else
p.line((float)xc, (float)yc,
(float)(xc-w*(1+left.right().size())), (float)(y+h));
Se escrevssemos apenas 1+left.right.size()
if (right.isEmpty())
... estaria mal, porque left de tipo Tree<T>, e no Cons<T>.
else (Logo, no tem membro de dados right.)
p.line((float)xc, (float)yc,
(float)(xc+w*(1+right.left().size())), (float)(y+h));
...
} Idem para 1+right.left.size().

6/1/16 Algoritmos e Estruturas de Dados 23


Programando left e right
Na classe Tree<T>:
public abstract Tree<T> left();
public abstract Tree<T> right();

Na classe Empty<T>:
public Tree<T> left()
{
throw new NoSuchElementException();
}

public Tree<T> right()


{
throw new NoSuchElementException();
}
Na classe Cons<T>:
public Tree<T> left()
{
return left;
}
Apesar de serem triviais,
public Tree<T> right() fazem falta, como vimos.
{
return right;
}

6/1/16 Algoritmos e Estruturas de Dados 24


Exerccios
Programar uma funo booleana isLeaf, que d true
se a rvore for uma folha. Uma folha uma rvore
no vazia que cujas subrvores so ambas vazia.
Programar uma funo countLeaves, que conta as
folhas.
Programar uma funo deleteLeaves, que elimina
todas as folhas.
Programar uma funo cut, com uma argumento
inteiro x, tal que cut(x) elimina todos os valores da
rvore a partir do x-simo nvel. O nvel da raiz
zero.

6/1/16 Algoritmos e Estruturas de Dados 25


Mais exerccios
Programar uma funo esttica para criar uma rvore anloga
da figura, com um nmero dado de nveis:

Programar uma funo que calcula a primeira subrvore de


uma rvore no vazia. Por definio, a primeira subrvore de
uma rvore que no tem filho esquerdo ela prpria; tendo
filho esquerdo, a primeira do filho esquerdo.
6/1/16 Algoritmos e Estruturas de Dados 26
Algoritmos e Estruturas de Dados

Lio n. 23
rvores de busca
rvores de busca
rvores imutveis de busca.
Implementao.
Esquetes.

6/1/16 Algoritmos e Estruturas de Dados 2


rvores de busca
Uma rvore de busca, em ingls binary search tree,
uma rvore binria tal que o valor da rvore maior
do que todos os valores da subrvore esquerda e
menor do que todos os valores da subrvore
direita... 42

... e o mesmo se verifica recursivamente em todas as


subrvores. 14 51

Estruturalmente, as rvores
de busca so rvores 3 32 58
44
binrias como as outras.
Apenas os valores esto
distribudos verificando
aquela propriedade. 12 43 47 63

6/1/16 Algoritmos e Estruturas de Dados 49 3


rvores de busca, imutveis
A classe para as rvores de busca muito parecida
com a das rvores que j vimos.
Todas as operaes anteriores estaro disponveis na
classe das rvores de busca, algumas tal e qual, outras
com implementaes melhoradas.
Alm disso, h novas operaes, que so prprias das
rvores de busca.
Teremos as trs classes do costume: Tree<T>,
Empty<T> e Cons<T>.
Usaremos o nome Tree para as rvores imutveis de busca, por serem as
mais interessantes, na prtica. As rvores bsicas das aulas anteriores
sero colocadas num pacote parte, para no atrapalhar.

6/1/16 Algoritmos e Estruturas de Dados 4


Classe abstrata Tree<T>
O tipo paramtrico deve ser comparvel e as
rvores so iterveis:
public abstract class Tree <T extends Comparable<T>>
implements Iterable<T>
{
public abstract T value(); // requires !isEmpty();
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean has(T x);

public abstract Tree<T> left(); // requires !isEmpty();


public abstract Tree<T> right(); // requires !isEmpty();

public abstract Tree<T> put(T x);


public abstract Tree<T> delete(T x);
public abstract Tree<T> remove(); // requires !isEmpty();
...
}
A funo put acrescenta um valor rvore de busca; a funo delete
elemina o elemento com o valor dado da rvore de busca, se houver;
6/1/16 a funo remove eliminaAlgoritmos
a raiz eda rvore
Estruturas de busca.
de Dados 5
Classe Empty<T>
As seis primeiras funes so como antes.
As funes put, delete e remove so novas:
public Tree<T> put(T x)
{
return new Cons<>(x, this, this);
}

public Tree<T> delete(T x)


{
return this;
}

public Tree<T> remove()


{
throw new NoSuchElementException();
}
6/1/16 Algoritmos e Estruturas de Dados 6
Classe Cons<T>
Acrescentamos um membro de dados para
guardar o tamanho da rvore:
class Cons<T extends Comparable<T>> extends Tree<T>
{
private final T value;
private final Tree<T> left; Algumas operaes, mais frente, precisam de
private final Tree<T> right;
private final int size;
conhecer o tamanho da rvore. Seria pouco
prtico ter de recalcular de cada vez.
public Cons(T value, Tree<T> left, Tree<T> right)
{
assert (left.isEmpty() || value.compareTo(left.value()) > 0) &&
(right.isEmpty() || value.compareTo(right.value()) < 0);
this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();
}
... Repare na pr-condio expressa na assero: s vlido construir a rvore
} de busca se a nova raiz for maior que o raiz da subrvore esquerda, no sendo
6/1/16 esta vazia, e menor do queAlgoritmos
a raiz da subrvore
e Estruturas direita, no sendo esta vazia.
de Dados 7
Buscabilidade
Todas as rvores de busca verificam a seguinte
propriedade, que constitui o invariante das
rvores de busca:
public abstract boolean invariant(); Na classe abstrata.

public boolean invariant() Na classe das rvores no vazias.


{
return (left.isEmpty() || value.compareTo(left.value()) > 0) &&
(right.isEmpty() || value.compareTo(right.value()) < 0) &&
size == 1 + left.size() + right.size() &&
left.invariant() &&
right.invariant();
}

public boolean invariant() Na classe das rvores vazias.


{
return true;
}

6/1/16 Algoritmos e Estruturas de Dados 8


Ps-condio do construtor
sada do construtor, assertamos que a
rvore uma rvore de busca:
public Cons(T value, Tree<T> left, Tree<T> right)
{
assert (left.isEmpty() || value.compareTo(left.value()) > 0) &&
(right.isEmpty() || value.compareTo(right.value()) < 0);
this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();
assert invariant();
}

6/1/16 Algoritmos e Estruturas de Dados 9


Classe Empty<T>, put, delete, remove
Ao acrescentar a uma rvore vazia um elemento
com um valor dado, criamos uma rvore no vazia,
com esse valor na raiz e mais nada:
public Tree<T> put(T x)
{
return new Cons<T>(x, this, this);
}
Ao retirar de uma rvore vazia um elemento com
um valor dado, o resultado uma rvore vazia:
public Tree<T> delete(T x)
{
return this;
}
Remover a raiz de uma rvore vazia no faz sentido:
public Tree<T> remove()
{
throw new NoSuchElementException();
}
6/1/16 Algoritmos e Estruturas de Dados 10
Classe Cons<T>, seletores simples
So todos como antes, exceto size, que agora
se limita a consultar o membro de dados:
public T value() public Tree<T> left()
{ {
return value; return left;
} }

public int size() public Tree<T> right()


{ {
return size; return right;
} }

public boolean isEmpty()


{
return false;
}

6/1/16 Algoritmos e Estruturas de Dados 11


Classe Cons<T>, has
Expectemos que a rvore tenha x, mas, se x for
menor que a raiz, ter se o filho esquerdo tiver, e
se x for maior que a raiz, ter se o filho direito
tiver:
public boolean has(T x)
{
boolean result = true;
int cmp = x.compareTo(value);
if (cmp < 0)
Numa rvore de busca
result = left.has(x); razoavelmente
else if (cmp > 0) equilibrada, esta funo
result = right.has(x); tem comportamento
return result; logartmico, claramente!
}
6/1/16 Algoritmos e Estruturas de Dados 12
Classe Cons<T>, acrescentar um elemento
Ateno forma de falar: no bem acrescentar rvore um um
elemento com um valor dado mas sim construir uma nova rvore
igual outra, com mais um elemento, o qual ter esse valor.
Mas s haver um novo elemento se no existir j na rvore um
elemento com o valor dado.
Expectemos que a nova rvore seja igual outra (porque a raiz
igual ao valor dado) mas se o valor dado for menor que a raiz,
ento criamos uma nova rvore igual subrvore esquerda da
outra acrescentada de um novo elemento com o valor dado,
fazendo dessa nova rvore a subrvore esquerda do resultado; a
raiz do resultado a raiz da outra e a subrvore direita do
resultado a subrvore direita da outra.
E se o valor dado for maior que a raiz a mesma lenga-lenga,
trocando esquerda por direita e direita por esquerda.
Se o valor dado no nem menor nem maior que a raiz ento
igual raiz e a nossa expectativa inicial ter-se- confirmado, sendo a
prpria rvore o resultado da funo.
6/1/16 Algoritmos e Estruturas de Dados 13
Classe Cons<T>, put
semelhante ao has:
public Tree<T> put(T x) Esta funo exemplar. Assegure-se
{ de que a percebe completamente!
Tree<T> result = this;
int cmp = x.compareTo(value);
if (cmp < 0)
result = new Cons<T>(value, left.put(x), right);
else if (cmp > 0)
result = new Cons<T>(value, left, right.put(x));
return result;
} Tal como a funo has, a funo put tem
comportamento logartmico, desde que a
rvore esteja razoavelmente equilibrada.

6/1/16 Algoritmos e Estruturas de Dados 14


Acrescentar modificando
Acrescentar modificando envolve percorrer a
rvore e ligar o novo n no local certo.
Por exemplo:
7 7

4 8 +5= 4 8

1 6 9 1 6 9
Note bem: isto o que acontece com rvores
mutveis. No o que acontece com as nossas
rvores, que so imutveis.
5
6/1/16 Algoritmos e Estruturas de Dados 15
Acrescentar imutavelmente
Acrescentar imutavelmente envolve criar uma nova
rvore a cada nvel.
A cada nvel, a nova rvore partilha a rvore do
outro lado:
7 7 7

4 8 +5= 4 4 8

1 6 9 1 6 6 9

5
6/1/16 Notee Estruturas
Algoritmos bem: a rvore
de Dados original continua intacta. 16
Classe Cons<T>, apagar um elemento
Esta funo a mais trabalhosa de todas, na classe
Cons<T>.
J vimos que na classe Empty<T> trivial:
class Empty<T extends Comparable<T>> extends Tree<T>
{
public Tree<T> delete(T x)
{
return this;
}
}

Em rvores no vazias, apagar complicado, porque


se o valor a apagar estiver no meio da rvore,
temos que decidir com cuidado como ocupar o lugar
que ficou vazio, por assim dizer.
Estar no meio da rvore significa, em rigor,
que ambas as subrvores so no vazias.
6/1/16 Algoritmos e Estruturas de Dados 17
Remover a raiz
Vejamos primeiro o problema de remover a raiz.
No complicado, a no ser que um dos filhos
(ou os dois) seja a rvore vazia.

7 4 7 8

4 1 6 8 9

1 6 9
Quer dizer: quando exatamente um dos filhos a rvore vazia, o resultado o
outro filho. Se ambos os filhos forem vazios, o resultado a rvore vazia (mas
este caso pode ser apanhado pelo caso anterior).
6/1/16 Algoritmos e Estruturas de Dados 18
Remover a raiz, caso complicado.
Se ambos os filhos no so vazios, a nova raiz ser o
elemento com valor imediatamente superior ao
valor da raiz eliminada, que precisamente a raiz da
primeira subrvore da subrvore direita:
5 7
3 8 3 8
1 4 7 9 1 4 9
A primeira subrvore do filho direito ter sempre
um filho vazio (o filho esquerdo), pelo menos, e
portanto a sua raiz pode ser removida usando a
tcnica da pgina anterior.
6/1/16 Algoritmos e Estruturas de Dados 19
A primeira subrvore
A primeira subrvore a subrvore cuja raiz fica
mais esquerda, quando desenhamos a rvore
(usando a funo draw):
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public Tree<T> first() 5 6
{
Tree<T> result = this; 3 8 2 8
if (!left.isEmpty())
result = left.first();
return result;
1 4 7 9 4 7 9
}
A laranja, a primeira subrvore
... de cada uma das rvores. 3 5
}
Na classe Empty<T>, a funo first lana uma exceo.
Tambm haver a funo last, por simetria.
6/1/16 Algoritmos e Estruturas de Dados 20
A ltima subrvore
Por simetria, a ltima subrvore a subrvore
cuja raiz fica mais direita, quando desenha-
mos a rvore:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public Tree<T> last() 5 6
{
Tree<T> result = this; 3 8 2 9
if (!right.isEmpty())
result = right.last(); 1 4 7 9 4 7
return result;
} A cor de tijolo, a ltima subrvore
... de cada uma das rvores. 3 5 8
}

6/1/16 Algoritmos e Estruturas de Dados 21


Remover a raiz da primeira subrvore
Observe com ateno:
class Cons<T extends Comparable<T>> extends Tree<T>
{ Repare: o mtodo protected, e no private, porque
... herdado da classe abstrata e no queremos que seja
protected Tree<T> removeFirst() usado fora das classes derivadas. Na classe Empty, lana
{ uma exceo.
Tree<T> result = right; // if left is empty, result is right
if (!left.isEmpty())
result = new Cons<T>(value, left.removeFirst(), right);
return result;
}
...
}

Quer dizer: se o filho esquerdo vazio, ento o resultado o filho


direito;
Se no, o resultado uma nova rvore, com a mesma raiz, com o
mesmo filho direito, e com o filho esquerdo resultante de se
remover a raiz da primeira subrvore do filho esquerdo (o tal que
no era vazio).
6/1/16 Algoritmos e Estruturas de Dados 22
Remover a raiz
J sabemos: se um dos filhos vazio, o resultado o outro
filho; se ambos no so vazios, o resultado uma rvore em
que a raiz a raiz da primeira da subrvore direita, em que o
filho esquerdo o mesmo e em que o filho direito o
resultado de remover a raiz da primeira subrvore do filho
direito:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public Tree<T> remove()
{
Tree<T> result;
if (right.isEmpty())
result = left;
else if (left.isEmpty())
result = right;
else
result = new Cons<T>(right.first().value(), left, right.removeFirst());
return result;
} ...
}

6/1/16 Algoritmos e Estruturas de Dados 23


Mtodo delete
Podemos finalmente programar o mtodo delete.
Afinal, nem assim to complicado:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public Tree<T> delete(T x)
{
Tree<T> result;
int cmp = x.compareTo(value);
if (cmp < 0)
result = new Cons<T>(value, left.delete(x), right);
else if (cmp > 0)
result = new Cons<T>(value, left, right.delete(x));
else
result = remove(); Se o valor a remover menor que a raiz, remove-se
return result; esquerda; se maior, remove-se direita; se no,
} remove-se a raiz. Isto se a rvore for no vazia. Se
... for vazia, no se faz nada, na classe Empty<T>.
}
6/1/16 Algoritmos e Estruturas de Dados 24
Esquete de teste private void update()

O esquete desenha a {
int x = StdIn.readInt();

rvore e a cada passo if (x > 0)


tree = tree.put(x);

acrescenta ou elimina: else


tree = tree.delete(-x);
}

t = t.delete(20) t = t.put(60) t = t.delete(91) t = t.delete(52)

6/1/16 Algoritmos e Estruturas de Dados 25


Esquete de estresse
Acrescentar muitos ns e depois elimin-los:

Acrescentando valores...

rvore com 512 ns.

Eliminando valores...

6/1/16 Algoritmos e Estruturas de Dados 26


Algoritmos e Estruturas de Dados

Lio n. 24
rvores de busca, complementos
rvores de busca, complementos
Funes de ordem.
Iterao.
Atravessamento.
Iteradores internos.
rvores de busca chave-valor.

6/1/16 Algoritmos e Estruturas de Dados 2


Funes de ordem
Nas nossas rvores, os elementos esto por
ordem. Por isso, simples calcular o
elemento mnimo, o elemento mximo, o
elemento que est na posio de ordem k e o
renque de um elemento:
public abstract class Tree<T extends Comparable<T>>
{
...
public abstract T minimum(); // requires !isEmpty();
public abstract T maximum(); // requires !isEmpty();
public abstract T select(int k); // requires 0 <= k < size();
public abstract int rank(T x); ...
}
Na classe Empty<T> as trs primeiras lanam
uma exceo e a ltima retorna sempre zero.
Recorde que o renque de x o nmero de
elementos menores ou iguais a x.
6/1/16 Algoritmos e Estruturas de Dados 3
Funes de ordem, Empty<T>
class Empty<T extends Comparable<T>> extends Tree<T>
{
...
public T minimum()
{
throw new UnsupportedOperationException();
}

public T maximum()
{
throw new UnsupportedOperationException();
}

public T select(int k)
{
throw new UnsupportedOperationException();
}

public int rank(T x)


{
return 0;
}
...
}

6/1/16 Algoritmos e Estruturas de Dados 4


Funes de ordem, Cons<T>
O mnimo o valor da raiz da primeira
subrvore e o mximo o valor da raiz da
ltima subrvore:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public T minimum()
{
return first().value();
}

public T maximum()
{
return last().value();
}
...
}

6/1/16 Algoritmos e Estruturas de Dados 5


Select
O nome select consagrado para representar a
funo que devolve o elemento dada a sua ordem:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public T select(int k)
{
T result = value;
int t = left.size();
if (k < t)
result = left.select(k);
else if (k > t)
result = right.select(k-t-1);
return result;
}
Se a ordem k menor que o tamanho da subrvore esquerda, basta
...
considerar a subrvore esquerda; se maior, ento selecionamos na
}
subrvore direita, na posio k-t-1, onde t o tamanho da subrvore
esquerda; se igual, o resultado o valor da raiz.
6/1/16 Algoritmos e Estruturas de Dados 6
Rank
A funo rank a inversa de select, para os elementos do
contradomnio. Em geral, o renque de um valor x, presente ou
no na rvore, o nmero de elementos na rvore que so
menores que x:
class Cons<T extends Comparable<T>> extends Tree<T>
{
...
public int rank(T x)
{
int result;
int cmp = x.compareTo(value);
if (cmp < 0)
result = left.rank(x);
else if (cmp > 0)
result = 1 + left.size() + right.rank(x);
else
result = left.size();
return result;
}
... A explicao anloga da funo select.
}
6/1/16 Algoritmos e Estruturas de Dados 7
Cho, Teto
O cho de x o maior elemento presente que
menor ou igual a x; o teto de x o menor elemento
presente que maior ou igual a x.
Em ambos os casos, no havendo, o resultado null.
Para o cho, se x menor que a raiz, basta
considerar a subrvore esquerda; se maior, das
duas uma: ou o cho de x no existe na subrvore
direita (porque todos os elementos da subrvore
direita so maiores que x) e o resultado a raiz; ou
existe e ento esse o resultado.
Para o teto a mesma coisa, trocando esquerda e
direita e menor e maior.

6/1/16 Algoritmos e Estruturas de Dados 8


Funes floor e ceiling
Na classe abstrata:
public abstract class Tree<T extends Comparable<T>>
{
...
public abstract T floor(T x);
public abstract T ceiling(T x);
...
}

Na classe Empty<T>:
class Empty<T extends Comparable<T>> extends Tree<T>
{
...
public T floor(T x)
{
return null;
}

public T ceiling(T x)
{
return null;
}
...
}
A explicao anloga da funo select.

6/1/16 Algoritmos e Estruturas de Dados 9


Funes floor e ceiling, na classe Cons<T>
Ambas implementam diretamente a explicao:
public T floor(T x) public T ceiling(T x)
{ {
T result = value; T result = value;
int cmp = x.compareTo(value); int cmp = x.compareTo(value);
if (cmp < 0) if (cmp > 0)
result = left.floor(x); result = right.ceiling(x);
else if (cmp > 0) else if (cmp < 0)
{ {
T t = right.floor(x); T t = left.ceiling(x);
if (t != null) if (t != null)
result = t; result = t;
} }
return result; return result;
} }

6/1/16 Algoritmos e Estruturas de Dados 10


Iterador de rvore
Declarmos que a classe Tree<T> itervel;
portanto tem de fornecer um mtodo iterator, que
devolve um objecto Iterator<T>:
public abstract class Tree<T extends Comparable<T>>
implements Iterable<T>
{
...
public Iterator<T> iterator() { ... }
}

Mais tarde, sendo t uma rvore, poderemos escrever,


por exemplo:
for (T x : t)
StdOut.print(x);

6/1/16 Algoritmos e Estruturas de Dados 11


Pilha de rvores
Usaremos uma pilha para guardar as
subrvores ainda no processadas:
public abstract class Tree<T extends Comparable<T>>
implements Iterable<T>
{
...
private class TreeIterator implements Iterator<T>
{
private Stack<Tree<T>> i;

public TreeIterator() { ... }

public boolean hasNext() { ... }

public T next() { ... }


}
}

6/1/16 Algoritmos e Estruturas de Dados 12


Classe TreeIterator
No construtor, empilhamos a rvore, se a
rvore no for vazia:
public TreeIterator()
{ Note bem: acedemos rvore
com Tree.this. Na classa
i = new Stack<>();
TreeIterator, a expresso this
if (!Tree.this.isEmpty()) refere o prprio iterador, no a
i.push(Tree.this); rvore.
}

O iterador terminar quando a pilha ficar


vazia:
public boolean hasNext()
{ Note bem: todas as rvores na
return !i.isEmpty(); pilha sero no vazias.
}
6/1/16 Algoritmos e Estruturas de Dados 13
TreeIterator.next
O prximo elemento no iterador a raiz da rvore
que est no topo da pilha, se esta no tiver
subrvore esquerda; se tiver, o prximo elemento ,
recursivamente, a raiz de subrvore esquerda;
public T next() Se a subrvore esquerda no for vazia, empilha-
{ se a subrvore direita com a raiz (para serem
Tree<T> t = i.pop();
processadas mais tarde) e depois empilha-se a
T result = t.value(); subrvore esquerda, e avana-se recursivamente
if (!t.left().isEmpty())
(anulando a primeira escolha de result).
{
i.push(new Cons<T>(result, new Empty<T>(), t.right()));
i.push(t.left());
result = next();
}
else if (!t.right().isEmpty())
i.push(t.right());
return result;
} Se a subrvore esquerda for vazia, empilha-se a subrvore direita (para ser
processada logo a seguir), se esta no for vazia; se for vazia, a funo retorna
6/1/16 Algoritmos e Estruturas de Dados 14
logo (sem mais chamadas recursivas).
Esta funo de teste pe na rvore
Funo de teste
public static void testOrderMethods()
os nmeros lidos da consola e
depois exercita as diversas funes
{
Tree<Integer> t = new Empty<Integer>(); de ordem e o iterador.
int[] numbers = StdIn.readAllInts(); $ java ... Tree
for (int x : numbers) 65 12 8 19 71 43 95 12 6 51 82 84 20 28
t = t.put(x); (65(12(8(6()())())(19()(43(20()(28()()))(5
StdOut.println(t); 1()()))))(71()(95(82()(84()()))())))
6 8 12 19 20 28 43 51 65 71 82 84 95
for (int x : t)
Min, max, median: 6 95 43
StdOut.print(" " + x); (min + max) / 2 = 50
StdOut.println(); Ranks: 0 13 7
int min = t.minimum(); Floors: null 95 43
int max = t.maximum(); Ceilings: 6 null 51
int median = t.select(t.size() / 2);
StdOut.printf("Min, max, median: %d %d %d\n", min, max, median);
StdOut.printf("(min + max) / 2 = %d\n", (min + max) / 2);
int r1 = t.rank(min - 1);
int r2 = t.rank(max + 1);
int r3 = t.rank((min + max) / 2);
StdOut.printf("Ranks: %d %d %d\n", r1, r2, r3);
Integer f1 = t.floor(min - 1);
Integer f2 = t.floor(max + 1);
Integer f3 = t.floor((min + max) / 2);
StdOut.printf("Floors: %d %d %d\n", f1, f2, f3);
Integer c1 = t.ceiling(min - 1);
Integer c2 = t.ceiling(max + 1);
Integer c3 = t.ceiling((min + max) / 2);
StdOut.printf("Ceilings: %d %d %d\n", c1, c2, c3);
6/1/16 Algoritmos e Estruturas de Dados 15
}
Atravessamento em profundidade
O iterador visita a rvore, pela ordem dita
entreordem: primeiro visita subrvore
esquerda, depois observa a raiz, depois visita a
subrvore direita.
Retocando a funo next, poderamos
atravessar em pr-ordem: primeiro observar a
raiz, depois visitar subrvore esquerda, depois
visitar a subrvore direita.
Ou ento em ps-ordem: primeiro visitar a
subrvore esquerda, depois visitar a subrvore
direita, depois observar a raiz.
Estes so os atravessamentos em profundidade.
6/1/16 Algoritmos e Estruturas de Dados 16
Atravessamento em largura
Atravessar uma rvore em largura observar
a raiz, depois as razes das subrvores
penduradas na raiz, depois as razes das
subrvores das subrvores penduradas na raiz,
e assim sucessivamente.
Programemos um iterador para isso:
private class TreeIteratorWide implements Iterator<T>
{
...
}

6/1/16 Algoritmos e Estruturas de Dados 17


Fila de rvores
No atravessamento em largura, usamos uma fila de
rvores no iterador:
public abstract class Tree<T extends Comparable<T>>
implements Iterable<T>
{
...
private class TreeIteratorWide implements Iterator<T>
{
private Queue<Tree<T>> i;

TreeIteratorWide()
{
i = new Queue<>();
if (!Tree.this.isEmpty())
i.enqueue(Tree.this);
}

public boolean hasNext()


{
return !i.isEmpty();
}
6/1/16
... Algoritmos e Estruturas de Dados 18
TreeIteratorWide.next
O prximo elemento no iterador o valor da
raiz da rvore que est na frente da fila.
De cada vez que o iterador avana, sai a
rvore que estava na frente da fila e entram na
fila as subrvores, desde que no sejam vazias:
public T next()
{
Tree<T> t = i.dequeue();
T result = t.value();
if (!t.left().isEmpty())
i.enqueue(t.left());
if (!t.right().isEmpty())
i.enqueue(t.right());
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 19
Testando os iteradores
$ java ... Tree
65 12 8 19 71 43 95 12 6 51 82 84 20 28
(65(12(8(6()())())(19()(43(20()(28()())
)(51()()))))(71()(95(82()(84()()))())))
Eis uma funo de teste que 95
-

testa os dois iteradores: 84


-

-
public static void testIterators() 82
{ -
Tree<Integer> t = new Empty<Integer>(); 71
-
int[] numbers = StdIn.readAllInts(); 65
for (int x : numbers) -
t = t.put(x); 51
StdOut.println(t); -
43
-
StdOut.println(t.diagram("", " ")); 28
-
Iterator<Integer> itDeep = t.iterator(); 20
-
while (itDeep.hasNext()) 19
StdOut.print(" " + itDeep.next()); -
StdOut.println(); 12
-
8
Iterator<Integer> itWide = t.iteratorWide(); -
while (itWide.hasNext()) 6
StdOut.print(" " + itWide.next()); -
StdOut.println(); 6 8 12 19 20 28 43 51 65 71 82 84 95
} 65 12 71 8 19 95 6 43 82 20 51 84 28
$

6/1/16 Algoritmos e Estruturas de Dados 20


Iteradores internos
Os objetos de tipo Iterator<T> so chamados
iteradores externos: enumeram os elementos de uma
coleo, colocando-os disposio de algum troo
de cdigo.
Inversamente, chamamos iteradores internos s
funes das colees que aplicam a cada elemento da
coleo uma funo passada em argumento.
Observmos isso na classe Queue<T>, por exemplo,
com a funo visit:
public class Queue<T> implements Iterable<T>
{
...
A funo visit um
public void visit(Consumer<T> action) iterador interno.
{
for (Node p = first; p != null; p = p.next)
action.accept(p.value);
}
...
6/1/16 } Algoritmos e Estruturas de Dados 21
Iteradores internos para rvores
Implementamos trs iteradores internos, que
visitam a rvore em profundidade:
O iterador em pr-ordem, que primeiro atua
sobre o valor da raiz, depois visita a subrvore
esquerda e depois visita a subrvore direita.
O iterador em entreordem, que primeiro visita a
subrvore esquerda, depois atua sobre o valor da
raiz e depois visita a subrvore direita.
O iterador em ps-ordem, que primeiro visita a
subrvore esquerda, depois visita a subrvore
direita e depois atua sobre o valor da raiz:
public abstract void preorder(Consumer<T> action);
public abstract void inorder(Consumer<T> action);
public abstract void postorder(Consumer<T> action);
6/1/16 Estas soAlgoritmos
as declaraes
e Estruturasna classe abstrata.
de Dados 22
Iteradores internos, na classe Empty<T>
Na classe Empty<T>, os iteradores no fazem
nada:
public void preorder(Consumer<T> action)
{
// Nothing to do
}

public void inorder(Consumer<T> action)


{
// Nothing to do
}

public void postorder(Consumer<T> action)


{
// Nothing to do
}

6/1/16 Algoritmos e Estruturas de Dados 23


Iteradores internos, na classe Cons<T>
Na classe Cons<T>, os iteradores fazem o que
definio diz:
public void preorder(Consumer<T> action)
{
action.accept(value);
left.preorder(action);
right.preorder(action);
}

public void inorder(Consumer<T> action)


{
left.inorder(action);
action.accept(value);
right.inorder(action);
}

public void postorder(Consumer<T> action)


{
left.postorder(action);
right.postorder(action);
action.accept(value);
6/1/16 } Algoritmos e Estruturas de Dados 24
Testando os iteradores internos
Eis uma funo de teste:
public static void testIteratorsInternal()
{
Tree<Integer> t = new Empty<Integer>();
int[] numbers = StdIn.readAllInts();
for (int x : numbers)
t = t.put(x);
StdOut.println(t);

t.preorder(x -> StdOut.print(" " + x));


StdOut.println();
t.inorder(x -> StdOut.print(" " + x));
StdOut.println();
t.postorder(x -> StdOut.print(" " + x));
StdOut.println();
}

$ java -ea -cp .:../../* Tree


78 3 67 4 12 94 2 38 6 46 33 95 61
(78(3(2()())(67(4()(12(6()())(38(33()())(46()(61()())))))()))(94()(95()())))
78 3 2 67 4 12 6 38 33 46 61 94 95
2 3 4 6 12 33 38 46 61 67 78 94 95 A funo inorder a que processa os
2 6 33 61 46 38 12 4 67 3 95 94 78
valores por ordem crescente.

6/1/16 Algoritmos e Estruturas de Dados 25


rvores de busca imutveis chave-valor
Programamos rvores de busca imutveis chave-valor,
na classe BST<K, V> por analogia com as rvores de
busca da classe Tree<T>.
Usamos o mesmo esquema: uma classe abstrata e
duas classes concretas, uma para rvores vazias e
outra para rvores no vazias.
public abstract class BST<K extends Comparable<K>, V>
implements Iterable<K>
{
}

class BSTEmpty<K extends Comparable<K>, V> extends BST<K, V>


{
}

class BSTCons<K extends Comparable<K>, V> extends BST<K, V>


{
}
6/1/16 Algoritmos e Estruturas de Dados 26
Classe abstrata BST<K, V>
Observe:
public abstract class BST<K extends Comparable<K>, V>
{
public abstract K key(); // requires !isEmpty();
public abstract V value(); // requires !isEmpty();
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean has(K k);

public abstract V get(K k);


public abstract BST<K, V> put(K k, V v);
public abstract BST<K, V> delete(K k);
...
}

A funo key devolve a chave da raiz da rvore e a


funo value devolve o valor associado a essa chave.

6/1/16 Algoritmos e Estruturas de Dados 27


Classe BSTEmpty<K,V>
Eis um extrato:
class BSTEmpty<K extends Comparable<K>, V> extends BST<K, V>
{
...
public V get(K key)
{
return null;
}

public BST<K, V> put(K k, V v)


{
return new BSTCons<>(k, v, this, this);
}

public BST<K, V> delete(K k)


{
return this;
}
}

6/1/16 Algoritmos e Estruturas de Dados 28


Classe BSTCons<K, V>
Sendo uma classe imutvel, os membros de dados
so final:
class BSTCons<K extends Comparable<K>, V> extends BST<K, V>
{
Temos agora um membro de dados key, de tipo K,
private final K key;
e outro membro de dados value, de tipo V. A
private final V value;
rvore ordenada pela chave. Por outras
private final BST<K, V> left;
palavras, o membro de dados key faz as vezes do
private final BST<K, V> right;
membro de dados value, na classe Tree<T>. No
private final int size;
confunda!
public BSTCons(K key, V value, BST<K, V> left, BST<K, V> right)
{
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();
}
...

6/1/16 Algoritmos e Estruturas de Dados 29


Funes get, put
So anlogas s da classe Tree<T>:
public V get(K k)
{
V result = value;
int cmp = k.compareTo(key);
if (cmp < 0)
result = left.get(k);
else if (cmp > 0)
result = right.get(k);
return result;
}

public BST<K, V> put(K k, V v) Esta anloga da classe


{ BinarySearchTree<T>, mas
BST<K, V> result = this; devolve uma nova rvore,
int cmp = k.compareTo(key); enquanto a outra devolvia void.
if (cmp < 0)
result = new BSTCons<>(key, value, left.put(k, v), right);
else if (cmp > 0)
result = new BSTCons<>(key, value, left, right.put(k, v));
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 30
Funo delete
public BST<K, V> removeFirst()
{
// remove a primeira rvore
} Tal como antes, esta mais trabalhosa, e
public BST<K, V> remove() apoia-se em funes auxiliares.
{
BST<K, V> result;
if (right.isEmpty())
result = left;
else if (left.isEmpty())
result = right;
else
{
BST<K, V> f = right.first();
result = new BSTCons<>(f.key(), f.value(), left, right.removeFirst());
}
return result;
}

public BST<K, V> delete(K k)


{
BST<K, V> result;
int cmp = k.compareTo(key);
if (cmp < 0)
result = new BSTCons<K, V>(key, value, left.delete(k), right);
else if (cmp > 0)
result = new BSTCons<K, V>(key, value, left, right.delete(k));
else
result = remove();
return result;
6/1/16 Algoritmos e Estruturas de Dados 31
}
Iterador
O iterador externo enumera as chaves.
anlogo ao da classe Tree<T>:
private class BSTIterator implements Iterator<K>
{
private Stack<BST<K, V>> i;

public BSTIterator()
{
i = new Stack<>();
if (!BST.this.isEmpty())
i.push(BST.this);
}

...
}

public Iterator<K> iterator()


{
return new BSTIterator();
}
}
6/1/16 Algoritmos e Estruturas de Dados 32
Mtodos do iterador
Continua a ser muito parecido com o outro.
anlogo ao da classe Tree<T>:
...
public boolean hasNext()
{
return !i.isEmpty();
}

public K next()
{
BST<K, V> t = i.pop();
K result = t.key();
V v = t.value();
if (!t.left().isEmpty())
{
i.push(new BSTCons<K, V>(result, v, new BSTEmpty<K, V>(), t.right()));
i.push(t.left());
result = next();
}
else if (!t.right().isEmpty())
i.push(t.right());
return result;
}
6/1/16
... Algoritmos e Estruturas de Dados 33
Iterador interno de entreordem
Na classe abstrata:
public abstract void inorder(BiConsumer<K, V> action);

Na classe BSTEmpty<K,V>:
public void inorder(BiConsumer<K, V> action)
{
// nothing to do
}

Na classe BSTCons<K,V>:
public void inorder(BiConsumer<K, V> action)
{
left.inorder(action);
action.accept(key, value);
right.inorder(action);
}
6/1/16 Algoritmos e Estruturas de Dados 34
$ java ... BST

Funo de teste p 4 rrr


p 8 iii
p 66 kkk
public static void testBST()
{ i
BST<Integer, String> bst = new BSTEmpty<>();
<4 rrr><8 iii><66 kkk>
while (!StdIn.isEmpty())
{ [4 rrr][8 iii][66 kkk]
String cmd = StdIn.readString(); p 30 ggg
if ("p".equals(cmd))
p 23 aaa
{
int x = StdIn.readInt(); i
String s = StdIn.readString(); <4 rrr><8 iii><23 aaa><30 ggg><66 kkk>
bst = bst.put(x, s); [4 rrr][8 iii][23 aaa][30 ggg][66 kkk]
}
else if ("g".equals(cmd)) g 30
{ ggg
int x = StdIn.readInt(); g 50
String s = bst.get(x);
StdOut.println(s); null
} d 8
else if ("d".equals(cmd)) i
{
int x = StdIn.readInt(); <4 rrr><23 aaa><30 ggg><66 kkk>
bst = bst.delete(x); [4 rrr][23 aaa][30 ggg][66 kkk]
}
g 8
else if ("i".equals(cmd))
{ Null
for (int x : bst) s
StdOut.print("<" + x + " " + bst.get(x) + ">");
(<4,rrr>()(<66,kkk>(<30,ggg>(<23,aaa>()()
StdOut.println();
bst.inorder((k, v) -> StdOut.print("[" + k + " " + v + "]")); )())()))
StdOut.println(); size: 4
} isEmpty? False
else if ("s".equals(cmd))
{ p 30 zzzzzz
StdOut.println(bst); i
StdOut.println("size: " + bst.size()); <4 rrr><23 aaa><30 ggg><66 kkk>
StdOut.println("isEmpty? " + bst.isEmpty());
} [4 rrr][23 aaa][30 ggg][66 kkk]
}
}

6/1/16 Algoritmos e Estruturas de Dados 35


rvores imutveis vs rvores de ns
As rvores imutveis tiram partido da herana
e do polimorfismo.
Ao invs, as rvores de ns so anlogas ao
que programaramos em C.
As rvores de ns parecem mais econmicas
pois recorrem menos memria dinmica.
No entanto, a imutabilidade permite a partilha,
que uma possibilidade muito interessante,
em geral.
Em geral, a programao das rvores imutveis
mais simples do que a das rvores de ns.
6/1/16 Algoritmos e Estruturas de Dados 36
Algoritmos e Estruturas de Dados

Lio n. 25
Outras rvores
Outras rvores
rvores equilibradas.
rvores AVL, rvores rubinegras e rvores
chanfradas.
rvores dois-trs.
rvores rubinegras imutveis.

6/1/16 Algoritmos e Estruturas de Dados 2


rvores perfeitamente equilibradas
Uma rvore perfeitamente equilibrada se para cada
n o tamanho das suas subrvores diferir de 1 no
mximo. Exemplos:

65 80 71 24 4

32 44 81 31 99 5 52

12 16 61
32 12

Esta no
2 59 33 7 perfeitamente
equilibrada.

91 14 11 41 42 54 1

6/1/16 Algoritmos e Estruturas de Dados 3


rvores equilibradas
Uma rvore equilibrada se para cada n a altura da
suas subrvores diferir de 1 no mximo.
Manter uma rvore equilibrada menos complicado
do que mant-la perfeitamente equilibrada, ao pr e
ao eliminar.
12

8 15

Esta equilibrada mas


4 9 14 no perfeitamente
equilibrada.

6/1/16 Algoritmos e Estruturas de Dados 4


rvores AVL
Uma rvore AVL uma rvore de busca equilibrada.
AVL so as iniciais dos inventores, os matemticos
russos Adelson-Velskii e Landis (1962).
Em certas implementaes, os ns das rvores AVL
tm um membro que guarda a altura da subrvore
cuja raiz esse n, ou ento a diferena da altura
com a subrvore irm.
Aps inserir um elemento, a rvore AVL reequilibra-
se automaticamente, se tiver ficado desequilibrada.
Idem, ao eliminar um elemento.
As rvores AVL garantem comportamento
logartmico em inseres, buscas e remoes.
6/1/16 Algoritmos e Estruturas de Dados 5
rvores rubinegras (red-black)
Uma rvore rubinegra (red-black tree) uma rvore binria de
busca, em que cada n tem uma de duas cores: vermelho ou
preto.
Restringindo as maneiras de colorir os ns ao longo dos
caminhos da raiz at s folhas, garante-se que nenhum caminho
da raiz at uma folha tem mais do dobro dos ns do que
qualquer outro.
Assim, a rvore fica aproximadamente equilibrada.
Quando se insere ou remove um elemento, a rvore reorganiza-
se automaticamente, de maneira a repor a propriedade das
rvores rubinegras, isto , de maneira a que nenhum caminho
seja mais do dobro dos ns que qualquer outro.
Isto garante comportamento logartmico em inseres, buscas e
remoes, tal como nas rvores AVL.
As rvores AVL so mais rgidas do que as rubinegras e, por
isso, trabalham mais para inserir ou remover mas menos para
procurar.
6/1/16 Algoritmos e Estruturas de Dados 6
rvores chanfradas
As rvores chanfradas (splay trees) so rvores binrias de
busca autoajustveis.
Depois de uma operao de acessobusca, insero ou
remooo elemento acedido promovido at raiz.
A ideia acelerar os acessos subsequentes aos ns mais
recentemente acedidos.
A promoo feita de dois em dois nveis, excepto quando o
n est no primeiro nvel.
Foram inventadas por Sleator e Tarjan em 1985. Daniel Sleator
professor na Universidade Carnagie-Mellon e Robert Tarjan
professor na Universidade de Princeton.
As rvores podem estar bastante desequilibradas
episodicamente, e no garantem comportamento logartmico
em todas as operaes, mas tm comportamento logartmico
em mdia.
6/1/16 Algoritmos e Estruturas de Dados 7
rvores dois-trs
As rvores dois-trs tm dois tipos de ns: ns com um valor
e duas subrvores, e ns com dois valores e trs subrvores.
Portanto, as rvores dois-trs no so rvores binrias.
No caso dos ns com duas subrvores, as coisas passam-se
como nas rvores de busca: todos os valores presentes na
subrvore da esquerda so menores que o valor da raiz e
todos os valores presentes na subrvore da direita so
maiores que o valor da raiz.
No caso dos ns com trs subrvores, as coisas so
ligeiramente mais complicadas: todos os valores presentes na
subrvore da esquerda so menores que o menor dos dois
valores da raiz, todos os valores presentes na subrvore da
direita so maiores que o maior dos dois valores da raiz, e
todos os valores da subrvore do meio so maiores que o
menor dos dois valores da raiz e menores que o maior dos
dois valores da raiz.
6/1/16 Algoritmos e Estruturas de Dados 8
rvores dois-trs, exemplos
Uma rvore dois-trs com trs nveis:

E uma outra com quatro nveis:

Notamos que, em qualquer n, ou todas as subrvores so vazias ou todas


so no vazias. Isso no acontece por acaso: as rvores dois-trs tm essa
6/1/16 propriedade (porque esto programadas
Algoritmos e Estruturaspara tal, bem entendido).
de Dados 9
rvores dois-trs, evoluo
Inserir 63, na
rvore vazia.
Inserir 14.

Inserir 81.

Inserir 70.

Inserir 75. O n 70+81 partiu-se.

Inserir 30.
6/1/16 Algoritmos e Estruturas de Dados 10
rvores dois-trs, evoluo (2)
(Pgina anterior.)

Inserir 25.
O nmero de
nveis aumentou!

Inserir 41, 66, 90.

Inserir 86, 27.

6/1/16 Algoritmos e Estruturas de Dados 11


rvores dois-trs, evoluo (3)
(Pgina
anterior.)

Note bem: o n 66+70


partiu-se e o 70 subiu; a
Inserir seguir o n 75+86
partiu-se e o 75 subiu.
72

Inserir
19, 65,
93.

6/1/16 Algoritmos e Estruturas de Dados 12


rvores dois-trs, evoluo (4)
(Pgina
anterior.)

O n 65+66 partiu-se
o o 66 subiu.
Inserir 68.

O n 14+19 partiu-se e o 19
subiu; o n 25+30 partiu-se e o
Inserir 23. 25 subiu; o n 63+75 partiu-se
e o 64 subiu. O nmero de
nveis aumentou.

6/1/16 Algoritmos e Estruturas de Dados 13


Propriedade fundamental das rvores 2-3
Uma rvore dois-trs com menos de 2N
valores ter no mximo N nveis.
De facto, na pior da hipteses, todos os ns de uma
rvore com 2N-1 valores tero um s valor; ora,
nesse caso, a rvore ter a forma de uma rvore
binria completa com N nveis.

6/1/16 Algoritmos e Estruturas de Dados 14


Programando as rvores dois-trs
Usaremos rvores imutveis.
Comeamos com uma classe abstrata:
TreeTwoThree<T>.
Acrescentamos trs classes concretas:
TwoThreeEmpty<T>, para rvores vazias.
TwoCons<T>, para rvores com duas subrvores.
ThreeCons<T>, para rvores com trs
subrvores.
Usaremos ainda uma classe transiente:
FourTree<T>, para rvores efmeras com quatro
subrvores, que s existem momentaneamente.
6/1/16 Algoritmos e Estruturas de Dados 15
Classe abstrata TwoThreeTree<T>
Apenas indicamos a funes essenciais:
public abstract class TwoThreeTree<T extends Comparable<T>>
{
public abstract T value(); // requires isTwo();
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean has(T x);

public abstract TwoThreeTree<T> left(); // requires isTwo() || isThree();


public abstract TwoThreeTree<T> right(); // requires isTwo() || isThree();

public abstract T valueLeft(); // requires isTwo() || isThree();


public abstract T valueRight(); // requires isTwo() || isThree();
public abstract TwoThreeTree<T> middle(); // requires isThree();

public abstract boolean isTwo(); Repare nos comentrios //requires


public abstract boolean isThree(); que restringem a utilizao das
protected abstract boolean isTransient();
funes s rvores que verificam a
public abstract boolean invariant(); pr-condio.

protected abstract TwoThreeTree<T> split(); // requires isTransient();

public abstract TwoThreeTree<T> put(T x);


protected abstract TwoThreeTree<T> putr(T x);
}6/1/16 Algoritmos e Estruturas de Dados 16
Classe TwoThreeEmpty<T>
class TwoThreeEmpty<T extends Comparable<T>> protected TwoThreeTree<T> split()
extends TwoThreeTree<T> {
{ throw new UnsupportedOperationException();
public T value() }
{
throw new UnsupportedOperationException(); public boolean isTwo()
} {
return false;
Nada de muito especial
public T valueLeft() } aqui. Mas veja alguns casos
{ interessantes, na pgina
throw new UnsupportedOperationException(); public boolean isThree() seguinte.
} {
return false;
public T valueRight() }
{
throw new UnsupportedOperationException(); protected boolean isTransient()
} {
return false;
public int size() }
{
return 0; public boolean invariant()
} {
return true;
public boolean isEmpty() }
{
return true; public TwoThreeTree<T> left()
} {
throw new UnsupportedOperationException();
public boolean has(T x) }
{
return false; public TwoThreeTree<T> right()
} {
throw new UnsupportedOperationException();
public TwoThreeTree<T> put(T x) }
{
return new TwoCons<>(x, this, this); public TwoThreeTree<T> middle()
} {
throw new UnsupportedOperationException();
public TwoThreeTree<T> putr(T x) }
{
throw new UnsupportedOperationException();
} 6/1/16 Algoritmos e Estruturas de Dados 17
Funes selecionadas, TwoThreeEmpty<T>
A rvore vazia no tem valores:
public boolean has(T x)
{
return false;
}

A rvore vazia respeita o invariante das


rvores dois-trs:
public boolean invariant()
{
return true;
}

Ao inserir um valor na rvore vazia, cria-se


uma rvore binria:
public TwoThreeTree<T> put(T x)
{
return new TwoCons<>(x, this, this);
}

6/1/16 Algoritmos e Estruturas de Dados 18


Classe TwoCons<T>
Esta parecida com a classe Cons das rvores
binrias de busca:
class TwoCons<T extends Comparable<T>> extends TwoThreeTree<T>
{
private final T value;
private final TwoThreeTree<T> left;
private final TwoThreeTree<T> right;
private final int size;

public TwoCons(T value, TwoThreeTree<T> left, TwoThreeTree<T> right)


{
assert left.invariant() && right.invariant() &&
(left.isEmpty() == right.isEmpty()) &&
(left.isEmpty() || value.compareTo(left.valueRight()) > 0) &&
(right.isEmpty() || value.compareTo(right.valueLeft()) < 0);

this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();

assert invariant();
}
6/1/16 Algoritmos e Estruturas de Dados 19
Invariante das dois-trs binrias
anlogo pr-condio do construtor:
public boolean invariant()
{
return left.invariant() && right.invariant() &&
(left.isEmpty() == right.isEmpty()) &&
(left.isEmpty() || value.compareTo(left.valueRight()) > 0) &&
(right.isEmpty() || value.compareTo(right.valueLeft()) < 0);
}

Na rvores TwoCons, as funes valueLeft e


valueRight equivalem funo value:
public T value() public T valueLeft()
{ {
return value; return value;
} }

public T valueRight()
{
return value;
}
6/1/16 Algoritmos e Estruturas de Dados 20
Funo has
A funo has anloga das rvores binrias
de busca:
public boolean has(T x)
{
boolean result = true;
int cmp = x.compareTo(value);
if (cmp < 0)
result = left.has(x);
else if (cmp > 0)
result = right.has(x);
return result;
}

Mas note que as chamadas recursivas podem


invocar a funo has de subrvores ternrias.

6/1/16 Algoritmos e Estruturas de Dados 21


Funo put
A funo put a nossa pice de resistance:
Por analogia com o que vir a seguir na classe
ThreeCons, convm deixar o trabalho para
uma funo recursiva, protegida, putr:
public TwoThreeTree<T> put(T x)
{
TwoThreeTree<T> result = putr(x);
assert result.invariant();
return result;
}

protected TwoThreeTree<T> putr(T x)


{
...
}
6/1/16 Algoritmos e Estruturas de Dados 22
Funo putr
Caso especial: se o valor a inserir igual ao
valor da raiz, no h nada a fazer:
protected TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmp = x.compareTo(value);
if (cmp == 0)
return result;
...
Nota: usamos aqui, excecionalmente,
} uma sada antecipada, no caso de cmp
ser zero. Se for diferente de zero, a
funo segue em frente, por assim dizer.

6/1/16 Algoritmos e Estruturas de Dados 23


Funo putr, folhas
Caso simples: se a rvore for uma folha,
transforma-se numa rvore ternria:
protected TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmp = x.compareTo(value);
if (cmp == 0)
return result;
assert left.isEmpty() && right.isEmpty() ||
!left.isEmpty() && !right.isEmpty();
if (left.isEmpty() && right.isEmpty())
// note: right.isEmpty() is redundant;
{
if (cmp < 0)
result = new ThreeCons<>(x, value, left, left, left);
else
result = new ThreeCons<>(value, x, left, left, left);
}
Note que neste caso, a
else
rvore left a rvore vazia.
...
}
6/1/16 Algoritmos e Estruturas de Dados 24
Funo putr, caso mais estimulante
Se a rvore no for uma folha, ento, tal como
nas rvores de busca, insere-se na subrvore
do lado apropriado, consoante o valor for
menor ou maior que o valor da raiz.
A rvore retornada pela chamada recursiva
substitui a subrvore respetiva, a no ser que
seja uma rvore quaternria, transiente, a qual
ento preciso partir.
No caso em que h uma rvore quaternria, o
valor que subiu junta-se ao valor da raiz da
rvore, para construir uma rvore ternria.
6/1/16 Algoritmos e Estruturas de Dados 25
Funo putr, para rvores internas
Observe:
protected TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmp = x.compareTo(value);
if (...)
...
else
{
if (cmp < 0)
{ Este parte corresponde a
TwoThreeTree<T> t = left.putr(x); inserir do lado esquerdo. Para
if (t.isTransient()) o lado direito anlogo.
{
t = t.split();
result = new ThreeCons<>(t.value(), value, t.left(), t.right(), right);
}
else
result = new TwoCons<>(value, t, right);
}
...
}

6/1/16 Algoritmos e Estruturas de Dados 26


Funo putr, inteira
protected TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmp = x.compareTo(value);
if (cmp == 0)
return result;
assert left.isEmpty() && right.isEmpty() || !left.isEmpty() && !right.isEmpty();
if (left.isEmpty() && right.isEmpty()) // note: right.isEmpty() is redundant;
{
if (cmp < 0)
result = new ThreeCons<>(x, value, left, left, left);
else
result = new ThreeCons<>(value, x, left, left, left);
}
else
{ Bela funo!
if (cmp < 0)
{
TwoThreeTree<T> t = left.putr(x);
if (t.isTransient())
{
t = t.split();
result = new ThreeCons<>(t.value(), value, t.left(), t.right(), right);
}
else
result = new TwoCons<>(value, t, right);
}
else
{
TwoThreeTree<T> t = right.putr(x);
if (t.isTransient())
{
t = t.split();
result = new ThreeCons<>(value, t.value(), left, t.left(), t.right());
}
else
result = new TwoCons<>(value, left, t);
}
}
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 27
Classe ThreeCons<T>
De certa forma, as rvores ternrias so uma
generalizao das rvores binrias:
class ThreeCons<T extends Comparable<T>> extends TwoThreeTree<T>
{
private final T valueLeft;
private final T valueRight;
private final TwoThreeTree<T> left;
private final TwoThreeTree<T> middle;
private final TwoThreeTree<T> right;
private final int size;

public ThreeCons(T valueLeft, T valueRight,


TwoThreeTree<T> left, TwoThreeTree<T> middle, TwoThreeTree<T> right)
{
assert left.invariant() && middle.invariant() && right.invariant()
&& (left.isEmpty() == middle.isEmpty()) && (middle.isEmpty() == right.isEmpty())
&& (valueLeft.compareTo(valueRight) < 0)
&& (left.isEmpty() || valueLeft.compareTo(left.valueRight()) > 0)
&& (right.isEmpty() || valueRight.compareTo(right.valueLeft()) < 0);

this.valueLeft = valueLeft;
this.valueRight = valueRight;
this.left = left;
this.middle = middle;
this.right = right;
this.size = 2 + left.size() + middle.size() + right.size();

assert invariant();
6/1/16} Algoritmos e Estruturas de Dados 28
Invariante das dois-trs ternrias
, de novo, anlogo pr-condio do construtor:
public boolean invariant()
{
return left.invariant() && middle.invariant() && right.invariant() &&
(left.isEmpty() == middle.isEmpty()) &&
(middle.isEmpty() == right.isEmpty()) &&
(valueLeft.compareTo(valueRight) < 0) &&
(left.isEmpty() || valueLeft.compareTo(left.valueRight()) > 0) &&
(right.isEmpty() || valueRight.compareTo(right.valueLeft()) < 0);
}

Neste caso, as valueLeft e valueRight devolvem os


valores respetivos, e a funo value no est disponvel:
public T valueLeft() public T value()
{ {
return value; throw new
} UnsupportedOperationException();
}
public T valueRight()
{
return value;
}
6/1/16 Algoritmos e Estruturas de Dados 29
Funo has para rvores ternrias de busca
uma generalizao do caso j conhecido:
public boolean has(T x)
{
boolean result = true;
int cmpLeft = x.compareTo(valueLeft);
int cmpRight = x.compareTo(valueRight);
if (cmpLeft < 0)
result = left.has(x);
else if (cmpRight > 0)
result = right.has(x);
else if (cmpLeft > 0 && cmpRight < 0)
result = middle.has(x);
return result;
}

Note que as chamadas recursivas podem invocar


polimorficamente a funo has de subrvores vazias,
binrias ou ternrias.
6/1/16 Algoritmos e Estruturas de Dados 30
Funo put, rvores ternrias
Se nas rvores binrias deu luta, aqui mais luta
dar.
No entanto, a ideia a mesma:
H a funo put e uma funo recursiva, putr:
public TwoThreeTree<T> put(T x)
{
TwoThreeTree<T> result = putr(x);
if (result.isTransient()) Ateno: se o resultado da funo
result = result.split(); putr for uma rvore quaternria,
return result; ento o resultado do put a rvore
} que resulta de partir a rvore
quaternria.
protected TwoThreeTree<T> putr(T x)
{
...
6/1/16
} Algoritmos e Estruturas de Dados 31
Funo putr, rvores ternrias
Caso especial: se o valor a inserir igual a um
dos valores da raiz, no h nada a fazer:
public TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmpLeft = x.compareTo(valueLeft);
int cmpRight = x.compareTo(valueRight);
if (cmpLeft == 0 || cmpRight == 0)
return result;
...
} De novo a tcnica da sada antecipada,
no caso em que no h nada a fazer.

6/1/16 Algoritmos e Estruturas de Dados 32


Funo putr, folhas
Caso simples: se a rvore for uma folha,
transforma-se numa rvore quaternria:
public TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
...
assert left.isEmpty() && middle.isEmpty() && right.isEmpty()
|| !left.isEmpty() && !middle.isEmpty() && !right.isEmpty();
if (left.isEmpty() && middle.isEmpty() && right.isEmpty())
{
if (cmpLeft < 0)
result = new FourCons<>(x, valueLeft, valueRight, left, left, left, left);
else if (cmpRight > 0)
result = new FourCons<>(valueLeft, valueRight, x, left, left, left, left);
else
result = new FourCons<>(valueLeft, x, valueRight, left, left, left, left);
}
else A rvore quaternria transiente e no
...
durar muito tempo. Ela ser partida logo a
}
seguir, pela funo que chamou esta!

6/1/16 Algoritmos e Estruturas de Dados 33


Funo putr, caso mais estimulante
Na verdade, perfeitamente anlogo ao caso das rvores
binrias.
Se a rvore ternria no for uma folha, ento insere-se na
subrvore apropriada, consoante o valor for menor que
o menor dos valores da raiz, maior que o maior dos
valores da raiz ou estiver entre os dois.
Tal como antes, a rvore retornada pela chamada
recursiva substitui a subrvore respetiva, a no ser que
seja uma rvore quaternria, transiente, a qual ento
preciso partir.
No caso em que h uma rvore quaternria, o valor que
subiu junta-se aos valores da raiz da rvore, para
construir uma rvore, desta vez quaternria, que h de
ser partida pela funo que chamou esta.
6/1/16 Algoritmos e Estruturas de Dados 34
Funo putr, para rvores ternrias internas
Confira:
public TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;
int cmpLeft = x.compareTo(valueLeft);
int cmpRight = x.compareTo(valueRight);
if (...)
{
...
}
else
{
assert !left.isEmpty() && !middle.isEmpty() && !right.isEmpty();
if (cmpLeft < 0)
{
TwoThreeTree<T> t = left.putr(x); Este parte corresponde a inserir
if (t.isTransient()) do lado esquerdo. Para inserir ao
{ meio ou do lado direito anlogo.
t = t.split();
result = new FourCons<>
(t.value(), valueLeft, valueRight, t.left(), t.right(), middle, right);
}
else
result = new ThreeCons<>(valueLeft, valueRight, t, middle, right);
}
else
...
}
6/1/16 Algoritmos e Estruturas de Dados 35
public TwoThreeTree<T> putr(T x)
{
TwoThreeTree<T> result = this;

Funo putr,
int cmpLeft = x.compareTo(valueLeft);
int cmpRight = x.compareTo(valueRight );
if (cmpLeft == 0 || cmpRight == 0)
return result;
assert left.isEmpty() && middle.isEmpty() && right.isEmpty()

para rvores
|| !left.isEmpty() && !middle.isEmpty() && !right.isEmpty();
if (left.isEmpty() && middle.isEmpty() && right.isEmpty())
{
if (cmpLeft < 0)
result = new FourCons<>(x, valueLeft, valueRight, left, left, left, left);
else if (cmpRight > 0)

ternrias
result = new FourCons<>(valueLeft , valueRight, x, left, left, left, left);
else
result = new FourCons<>(valueLeft , x, valueRight, left, left, left, left);
}
else
{
assert !left.isEmpty() && !middle.isEmpty() && !right.isEmpty();
if (cmpLeft < 0)
{
TwoThreeTree<T> t = left.putr(x);
if (t.isTransient())
{
t = t.split();
result = new FourCons<>(t.value(), valueLeft, valueRight, t.left(), t.right(), middle, right);
}
else
result = new ThreeCons<>(valueLeft, valueRight, t, middle, right);
}
else if (cmpRight > 0)
{
TwoThreeTree<T> t = right.putr(x);
if (t.isTransient())
{
t = t.split();
result = new FourCons<>(valueLeft, valueRight, t.value(), left, middle, t.left(), t.right());
}
else
result = new ThreeCons<>(valueLeft, valueRight, left, middle, t);
}
else
{
TwoThreeTree<T> t = middle.putr(x);
if (t.isTransient())
{
t = t.split();
result = new FourCons<>(valueLeft, t.value(), valueRight, left, t.left(), t.right(), right);
}
else
result = new ThreeCons<>(valueLeft, valueRight, left, t, right);
}
}
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 36


rvores quaternrias
As rvores quaternrias tm trs valores e
quatro subrvores.
No contexto das rvores dois-trs, as rvores
quaternrias so transientes: so criadas por uma
funo, e devolvidos como resultado, mas so
imediatamente partidas, pela funo que recebe o
resultado, para formar uma rvore binria.
Sendo assim, a maior parte das funes herdadas
da classe abstrata so dispensveis.
Basta o construtor, a funo split e a funo
isTransient.
6/1/16 Algoritmos e Estruturas de Dados 37
Classe FourCons<T>
Seria uma generalizao, mas no vale a pena, pois
bastam algumas poucas funes:
class FourCons<T extends Comparable<T>> extends TwoThreeTree<T>
{
private final T valueLeft;
private final T valueMiddle;
private final T valueRight;
private final TwoThreeTree<T> left;
private final TwoThreeTree<T> middleLeft;
private final TwoThreeTree<T> middleRight;
private final TwoThreeTree<T> right;

public FourCons(T valueLeft, T valueMiddle, T valueRight,


TwoThreeTree<T> left, TwoThreeTree<T> middleLeft,
TwoThreeTree<T> middleRight, TwoThreeTree<T> right)
{
assert valueLeft.compareTo(valueMiddle) < 0;
assert valueMiddle.compareTo(valueRight) < 0;
this.valueLeft = valueLeft;
this.valueMiddle = valueMiddle;
this.valueRight = valueRight;
this.left = left;
this.middleLeft = middleLeft;
this.middleRight = middleRight;
this.right = right;
}

...
6/1/16 Algoritmos e Estruturas de Dados 38
Classe FourCons<T>, split e isTransient
A funo split simples: o resultado uma
rvore binria de rvores binrias:
public TwoCons<T> split()
{
TwoCons<T> newLeft = new TwoCons<>(valueLeft, left, middleLeft);
TwoCons<T> newRight = new TwoCons<>(valueRight, middleRight, right);
return new TwoCons<>(valueMiddle, newLeft, newRight);
}

A funo isTransient retorna true:


protected boolean isTransient()
{
return true; Nas outras classes,
}
retorna false, claro.

E assim conclumos a programao da rvores dois-trs.As funes que no


considermos aqui explicitamente so triviais.
6/1/16 Algoritmos e Estruturas de Dados 39
Experincia
rvore binria de busca, com 63 valores, de 1 a 63, inseridos
por ordem aleatria:
A altura desta
rvore 9.

rvore dois-trs, com os mesmos 63 valores, inseridos pela


mesma ordem:

6/1/16 A altura desta rvore 5. Algoritmos e Estruturas de Dados 40


Outra experincia, maior
rvore binria de busca, com 255 valores, de 1 a 255,
inseridos por ordem aleatria:

A altura desta rvore 17.

rvore dois-trs, com os mesmos 255 valores,


inseridos pela mesma ordem:
A altura desta rvore 6.

6/1/16 Algoritmos e Estruturas de Dados 41


Moral da histria
As rvores dois-trs tm comportamento logartmico
garantido, em buscas e inseres.
Isso uma propriedade muito interessante.
No entanto, a sobrecarga de ter de gerir vrios tipos
de ns considervel, e o volume de cdigo grande,
quando comparado com o das rvores binrias de
busca.
E note que ainda faltaria programar uma srie de
coisas: a funo delete, as funes de ordem, os
iteradores, etc.
O ideal seria ter este tipo de garantia logartmica, mas
no contexto de rvores binrias.
o que faremos a seguir, com as rvores red-black.
6/1/16 Algoritmos e Estruturas de Dados 42
Algoritmos e Estruturas de Dados

Lio n. 26
rvores red-black
rvores red-black
rvores dois-trs.
rvores red-black imutveis.

6/1/16 Algoritmos e Estruturas de Dados 2


Redesenhando as rvores dois-trs
Eis um exemplo:

E eis a mesma rvore desenhada com os dois


valores dos ns ternrios separados, ligados
por um segmento horizontal:

6/1/16 Algoritmos e Estruturas de Dados 3


rvores red-black

As rvores red-black so rvores dois-trs em


que os ns ternrios so representados por
um par de ns binrios:

Tipicamente, ao desenhar os ns ternrios separados, o da esquerda


fica vermelho e o da direita fica preto. Da o nome red-black.

6/1/16 Algoritmos e Estruturas de Dados 4


As RBs so BSTs
Se baixarmos as ligaes horizontais, perce-
bemos claramente que as rvores red-black
so rvores binrias de busca.

6/1/16 Algoritmos e Estruturas de Dados 5


Propriedade fundamental das rvores RB
Uma rvore red-black com menos de 2N
valores ter no mximo 2N nveis.
De facto, j sabemos que a rvore dois-trs
correspondente tem no mximo N nveis. Ora, a
rvore red-black acrescenta no mximo um nvel por
cada nvel da rvore dois-trs correspondente.

Este exemplo mostra uma rvore RB de seis nveis


que corresponde a uma rvore 2-3 de trs nveis.

6/1/16 Algoritmos e Estruturas de Dados 6


Outras propriedades
A raiz preta.
A subrvore direita de uma rvore preta
preta (se no for vazia); a subrvore esquerda
preta ou vermelha (se no for vazia).
Ambas as subrvores de uma rvore vermelha
so pretas (se no forem vazias).
As subrvores de uma rvore vermelha so
ambas vazias ou so ambas no vazias.
Se a subrvore direita de uma rvore preta for
vazia, a subrvore esquerda a rvore vazia
ou uma rvore vermelha cujas subrvores
so ambas vazias.
6/1/16 Algoritmos e Estruturas de Dados 7
rvores red-black, evoluo
Inserir 63, na Inserir 75.
rvore vazia.
Inserir 14.

Inserir 81.
Inserir 30.

Inserir 70.

6/1/16 Algoritmos e Estruturas de Dados 8


rvores red-black, evoluo (2)
(Pgina Inserir
anterior.) 41, 66,
90.

Inserir 25. Inserir


86, 27.

6/1/16 Algoritmos e Estruturas de Dados 9


rvores red-black, evoluo (3)
(Pgina
Inserir 19, 65, 93.
anterior.)

Inserir
72

6/1/16 Algoritmos e Estruturas de Dados 10


rvores red-black, evoluo (4)
(Pgina Inserir 23.
anterior.)

O n 65+66 partiu-se
o o 66 subiu.

Ao inserir 23, a rvore


Inserir 68. reorganizou-se dramatica-
mente e o nmero de nveis
baixou de 6 para 5.

6/1/16 Algoritmos e Estruturas de Dados 11


Programando as rvores red-black
Usaremos rvores imutveis.
Comeamos com uma classe abstrata:
RedBlackTree<T>.
Acrescentamos trs classes concretas:
RedBlackEmpty<T>, para rvores vazias.
BlackTree<T>, para rvores pretas.
RedTree<T>, para rvores vermelhas.
Usaremos ainda uma classe transiente:
BrownTree<T>, para rvores efmeras que
correspondem s rvores dois-trs FourCons.

6/1/16 Algoritmos e Estruturas de Dados 12


Classe abstrata RedBlackTree<T>
public abstract class RedBlackTree<T extends Comparable<T>>
{
public abstract T value();
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean has(T x); Segue o padro habitual.

public abstract boolean isRed();


public abstract boolean isBlack();

public abstract boolean invariant(); O mtodo isTwo (resp. isThree) d


public abstract boolean isSearchTree();
true se a rvore corresponder a
public abstract boolean isTwo(); um n binrio (resp. ternrio) de
public abstract boolean isThree(); uma rvore 2-3.
public abstract RedBlackTree<T> left(); // requires !isEmpty();
public abstract RedBlackTree<T> right(); // requires !isEmpty();

protected abstract boolean isTransient();


protected abstract BlackTree<T> split(); // requires isTransient();

public abstract RedBlackTree<T> put(T x);


protected abstract RedBlackTree<T> putr(T x);
...
}
6/1/16 Algoritmos e Estruturas de Dados 13
Classe RedBlackEmpty<T>
class RedBlackEmpty<T extends Comparable<T>> public boolean isTwo()
extends RedBlackTree<T> {
{ throw new UnsupportedOperationException();
public T value() }
{
throw new UnsupportedOperationException(); public boolean isThree()
} {
throw new UnsupportedOperationException();
public int size() }
{
return 0; public RedBlackTree<T> left()
} {
throw new UnsupportedOperationException();
public boolean isEmpty() }
{
return true; public RedBlackTree<T> right()
} {
throw new UnsupportedOperationException();
public boolean has(T x) }
{
return false; protected boolean isTransient()
} {
return false;
public boolean isRed() }
{
return false; protected BlackTree<T> split()
} {
throw new UnsupportedOperationException();
public boolean isBlack() }
{
return false; public RedBlackTree<T> put(T x)
} {
return new BlackTree<>(x, this, this);
public boolean invariant() }
{
return true; public RedBlackTree<T> putr(T x)
} {
throw new UnsupportedOperationException();
}
}

6/1/16 Algoritmos e Estruturas de Dados 14


Classe BlackTree<T>
Esta a classe que realmente trabalha.
class BlackTree<T extends Comparable<T>> extends RedBlackTree<T>
{
private final T value;
private final RedBlackTree<T> left;
private final RedBlackTree<T> right;
private final int size;

public BlackTree(T value, RedBlackTree<T> left, RedBlackTree<T> right)


{
assert left.invariant() && right.invariant() &&
(left.isEmpty() || value.compareTo(left.value()) > 0) &&
(right.isEmpty() || value.compareTo(right.value()) < 0) &&
(right.isEmpty() || right.isBlack()) &&
(!left.isEmpty() || right.isEmpty());

this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();

assert invariant();
}
...
6/1/16 Algoritmos e Estruturas de Dados 15
Invariante das rvores pretas
Como de costume, anlogo pr-condio
do construtor:
public boolean invariant()
{
return left.invariant() && right.invariant()
&& (left.isEmpty() || value.compareTo(left.value()) > 0)
&& (right.isEmpty() || value.compareTo(right.value()) < 0)
&& (right.isEmpty() || right.isBlack())
&& (!left.isEmpty() || right.isEmpty());
}

Repare: a penltima linha significa que a subrvore direita


tem de ser preta, se no for vazia; a ltima linha significa
que se a subrvore esquerda for vazia, ento a subrvore
direita tambm . A segunda linha e a terceira exprimem
(recursivamente) que a rvore uma rvore binria de
busca.

6/1/16 Algoritmos e Estruturas de Dados 16


Funo has
A funo has copiada da das rvores binrias
de busca:
public boolean has(T x)
{
boolean result = true;
int cmp = x.compareTo(value);
if (cmp < 0)
result = left.has(x);
else if (cmp > 0)
result = right.has(x);
return result;
}

Note que as chamadas recursivas invocaro a


funo has de subrvores pretas, vermelhas ou
vazias.
6/1/16 Algoritmos e Estruturas de Dados 17
Funo put
A funo put o buslis.
Por analogia com as rvores dois-trs, convm deixar
o trabalho para uma funo recursiva, protegida, putr:
public RedBlackTree<T> put(T x)
{
RedBlackTree<T> result = putr(x);
if (result.isTransient()) Compare com a funo put da
result = result.split(); classe ThreeCons, das rvores
assert result.invariant(); dois-trs.
return result;
}

protected TwoThreeTree<T> putr(T x)


{
...
}
6/1/16 Algoritmos e Estruturas de Dados 18
Funo putr
Caso especial: se o valor a inserir igual ao
valor da raiz da rvore preta, no h nada a
fazer:
public RedBlackTree<T> putr(T x)
{
RedBlackTree<T> result = this;
int cmp = x.compareTo(value);
if (cmp == 0)
return result;
...
}
Anlogo funo homnima da classe
TwoCons, das rvores 2-3.

6/1/16 Algoritmos e Estruturas de Dados 19


Funo putr, folhas
Caso simples: se a rvore preta for uma folha,
transforma-se numa rvore ternria, ligando-se
esquerda a uma nova rvore vermelha:
...
assert (!left.isEmpty() || right.isEmpty());

if (left.isEmpty()) // this is a black node that is a leaf


{
assert right.isEmpty();
// just turn this into a ternary node
if (cmp < 0)
{
result = ThreeCons(x, value, left, left, left);
}
else
{
result = ThreeCons(value, x, left, left, left
}
} Nota: a funo ThreeCons cria um n duplo, formado por uma
else rvore preta ligada esquerda a uma rvore vermelha. Esse n
6/1/16 ... duplo representa
Algoritmos e um n ternrio
Estruturas de Dados nas rvores 2-3. 20
Funo putr, duas subrvores pretas
Se houver duas subrvores preta, ento
primeiro insere-se do lado certo.
Depois, se o resultado for uma rvore
transiente, parte-se a rvore e cria-se um n
duplo.
Se no, o resultado, isto , a subrvore com a
insero feita, substitui a subrvore original.
At aqui, anlogo ao caso da classe
TwoTreeCons, das rvores 2-3, com as devidas
adaptaes.

6/1/16 Algoritmos e Estruturas de Dados 21


Funo putr, rvores pretas internas
Observe:
else if (isTwo()) // This is a black node with two black subtrees.
{
assert (!left.isEmpty());
assert (!right.isEmpty());
assert (left.isBlack());
assert (right.isBlack());

if (cmp < 0)
{ Este parte corresponde a
RedBlackTree<T> t = left.putr(x);
inserir do lado esquerdo. Para
if (t.isTransient())
o lado direito anlogo.
{
t = t.split();
result = ThreeCons(t.value(), value, t.left(), t.right(), right);
}
else
result = new BlackTree<>(value, t, right);
}
else
...

6/1/16 Algoritmos e Estruturas de Dados 22


Funo putr, ns duplos
Se a rvore preta tiver uma subrvore esquerda
vermelha, ento corresponde a um n ternrio da
rvore dois-trs.
Programamos por analogia com o caso da classe
ThreeCons, das rvores dois-trs.
Se o valor a inserir for igual ao valor da raiz da
subrvore vermelha, no h nada a fazer:
else
{
// this is a black node whose left subtree is red
assert (isThree());
assert (!left.isEmpty() || right.isEmpty());
assert (left.isRed());
assert (right.isEmpty() || right.isBlack());
int cmpRed = x.compareTo(left.value());
if (cmpRed == 0)
return result;
...
6/1/16 Algoritmos e Estruturas de Dados 23
Funo putr, ns duplos, caso geral
anlogo ao caso das rvores ThreeCons.
Se o n duplo corresponde a uma rvore ternria que
uma folha, ento o resultado uma rvore transiente,
castanha.
Se o n duplo corresponde a uma rvore ternria que no
uma folha, ento insere-se na subrvore apropriada,
consoante o valor for menor que valor da raiz da
subrvore vermelha, maior que o valor da raiz da rvore
preta ou estiver entre os dois.
Tal como antes, a rvore retornada pela chamada recursiva
substitui a subrvore respetiva, a no ser que corresponda
a uma rvore quaternria, transiente, a qual ento preciso
partir.
No caso em que h uma rvore quaternria, o valor que
subiu junta-se ao valor da raiz da subrvore vermelha e
ao valor da raiz da rvore preta para construir uma rvore,
desta vez quaternria, que h de ser partida pela funo que
chamou esta.
6/1/16 Algoritmos e Estruturas de Dados 24
Funo putr, ns duplos folhas
Observe:
assert (left.left().isEmpty() && left.right().isEmpty() && right.isEmpty())
|| (!left.left().isEmpty() && !left.right().isEmpty() && !right.isEmpty());

if (right.isEmpty()) // this is a double node that is a leaf


{
assert left.left().isEmpty()
&& left.right().isEmpty()
&& right.isEmpty();
if (cmpRed < 0)
result = new BrownTree<>(x, left.value(), value, Nota: aqui, right a
right, right, right, right);
else if (cmp > 0)
rvore vazia.
result = new BrownTree<>(left.value(), value, x,
right, right, right, right);
else
result = new BrownTree<>(left.value(), x, value,
right, right, right, right);
}
...

6/1/16 Algoritmos e Estruturas de Dados 25


Funo putr, ns duplos internos, esquerda
Inserir esquerda, isto , na subrvore esquerda da
subrvore vermelha:
else // This is double node that is NOT a leaf.
{
assert !left.left().isEmpty()
&& !left.right().isEmpty()
&& !right.isEmpty();
if (cmpRed < 0) // x is inserted to the left.
{
RedBlackTree<T> t = left.left().putr(x);
if (t.isTransient())
{
t = t.split();
result = new BrownTree<>(t.value(), left.value(), value,
t.left(), t.right(), left.right(), right);
}
else
result = ThreeCons(left.value(), value, t, left.right(), right);
}
else
...
}

6/1/16 Algoritmos e Estruturas de Dados 26


Funo putr, ns duplos internos, direita
Inserir direita, isto , na subrvore direita (que
preta) da rvore preta:
else // This is double node that is NOT a leaf.
{
...
if (cmpRed < 0) // x is inserted to the left.
{
...
}
else if (cmp > 0) // x is inserted to the right
{
RedBlackTree<T> t = right().putr(x);
if (t.isTransient())
{
t = t.split();
result = new BrownTree<>(left.value(), value, t.value(),
left.left(), left.right(), t.left(), t.right());
}
else
result = new BlackTree<>(value, left, t); }
else
{ Este caso mais simples que o anterior, porque a rvore t
... pode ser usada diretamente. No caso anterior, a rvore t
} tinha de ser integrada num n duplo.
}
6/1/16 ... Algoritmos e Estruturas de Dados 27
Funo putr, ns duplos internos, ao meio
Inserir ao meio, isto , na subrvore direita da subrvore
vermelha:
else // This is like a 3-node that is NOT a leaf.
{
if (cmpRed < 0) // x is inserted to the left.
{
...
}
else if (cmp > 0) // x is inserted to the right
{
...
}
else
{
RedBlackTree<T> t = left.right().putr(x);
if (t.isTransient())
{
t = t.split();
result = new BrownTree<>(left.value(), t.value(), value,
left.left(), t.left(), t.right(), right);
}
else
result = ThreeCons(left.value(), value, left.left(), t, right);
}
} Este caso anlogo ao primeiro, mas agora trata-se da
6/1/16 Algoritmos e Estruturas de Dados 28
... subrvore direita da subrvore vermelha.
public RedBlackTree<T> putr(T x)
{
RedBlackTree<T> result = this;
// if x equal to the value, nothing to do

Funo putr, inteira


int cmp = x.compareTo(value);
if (cmp == 0)
return result;

// Remember: this is a black node: if the left subtree is empty, the right subtree must be empty
assert (!left.isEmpty() || right.isEmpty());

// is this a binary node that is a leaf?


if (left.isEmpty()) // this is a black node that is a leaf
{
assert right.isEmpty();
// just turn this into a ternary node
if (cmp < 0)
{
result = ThreeCons(x, value, left, left, left); // left is empty
}
else
{
result = ThreeCons(value, x, left, left, left); // left is empty
}
}
else if (isTwo()) // this is a black node whose left subtree is black
{
assert (!left.isEmpty());
assert (!right.isEmpty());
assert (left.isBlack());
assert (right.isBlack());

if (cmp < 0)
{
RedBlackTree<T> t = left.putr(x);
if (t.isTransient())
{
t = t.split();
result = ThreeCons(t.value(), value, t.left(), t.right(), right);
}
else
result = new BlackTree<>(value, t, right);
}
else
{
RedBlackTree<T> t = right.putr(x);

Esta certamente a maior funo


if (t.isTransient())
{
t = t.split();
result = ThreeCons(value, t.value(), left, t.left(), t.right());
}

da nossa cadeira: tem 124 linhas, }


}
else
result = new BlackTree<>(value, left, t);

incluindo comentrios e asseres.


else
{
// this is a black node whose left subtree is red. It corresponds to a ThreeCons node
assert (isThree());
assert (!left.isEmpty() || right.isEmpty());
assert (left.isRed());
assert (right.isEmpty() || right.isBlack());
int cmpRed = x.compareTo(left.value());
if (cmpRed == 0)
return result;
assert (left.left().isEmpty() && left.right().isEmpty() && right.isEmpty())
|| (!left.left().isEmpty() && !left.right().isEmpty() && !right.isEmpty());
if (right.isEmpty()) // this is a 3-node that is a leaf
{
assert left.left().isEmpty() && left.right().isEmpty() && right.isEmpty();
if (cmpRed < 0)
result = new BrownTree<>(x, left.value(), value, right, right, right, right); // right is empty.
else if (cmp > 0)
result = new BrownTree<>(left.value(), value, x, right, right, right, right);
else
result = new BrownTree<>(left.value(), x, value, right, right, right, right);
}
else // This is like a 3-node that is NOT a leaf.
{
assert !left.left().isEmpty() && !left.right().isEmpty() && !right.isEmpty();
if (cmpRed < 0) // x is inserted to the left.
{
RedBlackTree<T> t = left.left().putr(x); // Insert on the left of the the red node.
if (t.isTransient())
{
t = t.split();
result = new BrownTree<>(t.value(), left.value(), value, t.left(), t.right(), left.right(), right);
}
else
// Here we have to rebuild the red-black pair, with the new left subtree of the red node.
result = ThreeCons(left.value(), value, t, left.right(), right);
}
else if (cmp > 0) // x is inserted to the right (directly: this is a black node)
{
RedBlackTree<T> t = right().putr(x);
if (t.isTransient())
{
t = t.split();
result = new BrownTree<>(left.value(), value, t.value(), left.left(), left.right(), t.left(), t.right());
}
else
result = new BlackTree<>(value, left, t); // the new tree replaces the right subtree, so to speak.
// This case is simpler than the other two, because the right subtree is black.
// On the previous case, the red-black pair had to be rebuilt.
}
else
{
// x is inserted in the middle, i.e. to the right of the red node
RedBlackTree<T> t = left.right().putr(x);
if (t.isTransient())
{
t = t.split();
// result = new BrownTree<>(left.value(), t.value(), value, left.right(), t.left(), t.right(), right); // last bug found! :-)
result = new BrownTree<>(left.value(), t.value(), value, left.left(), t.left(), t.right(), right);
}
else
// Here we have to rebuild the red-black pair, with the new right subtree of the red node.
// This is similar to the first case, but now to the right.
result = ThreeCons(left.value(), value, left.left(), t, right);
}

6/1/16 Algoritmos e Estruturas de Dados 29


}
}
return result;
}
Funes auxiliares
A funo ThreeCons, constri um n duplo, vermelho-preto:
protected BlackTree<T> ThreeCons(T x, T y,
RedBlackTree<T> left, RedBlackTree<T> middle, RedBlackTree<T> right)
{
assert x.compareTo(y) < 0;
RedTree<T> r = new RedTree<T>(x, left, middle);
return new BlackTree<>(y, r, right);
}

A funo isTwo verifica se a rvore representa um n binrio:


// A black node represents a binary 2-3 tree if it is a leaf or both subtrees are black
public boolean isTwo()
{
return left.isEmpty() || left.isBlack();
// Note: by the invariant, the right subtree is black, if it is not empty.
}

A funo isThree verifica se a rvore corresponde a um n


ternrio:
// A black node represents a ternary 2-3 tree if the left subtree is red.
public boolean isThree()
{
Na verdade, esta funo no usada.
return !left.isEmpty() && left.isRed();
}

6/1/16 Algoritmos e Estruturas de Dados 30


Classe RedTree<T>
As rvores vermelhas tm pouca funcionalidade.
Todas as funes so triviais ou idnticas s das rvores
pretas.
A funo put proibida: no faz sentido acrescentar
diretamente s rvores vermelhas, pois elas so parte de
ns duplos e entram sempre como subrvores esquerdas
de rvores pretas.
class RedTree<T extends Comparable<T>> extends RedBlackTree<T>
{
...
public RedBlackTree<T> put(T x)
{
throw new UnsupportedOperationException();
}

protected RedBlackTree<T> putr(T x)


{
throw new UnsupportedOperationException();
}
...
6/1/16
} Algoritmos e Estruturas de Dados 31
rvores vermelhas
Apenas o invariante ligeiramente diferente:
class RedTree<T extends Comparable<T>> extends RedBlackTree<T>
{
private final T value;
private final RedBlackTree<T> left;
private final RedBlackTree<T> right;
private final int size;

public RedTree(T value, RedBlackTree<T> left, RedBlackTree<T> right)


{
assert left.invariant() && right.invariant()
&& (left.isEmpty() == right.isEmpty())
&& (left.isEmpty() || value.compareTo(left.value()) > 0)
&& (right.isEmpty() || value.compareTo(right.value()) < 0)
&& (left.isEmpty() || left.isBlack())
&& (right.isEmpty() || right.isBlack());

this.value = value;
this.left = left;
this.right = right;
this.size = 1 + left.size() + right.size();

assert invariant();
}

public boolean invariant()


{
return left.invariant() && right.invariant()
&& (left.isEmpty() == right.isEmpty())
&& (left.isEmpty() || value.compareTo(left.value()) > 0)
&& (right.isEmpty() || value.compareTo(right.value()) < 0)
&& (left.isEmpty() || left.isBlack())
&& (right.isEmpty() || right.isBlack());
}
6/1/16 ... Algoritmos e Estruturas de Dados 32
Classe BrownCons<T>
Usamos rvores castanhas para representar as
rvores quaternrias efmeras:
class BrownTree<T extends Comparable<T>> extends RedBlackTree<T>
{
private final T valueLeft;
private final T valueMiddle;
private final T valueRight;
private final RedBlackTree<T> left;
private final RedBlackTree<T> middleLeft;
private final RedBlackTree<T> middleRight;
private final RedBlackTree<T> right;

public BrownTree(T valueLeft, T valueMiddle, T valueRight,


RedBlackTree<T> left, RedBlackTree<T> middleLeft,
RedBlackTree<T> middleRight, RedBlackTree<T> right)
{
assert valueLeft.compareTo(valueMiddle) < 0;
assert valueMiddle.compareTo(valueRight) < 0;
this.valueLeft = valueLeft;
this.valueMiddle = valueMiddle;
this.valueRight = valueRight;
this.left = left;
this.middleLeft = middleLeft;
this.middleRight = middleRight;
this.right = right;
}

...
6/1/16 Algoritmos e Estruturas de Dados 33
rvores castanhas, split e isTransient
Tal como na classe FourCons, as nicas operaes
interessantes so split e isTransient.
Ambas so anlogas s da classe FourCons
A funo split constri rvore uma preta com
subrvores pretas:
protected BlackTree<T> split()
{
BlackTree<T> newLeft = new BlackTree<>(valueLeft, left, middleLeft);
BlackTree<T> newRight = new BlackTree<>(valueRight, middleRight, right);
return new BlackTree<>(valueMiddle, newLeft, newRight);
}

A funo isTransient retorna true:


protected boolean isTransient() Nas outras classes,
{ retorna false.
return true;
}
E assim conclumos a programao da rvores red-black.As funes que no
considermos aqui explicitamente so triviais.
6/1/16 Algoritmos e Estruturas de Dados 34
Experincia
rvore binria de busca, com 63
valores, de 1 a 63, inseridos por
ordem aleatria:
A altura desta rvore 15.

Esta rvore saiu bastante


desequilibrada...

rvore red-black, com os


mesmos valores, inseridos pela
mesma ordem.
A altura desta rvore 7.

6/1/16 Algoritmos e Estruturas de Dados 35


Outra experincia, maior
rvore binria de busca, com 511 valores, de 1 a 511,
inseridos por ordem aleatria:

A altura desta rvore 20.


rvore red-black, com os mesmos 511 valores, inseridos pela
mesma ordem:

A altura desta rvore 12.

6/1/16 Algoritmos e Estruturas de Dados 36


Moral da histria
As rvores red-black so fantsticas!
Sendo rvores binrias de busca, podemos
acrescentar-lhe as funes que programmos
na nossa classe original para rvores binrias
de busca, sem complicaes: funes de ordem,
iteradores, draw, etc.
Por outro lado, falta programar a funo
delete.
E faltaria experimentar implementar isto tudo
com rvores de ns.

6/1/16 Algoritmos e Estruturas de Dados 37


Apontamento final
As rvores red-black foram publicadas no artigo A
dichromatic framework for balanced trees, de Leonidas J.
Guibas e Robert Sedgewick, in 1978.
Segundo Sedgewick, o nome red-black tem a seguinte
explicao:
A lot of people ask why did we use the name redblack.Well, we
invented this data structure, this way of looking at balanced trees, at
Xerox PARC which was the home of the personal computer and
many other innovations that we live with today including graphic user
interfaces, Ethernet and object-oriented programming and many
other things. But one of the things that was invented there was laser
printing and we were very excited to have nearby a color laser
printer that could print things out in color and out of the colors the
red looked the best. So, thats why we picked the color red to
distinguish red links, the types of links, in tree nodes. So, thats an
answer to the question for people that have been asking.
6/1/16 Algoritmos e Estruturas de Dados 38
Algoritmos e Estruturas de Dados

Lio n. 27
Grafos e buscas em grafos
Grafos
Grafos em programao.
Grafos dirigidos e no dirigidos.
Grafos pesados.
Busca em largura.
Busca em profundidade.

6/1/16 Algoritmos e Estruturas de Dados 2


Grafos
Um grafo um conjunto de vrtices mais uma
coleo de arestas.
Os vrtices so o que ns quisermos:
nmeros, pontos, cadeias de carateres,
pessoas, cidades, etc.
As arestas so pares ordenados de vrtices,
associados a um nmero, dito o peso da
aresta.
Frequentemente, visualizamos os grafos
representado os vrtices por meio de pontos
e as arestas por meio de setas ou segmentos.
6/1/16 Algoritmos e Estruturas de Dados 3
Exemplo: livros emprestados
Um grafo que representa o nmero de livros
emprestados entre um conjunto de amigos:
4 2

Joo Rute
Lus
3

1 3
2
Vera Ana

1 David Uma aresta de X para Y com


etiqueta N significa que X
emprestou N livros a Y.
6/1/16 Algoritmos e Estruturas de Dados 4
Exemplo: autoestradas
Grafo das autoestradas em Portugal
55 Braga Neste caso, cada ligao
representa duas arestas,
Porto
uma para cada lado, ambas
65 60 90 com o mesmo peso.
110 Viseu Guarda
Aveiro
70 60
120
Coimbra
Covilh
65
Leiria
280
120
125
vora Note que h duas
Lisboa 120 autoestradas entre Lisboa e
270 Leiria e tambm entre
Aveiro e Porto.
Faro
6/1/16 Algoritmos e Estruturas de Dados 5
Exemplo: quadrilha
Quadrilha, de Carlos Drummond de Andrade:

Joo amava Teresa que amava Raimundo


que amava Maria que amava Joaquim que amava Lili,
que no amava ningum.
Joo foi para os Estados Unidos,Teresa para o convento,
Raimundo morreu de desastre, Maria ficou para tia,
Joaquim suicidou-se e Lili casou com J. Pinto Fernandes
que no tinha entrado na histria.

Joo Teresa Raimundo Maria Joaquim Lili

Uma seta de X para Y significa X amava Y. Neste caso, o peso


no importante. como se valesse 1 em todos os casos.
6/1/16 Algoritmos e Estruturas de Dados 6
Grafos em programao
Representamos os vrtices por nmeros inteiros, no
intervalo de 0 at ao nmero de vrtices menos um.
Representamos cada aresta por um triplo: vrtice de
partida, vrtice de chegada e peso.
A cada vrtice, associamos o saco das arestas que
dele partem.
Juntamos o saco de todas as arestas:
public class Graph
{
public final int nVertices; // number of vertices
private Bag<Edge>[] adj; // one bag for each vertex
private Bag<Edge> edges; // bag of all edges
...
}
Mais convencionalmente, usaramos listas, em vez de sacos.
6/1/16 Algoritmos e Estruturas de Dados 7
Classe Edge
class Edge
{
public final int from;
public final int to;
public final double weight;

public Edge(int from, int to, double weight)


{
assert from != to;
this.from = from;
this.to = to;
this.weight = weight;
}

public Edge(int from, int to)


{
this(from, to, 1.0);
}

public Edge reverse()


{
return new Edge(to, from, weight);
}

public static int compareByWeight(Edge e1, Edge e2)


{
double z = e1.weight - e2.weight; Esta funo de comparao
return z < 0 ? -1 : z > 0 ? 1 : 0;
} ser usada mais adiante.
}
6/1/16 Algoritmos e Estruturas de Dados 8
Classe Graph, construtor
public class Graph
{
public final int nVertices; // number of vertices
private Bag<Edge>[] adj; // one bag for each vertex
private Bag<Edge> edges; // bag of all edges

@SuppressWarnings("unchecked")
public Graph(int nVertices)
{
this.nVertices = nVertices;
edges = new Bag<>();
adj = (Bag<Edge>[]) new Bag[nVertices];
for (int i = 0; i < nVertices; i++)
adj[i] = new Bag<Edge>();
}
... De incio, fica tudo vazio.
}

6/1/16 Algoritmos e Estruturas de Dados 9


Classe Graph, acrescentar arestas
A operao bsica de acrescentar uma aresta
a seguinte:
public void addEdgeDirected(Edge x)
{
edges.add(x); Note que a mesma aresta
adj[x.from].add(x); acrescentada a dois sacos: o saco geral
} e o saco da vrtice de partida.

Frequentemente, queremos acrescentar ao


mesmo tempo uma aresta e a sua inversa:
public void addEdge(Edge x)
{
addEdgeDirected(x);
addEdgeDirected(x.reverse());
}
Note bem: no controlamos isso no nosso programa, mas nesta fase
proibido acrescentar mais do que uma aresta com o mesmo vrtice de
partida e o mesmo vrtice de chegada ou uma aresta em que o vrtice
6/1/16
de partida seja igualAlgoritmos
ao vrticee Estruturas de Dados
de chegada. 10
Iterando vrtices, arestas
Iteramos os vrtices enumerando (num ciclo
for...) os nmeros inteiros de 0 a nVertices-1.
Iteramos as arestas por meio das seguintes
funes:
public Iterable<Edge> edges()
{
return edges;
}

public Iterable<Edge> adj(int v)


{
return adj[v]; Fora da classe ningum sabe que
} na classe as arestas esto
guardadas em sacos.

6/1/16 Algoritmos e Estruturas de Dados 11


Grafos no dirigidos
Um grafo no dirigido um grafo onde se
existe uma aresta tambm existe a sua inversa.
Se essa propriedade no se verificar, o grafo
um grafo dirigido.
Certas questes dizem respeito apenas a
grafos dirigidos; outras no distinguem, mas a
interpretao do resultado pode variar,
consoante o grafo for dirigido ou no.
Nos exemplos introdutrios, usaremos apenas
grafos no dirigidos.

6/1/16 Algoritmos e Estruturas de Dados 12


Problema da busca
O problema da busca num grafo consiste em
calcular os vrtices que so alcanveis a
partir de um vrtice dado.
Um vrtice alcanvel a partir de outro se
existir no grafo um caminho que vai do outro
at ele.
Um caminho uma sequncia de arestas e1, e2,
..., eN, tal que ei.to == ei+1.from.
Acessoriamente, calculamos, para cada vrtice
alcanvel, um caminho do vrtice de partida
at esse vrtice.
6/1/16 Algoritmos e Estruturas de Dados 13
Classe abstrata Search
Representamos o problema da busca num grafo por
meio da classe abstrata Search:
public abstract class Search
{
public final Graph g; Todas as funes so
public final int start; abstratas, exceto pathTo.

public abstract boolean isMarked(int v);


public abstract int count(); Note bem: no
public abstract Iterable<Integer> result(); problema da
public abstract Edge edgeTo(int v); busca, o peso
public abstract Iterable<Edge> searchTree(); das arestas no
interessa.
public Iterable<Edge> pathTo(int v) {...}
}
Os vrtices marcados so os vrtices alcanveis; a funo count diz quantos
so; a funo result enumera-os; edgeTo(v) indica a aresta com a qual chegmos
a v; a funo searchTree enumera as arestas visitadas durante a busca; pathTo(v)
enumera as arestas que compem um caminho de start at v.
6/1/16 Algoritmos e Estruturas de Dados 14
Busca em profundidade
A busca em profundidade uma estratgia de busca num grafo.
Consideremos a analogia com uma rede de tneis, por exemplo do metro.
Para passear nos tneis, visitando todas as estaes, podemos adotar a
seguinte disciplina:
Inicialmente, por hiptese, toda a rede est s escuras.
A chegar a uma estao que est s escuras, acendemos a luz dessa
estao e marcamos o tnel que nos trouxe at estao; depois,
espreitamos por cada um dos tneis que partem dessa estao: se houver
uma luz ao fundo do tnel, esse tnel no interessa, porque a estao na
outra ponta j foi visitada anteriormente; inversamente se algum dos tneis
no tiver luz ao fundo, esse interessa: seguimos por ele e, chegados
estao que est ao fim do tnel, procedemos de acordo com a mesma
disciplina (acendendo a luz, marcando o tnel de chegada, etc.)
Se, ao chegar a uma estao, nenhum dos tneis interessar, retrocedemos,
usando o tnel pelo qual tnhamos chegado a essa estao da primeira vez
(o qual ns tnhamos deixado marcado).
Quando, nestas voltas, passarmos de novo pela estao inicial e j no
houver nesta tneis que interessem, todas as estaes alcanveis estaro
iluminadas.
6/1/16 Algoritmos e Estruturas de Dados 15
Classe DepthFirstSearch
Implementa a classe abstrata Search:
class DepthFirstSearch extends Search
{ Em result, guardamos as
private Bag<Integer> result; estaes visitadas;
private boolean[] marked; marked[x] vale true se a
private Edge[] edgeTo; luz da estao x estiver
private Bag<Edge> searchTree; acesa; edgeTo[x] assinala
o tnel pelo qual
public DepthFirstSearch(Graph g, int s)
chegmos estao x pela
{
super(g, s); primeira vez; searchTree
result = new Bag<Integer>(); coleciona os tneis que
marked = new boolean[g.nVertices]; percorremos.
edgeTo = new Edge[g.nVertices];
searchTree = new Bag<>();
dfs(s);
} Os clculos so feitos no
construtor, pela funo
private void dfs(int v) dfs.
{
...
}
6/1/16 Algoritmos e Estruturas de Dados 16
...
Funo dfs
Observe. um clssico:
private void dfs(int v)
{
marked[v] = true;
result.add(v);
for (Edge x : g.adj(v))
if (!marked[x.to])
{
edgeTo[x.to] = x;
searchTree.add(x);
dfs(x.to);
}
}

6/1/16 Algoritmos e Estruturas de Dados 17


As outras funes
So simples:
public boolean isMarked(int v)
{
return marked[v];
}

public int count()


{
return result.size();
}

public Iterable<Integer> result()


{
return result;
}

public Edge edgeTo(int x)


{
return edgeTo[x];
}

public Iterable<Edge> searchTree()


{
return searchTree;
6/1/16 } Algoritmos e Estruturas de Dados 18
Funo pathTo
Tambm muito interessante:
public Iterable<Edge> pathTo(int v)
{
Stack<Edge> result = null; Recorde que colocmos esta
if (isMarked(v)) funo na classe abstrata.
{
result = new Stack<>();
for (int x = v; x != start; x = edgeTo(x).from)
result.push(edgeTo(x));
}
return result;
}

Note que se o vrtice v no estiver marcado, o resultado null,


assinalando que no existe caminho de start at v; se v for igual a start,
existe caminho, mas vazio.

6/1/16 Algoritmos e Estruturas de Dados 19


Busca em largura
A busca em largura outra estratgia de busca num grafo.
Consideremos a mesma analogia com uma rede de tneis, que vai neste
caso ser explorada por um rob que tem a capacidade de se clonar.
Adotamos a seguinte disciplina:
Inicialmente, tal como antes, toda a rede est s escuras.
Quando o rob chega a uma estao que esteja s escuras, acende a luz,
marca o tnel que o trouxe at ali, espreita cada um dos tneis que saem
dessa estao e, se no houver luz ao fundo desse tnel, clona-se e faz o
clone seguir por esse tnel, para cada um desses tneis, ao mesmo tempo.
Os clones tero o mesmo comportamento: quando chegarem estao ao
fundo do tnel, acendero a luz (se a luz ainda estiver apagada), etc.
Se no houver tneis em que a estao ao fundo esteja apagada, o rob
desfaz-se em fumo.
Pode acontecer que, em algum momento, dois ou mais robs se dirijam
para a mesma estao, atravs de tneis diferentes. Nesse caso, ganha o
que chegar primeiro: esse que acende a luz, e que depois se clona. Os
que chegam depois desfazem-se em fumo.
Quando no houver mais robs, todas as estaes alcanveis estaro
iluminadas.
6/1/16 Algoritmos e Estruturas de Dados 20
Classe BreadthFirstSearch
Implementa tambm a classe abstrata Search:
class BreadthFirstSearch extends Search
{
private Bag<Integer> result;
private boolean[] marked;
private Edge[] edgeTo;
private Bag<Edge> searchTree;

public BreadthFirstSearch(Graph g, int s)


{
super(g, s);
result = new Bag<Integer>();
marked = new boolean[g.nVertices];
edgeTo = new Edge[g.nVertices];
searchTree = new Bag<>();
bfs(s);
} igual outra, exceto que
na funo que faz os
private void bfs(int v) clculos, agora chamada
{ bfs.
...
}
6/1/16 Algoritmos e Estruturas de Dados 21
...
Funo bfs
Outro clssico:
private void bfs(int v)
{
marked[v] = true;
result.add(v);
Queue<Integer> q = new Queue<>();
q.enqueue(v);
while (!q.isEmpty())
{
int w = q.dequeue();
for (Edge x : g.adj(w))
if (!marked[x.to])
{
marked[x.to] = true;
result.add(x.to);
edgeTo[x.to] = x;
searchTree.add(x); As outras funes so
q.enqueue(x.to); iguais s da classe
} DepthFirstSearch.
}
}
6/1/16 Algoritmos e Estruturas de Dados 22
Caminho mais curto
Na classe BreadthFirstSearch, o resultado de
pathTo(v) representa o caminho mais curto de
start at v.
Mais precisamente, se houver vrios caminhos
com comprimento mnimo, isto , com
comprimento igual ao comprimento do
caminho mais curto, representa um deles.
Havendo vrios caminhos mais curtos, aquele
dentre eles que ser calculado pela funo
depende da ordem por que as arestas foram
acrescentadas ao grafo.
6/1/16 Algoritmos e Estruturas de Dados 23
Relao com o atravessamento de rvores
De certa forma, os algoritmos de atravessamento
de rvores (lio 24) so casos particulares das
buscas em grafos.
Com efeito, uma rvore um grafo dirigido, com
um vrtice distintoa raize tal que da raiz at
qualquer outro vrtice h exatamente um
caminho.
Numa rvore binria, de cada vrtice saem
quando muito duas arestas.
A busca em profundidade corresponde ao
atravessamento em profundidade e a busca em
largura corresponde ao atravessamento por nvel.
6/1/16 Algoritmos e Estruturas de Dados 24
Algoritmos e Estruturas de Dados

Lio n. 28
Grafos: Prim e Dijkstra
Grafos: Prim e Dijkstra
Conectividade.
rvore de cobertura mnima.
Caminho mais curto.

6/1/16 Algoritmos e Estruturas de Dados 2


Conectividade
Problema da conectividade num grafo (no
dirigido): dados dois ns, h um caminho de
um at ao outro?
Num grafo, uma componente conexa um
subconjunto maximal de vrtices que esto
todos conectados uns com os outros.
Portanto, haver um caminho de um vrtice
para outro se e s se pertencerem mesma
componente conexa.
Problema: calcular as componentes conexas
de um grafo.
6/1/16 Algoritmos e Estruturas de Dados 3
Classe ConnectedComponents
ed
public class ConnectedComponents
{
private int[] id;
private int count;

ConnectedComponents(Graph g)
{
Os clculos fazem-se no
...
}
construtor.

public boolean connected(int v, int w)


{
return id[v] == id[w];
}
Isto parecido com o Union-Find,
mas recorde que nesse caso,
public int id(int v) tnhamos conectividade dinmica,
{ isto , as ligaes iam chegando.
return id[v]; Aqui, as arestas j esto todas no
} grafo quando calculamos as
componentes conexas.
...

6/1/16 Algoritmos e Estruturas de Dados 4


Calculando as componentes
Calculamos as componentes usando DFS,
sucessivamente:
public ConnectedComponents(Graph g) Isto o construtor da classe
{ ConnectedComponents.
id = new int[g.nVertices];
for (int i = 0; i < id.length; i++)
id[i] = -1;
int n = 0;
for (int i = 0; i < id.length; i++)
if (id[i] == -1)
{
DepthFirstSearch dfs = new DepthFirstSearch(g, i);
for (int x : dfs.result())
id[x] = n;
n++;
}
count = n;
}
6/1/16 Algoritmos e Estruturas de Dados 5
Enumerando os vrtices de uma componente
Usamos sacos internamente e devolvemos um
objeto itervel:
public Iterable<Integer> component(int x)
{
Bag<Integer> result = new Bag<>();
for (int i = 0; i < id.length; i++)
if (id[i] == x)
result.add(x);
return result;
}

6/1/16 Algoritmos e Estruturas de Dados 6


Encerramento de autoestradas
Considere a seguinte situao ficcional:
Um certo pas dispe de uma rede de autoestradas sem portagem,
que liga todas as cidades. Por causa da crise, o governo vai ter de
encerrar algumas dessas autoestradas, para poupar nos custos de
manuteno. No entanto, para evitar problemas sociais, tem de
garantir que todas as cidades continuam servidas por pelo menos
uma autoestrada e que de cada cidade se pode ir, sempre por
autoestrada, at qualquer outra cidade (ainda que dando uma volta
maior que antes...) tal como antes da crise.
Que autoestradas deve o governo encerrar, de maneira a gastar o
mnimo com a manuteno, admitindo que o custo da manuteno
proporcional ao comprimento da autoestrada?
Ou, inversamente, que autoestradas deve manter, de maneira a
gastar o menos possvel com a manuteno?
Supomos que a rede um grafo, onde as cidades so os vrtices e
as autoestradas so as arestas.

6/1/16 Algoritmos e Estruturas de Dados 7


rvore de cobertura mnima
Uma rvore de cobertura de um grafo um subgrafo
conexo desse grafo, subgrafo esse sem ciclos.
Um ciclo um caminho onde o ltimo vrtice igual
ao primeiro.
Em teoria de grafos, uma rvore , precisamente, um
grafo conexo sem ciclos.
Num grafo pesado, uma rvore de cobertura mnima
uma rvore de cobertura cujo peso menor ou
igual ao peso de qualquer outra rvore de cobertura.
O peso de uma rvore (ou de um grafo, em geral) a
soma dos pesos das arestas.
Problema da rvore de cobertura mnima: dado um
grafo no dirigido, conectado, pesado, calcular uma
rvore de cobertura
6/1/16
mnima.
Algoritmos e Estruturas de Dados 8
Propriedades das rvores de cobertura
Acrescentar uma aresta a uma rvore de
cobertura cria um ciclo.
Remover uma aresta de uma rvore de
cobertura parte-a em duas componentes (que
so rvores).

6/1/16 Algoritmos e Estruturas de Dados 9


Propriedade de corte
Num grafo, um corte uma partio do
conjunto de vrtices em dois conjuntos no
vazios.
Uma aresta de corte uma aresta que liga um
vrtice de um desses conjuntos a um vrtice
do outro.
Propriedade de corte: qualquer que seja o
corte, a aresta de corte de peso mnimo
pertence rvore de cobertura mnima.

6/1/16 Algoritmos e Estruturas de Dados 10


Algoritmo de Prim
Constri a rvore de cobertura acrescentando uma
aresta em cada passo.
Em cada passo, a parte da rvore de cobertura j
construda define um corte: de um lado os ns que
esto na rvore, do outro os que no esto.
Em cada passo, acrescenta-se rvore de cobertura
em construo a aresta de corte de peso mnimo
(porque essa tem de pertencer rvore de
cobertura mnima).
Comea-se com um corte em que dum lado est um
vrtice qualquer e do outro os outros vrtices
todos; nessa altura a rvore est vazia.
Havendo N vrtices, o algoritmo termina quando a
rvore tiver N-1 arestas.
6/1/16 Algoritmos e Estruturas de Dados 11
Fila com prioridade das arestas de corte
Quando acrescentamos uma aresta de corte
rvore em construo, acrescentamos um vrtice ao
conjunto de vrtices que pertencem rvore j
construda.
Porventura partiro desse vrtice novas arestas de
corte.
Essas arestas de corte so acrescentadas a uma fila
com prioridade, comparando por peso.
Assim, da prxima vez, escolhemos como aresta de
corte a acrescentar a aresta mais prioritria da fila
(isto , a menos pesada).
Mas ateno: algumas das arestas na fila tero
deixado de ser arestas de corte, porque entretanto o
corte mudou!
6/1/16 Algoritmos e Estruturas de Dados 12
Classe abstrata MinimumSpanningTree
A classe abstrata especifica a funo mst, que
devolve as arestas da rvore de cobertura
mnima:
public abstract class MinimumSpanningTree
{
public final Graph g;

public MinimumSpanningTree(Graph g)
{
assert new ConnectedComponents(g).count == 1;
this.g = g;
}

public abstract Iterable<Edge> mst();


}
Fazemos assim, porque haver vrios algoritmos para calcular a
rvore de cobertura mnima, ainda que aqui s estudemos um.
6/1/16 Algoritmos e Estruturas de Dados 13
Classe Prim
A classe Prim implementa o algoritmo de Prim:
class Prim extends MinimumSpanningTree
{
private Bag<Edge> mst = null;
private boolean[] marked;
private PriorityQueue<Edge> queue;

privage final Comparator<Edge> byWeight = Edge::compareByWeight;

public Prim (Graph g)


{
super(g); A rvore construda no construtor, por meio da
prim();
funo prim.
}

private void prim()


{
...
}
}

6/1/16 Algoritmos e Estruturas de Dados 14


Funo prim
Observe:
private void prim()
{
mst = new Bag<>();
queue = new PriorityQueue<>(byWeight.reversed());
marked = new boolean[g.nVertices];
visit(g, 0) ;
while (!queue.isEmpty()) Os vrtices marcados so os que j
{ esto na rvore; por isso, as arestas
Edge e = queue.remove(); respetivas no so elegveis, isto , j
if (marked[e.to]) no so arestas de corte.
continue;
mst.add(e); private void visit(Graph g, int v)
visit(g, e.to); {
} marked[v] = true;
} for (Edge e : g.adj(v))
if (!marked[e.to])
public Iterable<Edge> mst()
queue.insert(e);
{
}
return mst;
6/1/16 } Algoritmos e Estruturas de Dados 15
Problema do caminho mais curto
O problema do caminho mais curto num grafo
pesado consiste em descobrir se h um
caminho de um vrtice dado a outro e,
havendo pelo menos um, calcular de entre
todos esses o de menor peso.
Acessoriamente, calculamos o menor caminho
do vrtice dado a todos os outros vrtices do
grafo que sejam alcanveis e o peso de cada
um desses caminhos.
Admitimos nesta fase que os pesos so
positivos.
6/1/16 Algoritmos e Estruturas de Dados 16
Classe abstrata ShortestPaths
A classe abstrata especifica funes para
realizar os clculos de que falmos:
public abstract class ShortestPaths
{
public final Graph g;
public final int start;

public ShortestPaths(Graph g, int start)


{
A funo shortestPathsTree devolve as arestas da
this.g = g;
rvore que representa os caminhos mais curtos; a
this.start = start;
} funo pathTo devolve a sequncia de arestas do
caminho mais curto desde start at ao argumento.
public abstract double distanceTo(int v);
public abstract boolean hasPathTo(int v);
public abstract Iterable<Edge> pathTo(int v);
public abstract Iterable<Edge> shortestPathsTree();
}
6/1/16 Algoritmos e Estruturas de Dados 17
Algoritmo de Dijkstra
Constri a rvore dos caminhos mais curtos, passo a passo,
comeando pelo vrtice start.
Em cada passo, o algoritmo ter calculado os caminhos mais
curtos de start at cada um dos vrtices presentes na rvore
do caminhos mais curtos em construo.
Ter calculado tambm, a distncia de start at cada um
desses vrtices.
Em cada passo, o algoritmo acrescenta rvore uma aresta,
cuidadosamente selecionada, de entre as arestas de corte.
A aresta selecionada a que leva ao vrtice mais prximo de
start, de entre todos dos vrtices de destino das arestas de
corte.
Desta forma se garante que os vrtices so acrescentados
rvore pela ordem da sua distncia a start; logo, o caminho
registado na rvore o caminho mais curto.
6/1/16 Algoritmos e Estruturas de Dados 18
Classe Dijkstra
A classe Dijkstra implementa o algoritmo de Dijkstra:
class Dijkstra extends ShortestPaths
{
private Edge[] edgeTo;
private double[] distanceTo;
private boolean[] marked;
private Bag<Edge> spt;

public Dijkstra(Graph g, int start)


{
super(g, start);
marked = new boolean[g.nVertices];
edgeTo = new Edge[g.nVertices];
spt = new Bag<>();
dijkstra(start); Os clculos propriamente ditos so
} feitos na funo dijkstra.
private void dijkstra(int v)
{
...
}
}
6/1/16 Algoritmos e Estruturas de Dados 19
Funo dijkstra
Observe:
private void dijkstra(int v) Os vrtices marcados so os que j
{ esto na rvore dos caminhos mais
marked[v] = true; curtos, em construo.
edgeTo = new Edge[g.nVertices];
distanceTo = new double[g.nVertices];
Arrays.fill(distanceTo, Double.POSITIVE_INFINITY);
distanceTo[v] = 0;
while (true)
{ A funo dijkstraEdge seleciona a
Edge e = dijkstraEdge(); prxima aresta a acrescentar rvore;
if (e == null) se no houver, o algoritmo termina.
break;
spt.add(e);
distanceTo[e.to] = distanceTo[e.from] + e.weight;
marked[e.to] = true;
edgeTo[e.to] = e;
}
}
6/1/16 Algoritmos e Estruturas de Dados 20
Funo dijkstraEdge
Nesta implementao, no muito inteligente, o
algoritmo enumera todas as arestas para descobrir
quais so arestas de corte.
De entre essas, escolhe a que d a distncia mnima:
private Edge dijkstraEdge()
{
Edge result = null;
double min = Double.POSITIVE_INFINITY;
for (Edge e : g.edges())
if (marked[e.from] && !marked[e.to])
if (min > distanceTo[e.from] + e.weight)
{
min = distanceTo[e.from] + e.weight;
result = e;
}
return result;
}
6/1/16 Algoritmos e Estruturas de Dados 21
Classe Dijkstra, restantes funes
So anlogas a outras que j vimos noutras classes:
public double distanceTo(int v)
{
return distanceTo[v];
}

public boolean hasPathTo(int v)


{
return marked[v];
}

public Iterable<Edge> pathTo(int v)


{
Stack<Edge> result = null;
if (marked[v])
{
result = new Stack<>();
for (int x = v; x != start; x = edgeTo[x].from)
result.push(edgeTo[x]);
}
return result;
}

public Iterable<Edge> shortestPathsTree()


{
return spt;
}

6/1/16 Algoritmos e Estruturas de Dados 22


Anlise da complexidade
O tempo de execuo da funo prim dominado
pela fila com prioridade.
No pior dos casos todas as arestas entram na fila e
todas saem.
Logo, havendo E arestas, a complexidade do
algoritmo de Prim proporcional a E log E.
O ciclo while da funo dijkstra d um passo por
cada aresta.
Em cada passo, enumera todas as arestas, para
calcular a distncia mnima.
Logo, havendo E arestas, a complexidade do
algoritmo de Dijkstra, na verso que estudmos E2.

6/1/16 Algoritmos e Estruturas de Dados 23


Algoritmo de Dijkstra E log V
No algoritmo de Dijkstra, no pudemos usar filas com
prioridade para as arestas de corte, porque a comparao
feita com as distncias ao ponto de destino, as quais variam de
passo para passo, e no com os pesos, que so fixos.
Por isso, em cada passo, tivemos de inspecionar todas as
arestas, ainda que descartando as que no eram arestas de
corte.
No entanto, usando uma fila com prioridades indexada para as
arestas de corte consegue-se calcular a prxima aresta com
complexidade Log V, sendo V o nmero de vrtices.
Assim, a complexidade do algoritmo de Dijkstra nessa
variante E Log V.
As filas com prioridade indexadas so uma variante das filas
com prioridade onde possvel fazer variar a prioridade de
um elemento da fila, acessvel atravs de uma chave.
6/1/16 Algoritmos e Estruturas de Dados 24

You might also like