Вы находитесь на странице: 1из 493

Algoritmos e Estruturas de Dados

Prof. Marilton Sanchotene de Aguiar


PPGC/CDTEC/UFPEL

1
Sumário
-  Estruturas Lineares (slide 3)
-  Estruturas Não Lineares (slide 57)
-  Grafos (slide 211)
-  Classificação de Dados (slide 360)
-  Compressão de Dados (slide 421)

2
Parte 1

ESTRUTURAS LINEARES

3
Introdução
Uma lista linear é um conjunto de n elementos (de informações)
x1, x2, …, xn,
cuja propriedade estrutural envolve as posições relativas de seus elementos.

Supondo n > 0, tem-se:


i.  x1 é o primeiro elemento
ii.  para 1 < k < n; xk é precedido por xk-1 e seguido por xk+1
iii.  xn é o último elemento.

4
Operações
Algumas operações que se pode querer realizar sobre listas lineares:
1.  Ter acesso a xk, k qualquer, a fim de examinar ou alterar o conteúdo de seus campos
2.  Inserir um elemento novo antes ou depois de xk
3.  Remover xk
4.  Combinar 2 ou mais listas lineares em uma só
5.  Quebrar uma lista linear em duas ou mais
6.  Copiar uma lista linear em um outro espaço
7.  Determinar o no. de elementos de uma lista linear

5
Operações (cont.)
— Operações 1., 2. e 3. para k = 1 e k = n são muito importantes. Nomes especiais como pilha ou
fila são dados para as listas lineares conforme a maneira essas operações são realizadas, como
será visto em breve.
— Em uma aplicação, raramente são necessárias todas as operações simultaneamente. A maneira
de representar listas lineares depende da classe de operações mais frequentes.
— Não existe, em geral, uma única representação para a qual todas as operações são eficientes.
Por exemplo, não existe uma representação para atender as seguintes operações de maneira
eficiente:
— Ter acesso fácil ao xk, para k qualquer
— Inserir ou remover elementos em qualquer posição da lista linear

6
Pilha (ou stack)
— É uma lista linear em que todas as inserções e remoções são feitas numa mesma extremidade
da lista linear. Esta extremidade se denomina topo (em inglês top) ou lado aberto da pilha.
— As operações definidas para uma pilha incluem:
— Verificar se a pilha está vazia
— Inserir um elemento na pilha (empilhar ou push), no lado do topo.
— Remover um elemento da pilha (desempilhar ou pop), do lado do topo.
— Como o último elemento que entrou na pilha será o primeiro a sair da pilha, a pilha é conhecida
como uma estrutura do tipo LIFO (Last In First Out).

7
Exemplos
1.  Na vida real: pilhas de pratos numa cafeteria (acréscimos e retiradas de pratos sempre feitos
num mesmo lado da pilha - lado de cima)
2.  Na execução de uma programa: uma pilha pode ser usada na chamada de procedimentos, para
armazenar o endereço de retorno (e os parâmetros reais). A medida que procedimentos
chamam outros procedimentos, mais e mais endereços de retorno devem ser empilhados. Estes
são desempilhados a medida que os procedimentos chegam ao seu fim.
3.  Na avaliação de expressões aritméticas, a pilha pode ser usada para transformar expressões em
notação polonesa ou pós-fixa. A pilha também pode ser usada na avaliação de expressões
aritméticas em notação polonesa.

8
Fila (ou queue)
— É uma lista linear em que todas as inserções de novos elementos são realizadas numa
extremidade da lista e todas as remoções de elementos são feitas na outra extremidade da
lista.
— As filas são estruturas do tipo FIFO (First In First Out). Segundo Knuth, elementos novos são
inseridos no lado R (Rear, ou fim da fila) e a retirada ocorre no lado F (Front, ou frente ou
começo da fila).
— Exemplo: Num sistema operacional, os processos prontos para entrar em execução (aguardando
apenas a disponibilidade da CPU) são geralmente mantidos numa fila.
— Exemplo: Existe um tipo de fila em que as retiradas de elementos da fila depende de um valor
chamado prioridade de cada elemento. O elemento de maior prioridade entre todos os
elementos da fila é o próximo a ser retirado. Tal fila recebe o nome de fila de prioridade.

9
Representação de Listas Lineares
-  Alocação Sequencial: Os elementos da lista linear ocupam posições consecutivas da memória
do computador.
-  Pilha:
x[1] x[2] x[3] x[m]

T
T = topo da pilha
Convenção adotada para pilha vazia: T = 0

10
Pilha: Operações
—  Inserir um novo elemento de valor Y na pilha:
T←T+1
SE T > m ENTÃO ERRO_PILHA_CHEIA
SENÃO
x[T] ← Y
—  Remover um elemento da pilha, colocando o valor do elemento retirado em Y:
SE T = 0 ENTÃO ERRO_PILHA_VAZIA
SENÃO
Y ← x[T]
T←T-1

11
Fila: Esquema

x[1] x[2] x[m]

F R
Convenção para fila vazia: R = F
Situação inicial: R = F = 0

12
Fila: Operações
—  Inserir um elemento Y na fila: —  Remover da fila:
R ← R + 1; SE R = F ENTÃO ERRO_FILA_VAZIA
SE R > m ENTÃO ERRO_FILA_CHEIA SENÃO
SENÃO x[R] ← Y F←F+1
Y ← x[F]
SE R = F ENTÃO
F←0
R←0

13
Fila Circular
—  Na fila circular, tudo se passa como se a posição x[m] seja seguida por x[1], ou x[1] seja
precedida por x[m].

Convenção de fila vazia: R = F


Situação inicial: R = F = m

-  Inserir Y na fila circular: -  Remover da fila circular:


SE R = m ENTÃO R ← 1 SE R = F ENTÃO ERRO_FILA_VAZIA
SENÃO R ← R + 1 SENÃO SE F = m ENTÃO F ← 1
SE R = F ENTÃO ERRO_FILA_CHEIA SENÃO F ← F + 1
SENÃO x[R] ← Y Y ← x[F]

14
Considerações sobre Alocação Sequencial
— Por um lado, a alocação sequencial simplifica bastante a implementação dos algoritmos de inserção
e remoção.
— Por outro lado, pode-se constatar a grande dificuldade quando várias estruturas de dados (digamos 3
pilhas e duas filas) devem ser implementadas num espaço sequencial único.
— Temos que dividir a priori o espaço em três partes para implementar cada estrutura de dado em cada
parte do espaço. Esse dimensionamento pode não ser fácil pois podemos desconhecer qual das
estruturas irá crescer mais que outras.
— Assim, podemos chegar a situação em que se esgota o espaço alocado para uma determinada
estrutura de dado enquanto há na verdade espaço sobrando naquelas partes reservadas para as
outras estruturas de dados.
— O rearranjo de espaço é possível mas em geral envolve uma grande movimentação de dados e
portanto é muito custoso. Para compartilhar um mesmo espaço por várias estruturas de dados, uma

15
forma muito elegante é usar uma outra alocação, a chamada alocação ligada ou encadeada.
Alocação Ligada
-  Para usar a alocação ligada, todo o espaço livre é organizado inicialmente numa lista livre, onde
os elementos apresentam dois campos:
-  um chamado info (para armazenar as informações das listas lineares); e,
-  um campo chamado link (para apontar para o próximo elemento).
-  Se x aponta para um elemento, então os seus campos info e link serão indicados por info(x) e
link(x).
-  Por simplicidade, nos exemplos, o campo info será constituído apenas por um valor do tipo
inteiro. Evidentemente, ele pode conter informações mais complexas, dependendo do
problema.

16
-  A lista livre é apontada por uma variável apontadora chamada avail.
-  A idéia é que quando uma lista linear precisa crescer de tamanho (inserção), um elemento livre
é extraído da lista livre e usado pela lista linear.
-  Quando a lista linear não precisa mais de um elemento (remoção), o elemento removido é
devolvido à lista livre.
-  A lista livre pode assim ser compartilhada por várias listas lineares. Esse esquema de alocação
de memória é também conhecido pelo nome de alocação dinâmica de memória

avail info
..." /
link
Pilha
—  Considere uma pilha cujo topo é apontado pela variável T.
Pilha vazia: T = nil
—  Para inserir um novo elemento, que contém a informação Y, na pilha apontada por T
—  push(T, Y):
Aloca(P)
info(P) ← Y
link(P) ← T
T←P
... /

18
Pilha
—  Remover um elemento da pilha apontada por T, colocando a informação do elemento removido
em Y
—  pop (T, Y):
SE T = nil ENTÃO ERRO_PILHA_VAZIA
SENÃO
P←T
T ← link(P)
Y ← info(P)
Libera(P)
... /

19
Fila
Variáveis apontadoras da fila: F e R
Fila vazia: F = R = nil
—  Insere um elemento novo com informação Y na fila
—  InsereFila(F, R, Y):
Aloca(P)
info(P) ← Y
link(P) ← nil
SE R != nil ENTÃO
... /
link(R) ← P
R←P F R
SENÃO
R←P

20
F←P
Fila
—  Remove um elemento da fila, colocando a informação do elemento removido em Y
—  RemoveFila(F, R, Y ):
SE F = nil ENTÃO ERRO_FILA_VAZIA
SENÃO
P←F
F ← link(P)
Y ← info(P)
Libera(P)
SE F = nil ENTÃO R ← nil
... /

21
F R
Listas Circulares Ligadas
PTR
...
primeiro último

—  O último nó aponta de volta para o primeiro nó.


—  O apontador PTR aponta para o último nó; assim sendo, link(PTR) dará acesso ao primeiro nó.
—  Lista vazia: PTR = nil

22
Lista Circular com Cabeça de Lista
H
...
primeiro último

—  O último nó aponta de volta para o primeiro nó.


—  O apontador H aponta para a cabeça da lista.

—  Lista vazia: link(H) = H


H

23
Listas Duplamente Ligadas
—  Um nó possui além do campo info, campos para dois apontadores llink e rlink.
—  Lista vazia: Tanto llink como rlink apontam para a cabeça.

H ...

—  Uma vantagem óbvia é a possibilidade de percorrer a lista nos dois sentidos (tanto para a direita
como para a esquerda).
—  Uma outra característica é a possibilidade de poder remover um elemento qualquer da lista,
conhecendo-se apenas um apontador ao mesmo.

24
Operações
—  RemoveListaDupla(x) —  BuscaListaDupla(H, k)
rlink(llink(x)) ← rlink(x) x ← rlink(H)
llink(rlink(x)) ← llink(x) ENQUANTO
(x != H) E (info(x) != k)
—  InsereListaDupla(H, x)
FAÇA
rlink(x) ←rlink(H)
x ← rlink(x)
llink(rlink(x)) ← x
rlink(H) ← x DEVOLVE x
llink(x) ← H

25
Deques
—  Uma double-ended queue (dequeue, abreviada para deque) implementa uma fila no qual os
elementos podem ser adicionados ou removidos tanto pela cabeça quanto pelo fim da fila.
—  Tem dois sub-tipos: i) uma deque restrita na entrada é aquela onde a remoção pode ser feita
nas duas pontas, mas a inserção pode ser somente feita em uma das pontas; e ii) uma deque
restrita na saída é aquela onde a inserção pode ser feita em ambas pontas, mas a remoção
pode ser somente feita em apenas uma das pontas.
—  Ambos casos, filas e pilhas podem ser considerados como especializações de deques, e podem
ser implementadas utilizando deques.

26
Exercícios
1.  Seguindo as notações utilizadas para as listas duplamente encadeadas (com cabeça), escreva os
algoritmos para:
1.  Inserir um elemento x no fim da lista
2.  Remover um elemento x do início da lista
3.  Remover um elemento x do fim da lista
4.  Trocar as informações entre dois elementos da lista
2.  Escreva um algoritmo, baseado no bubblesort para a ordenação das informações dos elementos
de uma lista duplamente encadeada (considere que a informação armazenada seja uma chave).
3.  Modifique o algoritmo de inserção na lista duplamente encadeada do slide 25 de modo que a
inserção seja feita sempre em ordem crescente do campo info(x).

27
Pesquisa Calculada – Hashing
—  É considerada uma estrutura eficiente para implementar dicionários.
—  Embora a busca por um elemento em uma tabela hash possa demorar tanto quanto procurar
por um elemento em uma lista ligada, na prática o hash funciona muito bem.
—  Uma tabela hash é uma generalização da noção mais simples de um arranjo (vetor) comum.
—  O endereçamento direto é aplicável quando se tem condições de alocar um arranjo que tem
uma única posição para cada chave (info(x)) possível.
—  Quando o número de chaves realmente armazenadas é pequeno em relação ao número total de
chaves possíveis, as tabelas hash se tornam uma alternativa eficiente para se endereçar
diretamente um arranjo.
—  Em vez de se usar a chave diretamente como um índice de arranjo, o índice é computado a
partir da chave.

28
Tabelas de Endereço Direto
—  Funciona muito bem quando o universo U de chaves é razoavelmente pequeno e quando não há
dois elementos com a mesma chave.
—  Suponha que uma aplicação necessite de um conjunto dinâmico no qual cada elemento tenha
uma chave definida a partir do universo T chave
U = {0, 1, 2, …, m-1}, onde m não é muito grande. 0 / dados
1 / satélite
1 0 2 2
9
3 3
6
2 4 /
7
chaves 3 5 5
5 6 /
8 7 /

29
U 8 8
9 /
Operações sobre T
BUSCA_ENDEREÇO_DIRETO (T, k)
DEVOLVE T[k]

INSERE_ENDEREÇO_DIRETO (T, x)
T[chave(x)] ← x

REMOVE_ENDEREÇO_DIRETO (T, x)
T[chave(x)] ← nil
Lembre que estamos chamando
info(x) de chave(x)

30
-  Para algumas aplicações, os elementos no conjunto dinâmico podem ser armazenados na
própria tabela de endereço direto.
-  Ou seja, em vez de armazenar a chave e os dados satélite de um elemento em um objeto
externo à tabela, com um ponteiro de uma posição na tabela até ao objeto, pode-se armazenar
o objeto na própria posição, economizando espaço.
-  Em alguns casos é desnecessário armazenar o campo chave pois a tabela já tem armazenado
este índice. Neste caso, deve-se ter especial cuidado para saber se a posição está vazia.
Tabelas HASH
—  Se o universo U é grande, o armazenamento em uma tabela T de tamanho |U | pode ser
impraticável, ou mesmo impossível, em virtude da memória disponível.
—  Por outro lado, o conjunto de chaves que serão armazenadas pode ser tão pequeno em relação
a U que a maior parte do espaço alocado por T seria desperdiçada.
—  Neste caso, uma tabela hash exige muito menos espaço de armazenamento que uma tabela de
endereço direto.
—  Com o endereçamento direto, um elemento com chave k é armazenado na posição k. No caso
do hash, esse elemento é armazenado na posição h(k).
—  h mapeia o universo U de chaves nas posições de uma tabela hash T[0 … m-1]:
h: U → {0, 1, …, m-1}.
Diz-se que k efetua o hash para a posição h(k), ou que h(k) é o valor hash da chave k.

32
— 
-  Reduz-se o intervalo de índices de arranjos que precisam ser tratados, em vez de |U | valores
precisam-se manipular apenas m valores, e os requisitos de armazenamento são também
reduzidos.
T
0 /
U /
h(k1)
/
k1 /
K k2 h(k2)=h(k3)
(chaves reais) k3 /
/

33
k4
h(k4)
m-1 /
Colisões
—  Nesta proposta, duas chaves podem ter o hash na mesma posição.
—  A solução ideal seria escolher uma função hash ideal parecendo “aleatória”, evitando a colisão
ou minimizando.
—  Entretanto, é claro que uma função hash deve ser determinística, no sentido que uma dada
entrada k sempre deve produzir a mesma saída h(k).
—  Porém, tendo em vista que |U | >> m, devem existir duas chaves que tenham o mesmo valor
hash. Por isso, estudam-se métodos para resolver as colisões que ocorrerem.

34
Resolução de colisões por encadeamento
—  No encadeamento, colocam-se todas os elementos que efetuam o hash para a mesma posição
em uma lista ligada.
—  A posição j contém um ponteiro para o início da lista de todos os elementos armazenados que
efetuam hash para j ; se não houver nenhum elemento, a posição j conterá nil.
T
U
/
k1 k4 /
k1
/
k4
K /
k5
(chaves k7 /
reais) k2 k5 k2 k7 /
k3
k6 k8 /

35
k3 /

k8 k6 /
/
Operações
INSERE_HASH_ENCADEADO (T, x)
INSERE_INICIO_LISTA(T[h(chave(x))], x)

BUSCA_HASH_ENCADEADO (T, k)
BUSCA_LISTA(T[h(k)], k)

REMOVE_HASH_ENCADEADO (T, x)
REMOVE_LISTA(T[h(chave(x))], x)

36
Operações (cont.)
INSERE_INICIO_LISTA(T, x) BUSCA_LISTA(T, k)
rlink(x) ← T x←T
T←x ENQUANTO (x != nil) E (chave(x) != k)
FAÇA x ← rlink(x)
DEVOLVE x

REMOVE_LISTA(T, x)
y←T
ENQUANTO (y != nil) E (rlink(y) != x) FAÇA y ← rlink(y)
SE (y != nil) ENTÃO rlink(y) ← rlink(rlink(y))
SENÃO ERRO_ELEMENTO_INEXISTENTE

37
Funções HASH
—  Uma função hash de boa qualidade satisfaz a hipótese do hash uniforme simples: cada chave
tem igual probabilidade de efetuar o hash para qualquer das m posições, não importando a
posição para onde foi feito o hash de qualquer outra chave.
—  Em geral não é possível verificar essa condição, pois é raro conhecer a distribuição de
probabilidades segundo a qual as chaves são obtidas.
—  Algumas aplicações de funções hash poderiam exigir propriedades mais fortes que aqueles
usualmente utilizadas em hash uniforme simples. Por exemplo, propriedades que agrupariam
chaves conforme alguma característica em comum (clustering).

38
O método de divisão
—  Mapeia-se uma chave k para uma tabela hash de m posições, tomando o resto de k divido por
m.
h(k) = k mod m
—  Exemplo:
Função hash (h): h(k) = k mod 12
Tamanho da tabela (m): 12
Para inserir a chave 100 (k): h(100) = 100 mod 12 = 4

—  Por exigir uma única operação de divisão, o hash por divisão é bastante rápido.
—  A prática não recomenda que m seja uma potência de 2.

39
Uma boa função hash...
—  Um número primo não muito próximo de uma potência de 2 frequentemente é uma boa escolha
para m.
—  Exemplo: para armazenar aproximadamente 2000 chaves, examinando em média 3 elementos
ao realizar uma pesquisa malsucedida.
—  Um número primo próximo a 2000/3, não muito próximo de uma potência de 2 é 701.

Então a função hash seria:


h(k) = k mod 701

40
O método de multiplicação
—  Opera em duas etapas:
1.  Multiplica-se a chave k por uma constante A no intervalo 0< A< 1 e se extrai a parte
fracionária de kA.
2.  Multiplica-se este valor por m e toma-se o piso do resultado (arredonda-se para baixo).
h(k) = #m (kA mod 1)$%
—  Uma vantagem deste método é que o valor de m não é crítico. Em geral escolhe-se um valor que
seja uma potência de 2.
—  Embora funcione para qualquer A, funciona melhor com alguns valores. Knuth sugere que:
A ( 5 - 1)/2 = 0,618033989

41
-  Exemplo:
k = 123456
m = 1024
A = 0,618033989

h(k) = #1024 * (123456 * 0,618033989 mod 1)$


h(k) = #1024 * (76300,004145984 mod 1)$
h(k) = #4,245487616$
h(k) = 4
O método da dobra
—  As chaves são interpretadas como uma sequência de dígitos escritos num pedaço de papel. Ele
consiste em “dobrar” esse papel, de maneira que os dígitos se superponham sem levar em
consideração o “vai um”.
—  O processo é repetido até que os dígitos formem um número menor que o tamanho da tabela
hash.
—  É importante destacar que o método da dobra também pode ser usado com números binários.
Nesse caso, ao invés da soma, deve-se realizar uma operação de "ou exclusivo", pois operações
de "e" e de "ou" tendem a produzir endereços base concentrados no final ou no início da tabela.

43
7 1 3 4 8 7 9 0

7+0=7
1 + 9 = 10
7 1 3 4 8 7 9 0 3 + 7 = 10
4 + 8 = 12

2 0 0 7

2+7=9
2 0 0 7 0+0=0 Leitura Adicional:
Hash universal (Cormem, p188)

44
0 9
Exercícios
1.  Demonstre a inserção das chaves 5, 28, 19, 15, 20, 33, 12, 17, 10 em uma tabela hash com
colisões resolvidas por encadeamento. Seja a tabela com 9 posições, e seja a função hash
h(k) = k mod 9.
2.  Considere uma tabela hash de tamanho m = 1000 e a função hash correspondente h(k) igual
a #m (kA mod 1)$ para A = ( 5 – 1)/2. Calcule as localizações para as quais as chaves 61, 62,
63, 64 e 65 estão mapeadas.
3.  Mostre como ficam inseridas as chaves abaixo utilizando o método da dobra para uma tabela
com 10 posições.
22717504, 72044182, 81447277, 26913128 e 62404887

45
Endereçamento Aberto
—  Todos os elementos estão armazenados na própria tabela hash, ou seja, cada entrada contém
um elemento do conjunto dinâmico ou nil.
—  Ao procurar por um elemento, examinam-se sistematicamente as posições da tabela até
encontrar o elemento desejado ou certificar que o elemento não está na tabela.
—  Não existe nenhuma lista ou elemento armazenado fora da tabela.
—  Neste caso, a tabela pode ficar cheia, não possibilitando inserções adicionais.
—  Ao invés de seguir ponteiros, calcula-se a sequência de posições que serão examinadas.

46
Endereçamento Aberto (cont.)
—  Para fazer uma inserção, examina-se sucessivamente (sondagem) a tabela hash até que seja
encontrada uma posição vazia na qual seja possível inserir a chave.
—  A sequência de posições examinadas dependerá da chave que será inserida e não da ordem fixa
0, 1, …, m-1.
—  Para determinar quais serão as posições sondadas, estende-se a função hash com o objetivo de
incluir o número de sondagens (a partir de 0) como uma segunda entrada.
—  Exige-se, para toda chave k, a sequência de sondagem
〈h(k,0), h(k,1), …, h(k, m-1)〉%

47
-  No algoritmo abaixo, supõe-se que os elementos da tabela hash T são chaves sem dados
satélite. Cada posição contém uma chave ou nil ( se a posição é vazia).

INSERE_EA(T, k)
i 0
ENQUANTO i < m FAÇA
j h(k,i)
SE T[j] = nil ENTÃO
T[j] k
DEVOLVE j
SENÃO i i+1
DEVOLVE ERRO_TABELA_CHEIA
-  O algoritmo para procurar pela chave k efetua
a sondagem da mesma sequência de BUSCA_EA(T, k)
posições que o algoritmo de inserção anterior i 0
quando a chave k foi inserida. j h(k,i)
-  A pesquisa pode terminar sem sucesso ao ENQUANTO T[j] != nil E i < m FAÇA
encontrar uma posição vazia, pois k teria sido SE T[j] = k ENTÃO
inserido ali e não mais adiante na sondagem. DEVOLVE j
-  O algoritmo ao lado toma como entrada uma SENÃO
tabela hash T e uma chave k, retornando j se i i+1
a posição contém a chave k, retornando nil j h(k,i)
caso contrário. DEVOLVE nil
O problema de remover
—  A eliminação é difícil em uma tabela de endereçamento aberto.
—  Ao se eliminar uma chave da posição i, não se pode simplesmente assinalar essa posição como
vazia, armazenando nil em sua posição.
—  Isso poderia tornar impossível recuperar qualquer chave k, se durante a inserção dessa chave
tivesse sido sondado a posição i e encontrado esta posição ocupada.
—  Uma solução seria assinalar esta posição como DELETADA em lugar de nil.
—  Então o procedimento INSERE_EA deve ser modificado para tratar tal posição como se estivesse
vazia.
—  Três técnicas podem ser utilizadas para realizar a sondagem: sondagem linear, sondagem
quadrática e hash duplo.

50
Sondagem Linear
—  Dada uma função hash comum h’ : U→{0, 1, ..., m-1}, a qual é chamada de função hash auxiliar, o
método de sondagem linear usa a função hash h(k,i) = (h'(k) + i) mod m, para i=0, 1, …, m-1.
—  Dada a chave k, a primeira posição sondada é T[h'(k)], isto é, a posição dada pela função hash
auxiliar. Em seguida será sondada T[h'(k)+1] e assim por diante, até a posição T[m-1]. Depois,
T[0], T[1], …, até finalmente a sondagem de T[h'(k)-1].
—  A sondagem linear é fácil de implementar, mas sofre pelo agrupamento primário, onde longas
sequências de posições ocupadas são construídas, aumentando o tempo médio de pesquisa.

51
Sondagem Quadrática
—  A sondagem quadrática utiliza uma função hash da forma
h(k,i) = (h'(k)+c1i+c2i2) mod m,
onde h' é a função hash auxiliar, c1 e c2 ≠ 0 são constantes auxiliares e i=0, 1, ..., m-1.
—  A posição inicial sondada é T[h'(k)]; posições posteriores sondadas são deslocadas por
quantidades que dependem de forma quadrática do número da sondagem i.

52
Hash Duplo
—  Considerado como um dos melhores métodos de sondagem para o endereçamento aberto,
porque as permutações produzidas têm muitas características de permutações escolhidas
aleatoriamente.
—  O hash duplo usa uma função hash da forma
h(k,i)=(h1(k) + ih2(k)) mod m,
onde h1 e h2 são funções hash auxiliares.
—  A posição inicial sondada é T[h1(k)]; posições de sondagem sucessivas são deslocadas a partir
de posições anteriores pela quantidade h2(k), módulo m.
—  Diferente do caso da sondagem linear ou quadrática, aqui a sequência de sondagem depende da
chave k de duas maneiras, pois a posição de sondagem inicial, o deslocamento, ou ambos
podem variar.

53
Exemplo
—  Inserção por hash duplo: 0
/
79
1
—  Uma tabela de tamanho 13 com: 2 /
h1(k) = k mod 13 3 /
69
h2(k)=1+(k mod 11) 4
5 98
6 /
—  Para inserir a chave 14:
7 72
h1(14) = 1 e h2(14) = 4 8 /
9 14
h(14,0)= (h1(14)+0*h2(14)) mod 13 = 1 mod 13 = 1 10 /
h(14,1) = (h1(14)+1*h2(14)) mod 13 = 5 mod 13 = 5 11 50
h(14,2) = (h1(14)+2*h2(14)) mod 13 = 9 mod 13 = 9 12

54
/
Para pesquisar toda a tabela...
—  Escolhe-se m primo e fazer:
h1(k) = k mod m
h2(k)=1+(k mod m')
onde m' é escolhido com um valor ligeiramente menor que m (por exemplo m-1).

—  Exemplo:
Se k = 123456, m = 701 e m' = 700, tem-se h1(k) = 80 e h2(k) = 257; assim a primeira
sondagem ocorre na posição 80, e depois cada 257-ésima posição (módulo m) é
examinada até a chave ser encontrada ou todas as posições serem examinadas.

55
Exercícios
1.  Considere a inserção das chaves 10, 22, 31, 4, 15, 28, 17, 88, 59 em uma tabela hash de
comprimento m=11 usando endereçamento aberto com a função hash primária
h'(k) = k mod m.
Ilustre o resultado da inserção destas chaves com o uso da sondagem linear, empregando a
sondagem quadrática com c1=1 e c2=3, e com a utilização do hash duplo com
h2(k) = 1 + (k mod (m-1)).
2.  Escreva o algoritmo para REMOVE_EA da forma apresentada no slide 50 e modifique INSERE_EA
para tratar o valor especial DELETADA.

56
Parte 2

ESTRUTURAS NÃO-LINEARES

57
Árvores de Pesquisa Binária (APB)
—  São estruturas de dados que permitem diversas operações de conjuntos dinâmicos, incluindo
BUSCA, MÍNIMO, MÁXIMO, PREDECESSOR, SUCESSOR, INSERE e REMOVE.
—  Utilizada também como dicionário ou como fila de prioridades.
—  As operações sobre árvores de pesquisa binária demoram tempo proporcional à altura da
árvore. No caso de uma árvore completa de n nós, são executadas em tempo O(lg n) no pior
caso.

Recurso de Apoio
http://webdiis.unizar.es/asignaturas/EDA/AVLTree/avltree.html

58
Definições
—  Uma árvore de pesquisa binária é organizada, conforme seu nome sugere, em uma árvore
binária, como mostra a Figura.
raiz(T) raiz(T)
5 2
Uma árvore de pesquisa
3 7 3 binária menos eficiente,
com altura 4 e com
2 5 8 7 as mesmas chaves.

Uma árvore de pesquisa 5 8


binária em 6 nós
com altura 2.

59
5
Definições (cont.)
—  Uma árvore deste tipo pode ser representada por uma estrutura de dados encadeada em que
cada nó é um objeto.
—  Além de um campo chave e de dados satélite, cada nó contém campos esquerdo, direito e pai
que apontam para os nós correspondentes a seu filho da esquerda, seu filho da direita e a seu
pai, respectivamente.
—  Se um filho ou o pai estiver ausente, o campo apropriado conterá nil. O nó raiz é o único nó da
árvore cujo campo pai é nil.
—  Propriedade:
Seja x um nó em uma APB.
Se y é um nó na subárvore esquerda de x, então chave(y) ≤ chave(x).
Se y é um nó na subárvore direita de x, então chave(x) ≤ chave(y).

60
Percurso
—  A propriedade das APB permite imprimir todas as chaves em uma APB em sequência ordenada, por
meio de um algoritmo recursivo bastante simples, chamado de percurso de árvore em ordem.
—  Este nome deriva do fato de que a chave raiz de uma subárvore é impressa entre os valores de sua
subárvore esquerda e aqueles de sua subárvore direita.
—  Para realizar o percurso e imprimir todos os elementos a partir da raiz da árvore binária T, chama-se
PERCURSO_EM_ORDEM(raiz(T)).

PERCURSO_EM_ORDEM(x)
SE x != nil ENTÃO
PERCURSO_EM_ORDEM(esquerda(x))
IMPRIME chave(x)
PERCURSO_EM_ORDEM(direita(x))

61
Percurso (cont.)
-  De modo semelhante, um percurso de árvore em pré-ordem imprime a raiz antes dos valores em
uma ou outra subárvore, e um percurso de árvore em pós-ordem imprime a raiz depois dos valores
contidos em suas subárvores.

PERCURSO_PRÉ_ORDEM(x) PERCURSO_PÓS_ORDEM(x)
SE x != nil ENTÃO SE x != nil ENTÃO
IMPRIME chave(x) PERCURSO_PÓS_ORDEM(esquerda(x))
PERCURSO_PRÉ_ORDEM(esquerda(x)) PERCURSO_PÓS_ORDEM(direita(x))
PERCURSO_PRÉ_ORDEM(direita(x)) IMPRIME chave(x)

62
Exercícios
1.  Mostre o resultado da impressão dos algoritmos PERCURSO_EM_ORDEM,
PERCURSO_PRÉ_ORDEM e PERCURSO_PÓS_ORDEM, a partir da raiz, para as árvores exemplo do
slide 59.
2.  Monte APBs de altura 2, 3, 4, 5 e 6 sobre o conjunto de chaves 1, 4, 5, 10, 16, 17 e 21.

63
Consultas em uma APB
15

6 18

3 7 17 20

2 4 13

9
BUSCA(13): 15 → 6 → 7 → 13 SUCESSOR(15): 17
MÍNIMO(raiz(T)): 2 PREDECESSOR(6): 4
MÁXIMO(raiz(T)): 20 SUCESSOR(13): 15

64
Como pesquisar
—  Dado um ponteiro para a raiz da árvore e uma chave k, BUSCA retorna um ponteiro para um nó
com chave k, se existir; caso contrário, ele retorna nil.

BUSCA(x, k) BUSCA_ITERATIVA(x, k)
SE x = nil OU k = chave(x) ENTÃO DEVOLVE x ENQUANTO x != nil E k != chave(x) FAÇA
SE k < chave(x) SE k < chave(x)
ENTÃO DEVOLVE BUSCA(esquerda(x), k) ENTÃO x ← esquerda(x)
SENÃO DEVOLVE BUSCA(direita(x), k) SENÃO x ← direita(x)
DEVOLVE x

65
Mínimo e Máximo
—  Um elemento em uma APB cuja chave é um mínimo sempre pode ser encontrado seguindo-se
ponteiros filhos da esquerda desde a raiz até ser encontrado um valor nil.
MÍNIMO(x)
ENQUANTO esquerda(x) != nil FAÇA x ← esquerda(x)
DEVOLVE x
—  O algoritmo para MÁXIMO é simétrico.
MÁXIMO(x)
ENQUANTO direita(x) != nil FAÇA x ← direita(x)
DEVOLVE x

66
Sucessor e Predecessor
—  Se todas as chaves são distintas, o sucessor de um nó x é o nó com a menor chave maior que
chave(x).

SUCESSOR(x)
SE direita(x) != nil ENTÃO DEVOLVE MÍNIMO(direita(x))
y ← pai(x)
ENQUANTO y != nil E x = direita(y) FAÇA
x←y
y ← pai(y)
DEVOLVE y

67
Exercícios
1.  Suponha que se tem números entre 1 e 1000 em uma APB e se quer procurar pelo número
363. Qual das sequências a seguir não poderia ser a sequência de nós examinados?
1.  2, 252, 401, 398, 330, 344, 397, 363.
2.  924, 220, 911, 244, 898, 258, 362, 363.
3.  925, 202, 911, 240, 912, 245, 363.
4.  2, 399, 387, 219, 266, 382, 381, 278, 363.
5.  935, 278, 347, 621, 299, 392, 358, 363.
2.  Escreva o algoritmo PREDECESSOR.

68
Inserção
- Para inserir um novo valor v em uma APB T, o INSERE( T, z)
y ← nil
procedimento recebe a passagem de um nó z x ← raiz(T)
para o qual chave(z) = v, esquerda(z) = nil e ENQUANTO x != nil FAÇA
direita(z) = nil. y←x
SE chave(z) < chave(x)
- Ele modifica T e alguns dos campos de z de ENTÃO x ← esquerda(x)
tal modo que z é inserido em uma posição SENÃO x ← direita(x)
pai(z) ← y
apropriada na árvore. SE y = nil
ENTÃO raiz(T) ← z
SENÃO SE chave(z) < chave(y)
ENTÃO esquerda(y) ← z
SENÃO direita(y) ← z

69
Eliminação
—  O procedimento para remover um dado nodo z de uma APB toma como argumento um ponteiro
para z. O procedimento considera os três casos a seguir:
1.  Se z não tem filhos:
modifica-se pai(z) para substituir z com nil como seu filho
2.  Se o nó tem somente um filho:
remove-se z criando um novo link entre seu nó filho e seu pai.
3.  Se o nó tem dois nós filhos:
move a chave e os dados satélite do sucessor y de z, que não tem nenhum filho à
esquerda.

70
Caso 1
15

5 16

3 12 20

10 13 18 23
z
6

71
Caso 1 (cont.)
15

5 16

3 12 20

10 18 23

72
Caso 2
15
z
5 16

3 12 20

10 13 18 23

73
Caso 2
15

5 20

3 12 18 23

10 13

74
Caso 3
15
z
5 16

3 12 20

10 13 18 23

y 6

75
Caso 3
15

6 16

3 12 20

10 13 18 23

76
Algoritmo para remoção
REMOVE(T, z)
SE esquerda(z) = nil OU direita(z) = nil
ENTÃO y ← z
SENÃO y ← SUCESSOR(z)
SE esquerda(y) != nil
ENTÃO x ← esquerda(y)
SENÃO x ← direita(y)
SE x != nil ENTÃO pai(x) ← pai(y)
SE pai(y) = nil
ENTÃO raiz(T) ← x
SENÃO SE y = esquerda(pai(y))
ENTÃO esquerda(pai(y)) ← x
SENÃO direita(pai(y)) ← x
SE y != z ENTÃO chave(z) ← chave(y)
DEVOLVE y

77
Exercícios
1.  Faça o esboço das seguintes operações sobre uma APB:
Inserção das chaves: 16, 6, 17, 4, 13, 21, 11, 14, 19, 24, 7 e 8, nesta ordem.
Remoção das chaves: 14, 17 e 6.
2.  Faça o esboço das seguintes operações sobre uma APB, considerando na remoção de
elementos com dois filhos a operação PREDECESSOR.
Inserção: 16, 6, 17, 4, 13, 21, 11, 14, 19, 24, 7 e 8.
Remoção: 14, 17 e 6.
Inserção: 6, 17 e 14.
Remoção: 21, 11 e 7.
Inserção: 7, 11 e 21.
Remoção: 4, 19, 13, 11 e 14.

78
Balanceamento de APBs
—  Como pode ser observado, as APBs têm uma séria desvantagem que pode afetar o tempo
necessário para recuperar um item armazenado.
—  A estrutura da árvore depende fortemente da ordem em que os elementos são inseridos. Ao
inserir elementos já em ordem, a estrutura da árvore será resumida a uma lista encadeada.
—  Idealmente se quer que para qualquer nodo x, a altura da subárvore esquerda seja
aproximadamente igual à altura da subárvore direita.
—  Por isso, espera-se um custo extra de processamento para manter a árvore balanceada,
compensado quando os dados armazenados precisam ser recuperados muitas vezes.

79
Árvores AVL

—  A ideia de manter uma APB balanceada dinamicamente foi proposta por Adelson-Velskii e
Landis (1962).
—  Por definição, uma árvore AVL é uma APB onde a diferença em altura entre as subárvores
esquerda e direita é no máximo 1 (positivo ou negativo).
—  Assim, para cada nodo calcula-se um fator de balanceamento (FB) que vem a ser um número
inteiro calculado a partir de:
FB(x) = altura(direita(p)) - altura(esquerda(p))

80
Netos Interiores e Exteriores
x

e d

ee ed de dd

Netos Interiores
Netos Exteriores

81
Situações de Desbalanceamento
—  Tipo 1: O nó a ser inserido é neto exterior de x:

y
C

A B

82
Neto Exterior
Situações de Desbalanceamento
—  Tipo 1: O nó a ser inserido é neto exterior de x:

y
C

83
Situações de Desbalanceamento
—  Tipo 1: O nó a ser inserido é neto exterior de x:

y
C

84
Situações de Desbalanceamento
—  Tipo 1: O nó a ser inserido é neto exterior de x:

A
B C

85
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

y
C

A B

86
Neto Interior
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

y
C

87
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

y
C

B
A

88
B1 B2
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

y
C

B
A

89
B1 B2
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

B
C

B2

90
A B1
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

B
C

B2

91
A B1
Situações de Desbalanceamento
—  Tipo 2: O nó a ser inserido é neto interior de x:

y x

A B1 B2 C

92
Exemplo I

1 8

2 6 12

10 14

93
Inserir 16
Exemplo I (cont.)

1 8

2 6 12

10 14

94
16
Exemplo I (cont.)
Relembrando:
4 FB(x) = a(dir(x))-a(esq(x))

1 8

2 6 12

10 14

95
16
Exemplo I (cont.)
+2
4
+1 +2
1 8
0 0 +1
2 6 12

0 +1
10 14
0

96
16
Exemplo I (cont.)
+2
4
+1 +2
1 8
0 0 +1
2 6 12

0 +1
10 14
0

97
16
Exemplo I (cont.)

1 8

2 6 12

10 14

98
16
Exemplo I (cont.)

1 12

2 8 14

6 10 16

99
Exemplo I (cont.)
+1
4
+1 0
1 12
0 0 +1
2 8 14
0 0 0
6 10 16

100
Exemplo II

12

8 16

4 10 14

2 6

101
Inserir 5
Exemplo II

12

8 16

4 10 14

2 6

102
5
Exemplo II (cont.)

12

8 16

4 10 14

2 6
Relembrando:
FB(x) = a(dir(x))-a(esq(x))

103
5
Exemplo II (cont.)
-2
12
-2 -1
8 16
+1 0 0
4 10 14

0 -1
2 6

104
5
Exemplo II (cont.)
-2
12
-2 -1
8 16
+1 0 0
4 10 14

0 -1
2 6

105
5
Exemplo II (cont.)

12

8 16

4 10 14

2 6

106
5
Exemplo II (cont.)

12

8 16

6 10 14

107
2 5
Exemplo II (cont.)
-2
12
-2 -1
8 16
-2 0 0
6 10 14
0
4
0 0

108
2 5
Exemplo II (cont.)

12

8 16

6 10 14

109
2 5
Exemplo II (cont.)
-1
12
0 -1
6 16
0 +1 0
4 8 14

0 0 0
2 5 10

110
Forma Geral
—  Tipo I:
—  O nó raiz de uma subárvore tem FB +2 (ou -2) e tem um filho com FB +1 (ou -1), ou seja, o

mesmo sinal do FB do nó pai.


—  Ação:

—  Rotação simples sobre o nó de FB +2 (ou -2).


São feitas à esquerda quando FB é positivo e à direita quando FB negativo.
—  Tipo II:
—  O nó raiz de uma subárvore tem FB +2 (ou -2) e tem um filho com FB -1 (ou +1), ou seja, com

sinal oposto ao FB do nó pai.


—  Ações:

—  Rotação simples sobre o nó com FB -1 (ou +1) na direção apropriada.


Rotação simples sobre o nó com FB +2 (ou -2) na direção oposta.

111
— 
Algoritmos
ROTAÇÃO_DIREITA(x)
y ← esquerda(x)
aux ← direita(y)
direita(y) ← x
esquerda(x) ← aux
x←y
DEVOLVE x

112
Algoritmos
ROTAÇÃO_DIREITA(x)
y ← esquerda(x)
aux ← direita(y)
direita(y) ← x
esquerda(x) ← aux 1.  Seja y o filho à esquerda de x
x←y 2.  Torne x o filho à direita de y
DEVOLVE x 3.  Torne o antigo filho à direita de y o filho à
esquerda de x

113
Algoritmos
ROTAÇÃO_ESQUERDA(x)
y ← direita(x)
aux ← esquerda(y)
esquerda(y) ← x
direita(x) ← aux
x←y
DEVOLVE x

114
Algoritmos
ROTAÇÃO_ESQUERDA(x)
y ← direita(x)
aux ← esquerda(y)
esquerda(y) ← x
direita(x) ← aux 1.  Seja y o filho à direita de x
x←y 2.  Torne x o filho à esquerda de y
DEVOLVE x 3.  Torne o antigo filho à esquerda de y o filho
à direita de x

115
Remoção em Árvores AVL
—  A remoção ocorre à mesma maneira das APBs. Entretanto, para cada um dos casos de remoção,
após remover o elemento e operar sobre a árvore, deve ser observado o FB da árvore.
—  Enquanto houver desbalanceamento, rotações simples ou duplas devem ser realizadas.

116
Exercícios
1.  Faça o esboço da inserção dos conjuntos de chaves abaixo em árvores do tipo AVL:
〈 40, 20, 10, 30, 25, 24, 21, 22, 39, 37, 38〉
〈 14, 6, 4, 2, 16, 20, 12, 13, 1, 15, 17, 19, 10, 18〉
Desenhe uma nova árvore quando necessitar aplicar rotação.
2.  Faça o esboço das seguintes operações sobre uma AVL, considerando na remoção de
elementos com dois filhos a operação PREDECESSOR.
Inserção: 16, 6, 17, 4, 13, 21, 11, 14, 19, 24, 7 e 8.
Remoção: 14, 17 e 6.
Inserção: 6, 17 e 14.
Remoção: 21, 11 e 7.
Inserção: 7, 11 e 21.
Remoção: 4, 19, 13, 11 e 14.

117
Árvores vermelho-preto
—  Uma árvore vermelho-preto (AVP, do inglês Red-black tree) é uma APB com um bit extra de
armazenamento por nó (sua cor), que pode ser VERMELHO ou PRETO.
—  Restringindo-se o modo como os nós podem ser coloridos em qualquer caminho desde a raiz até uma
folha, as árvores AVP asseguram que nenhum destes caminhos será maior que duas vezes o comprimento
de qualquer outro, de forma que a árvore é aproximadamente balanceada.
—  Cada nó da AVP contém os campos: cor, chave, esquerda, direita e pai. Se um filho ou o pai de um nó não
existir, o ponteiro correspondente do nó conterá o valor nil.
—  Tratam-se estes valores nil como ponteiros para nós externos (folhas) da APB e, portanto, os nós normais
que contém chaves serão tratados como nós internos da árvore.

118
Propriedades
—  Uma APB é uma AVP se satisfaz as seguintes propriedades:
1.  Todo nó é vermelho ou preto
2.  A raiz é preta
3.  Toda folha (nil) é preta
4.  Se um nó é vermelho, então ambos nós filhos são pretos
5.  Para cada nó, todos os caminhos desde este nó até as folhas descendentes contém o
mesmo número de nós pretos. (altura de pretos)

119
26

17 41

14 21 30 47

10 16 19 23 28 38

7 12 15 20 35 39

120
26

17 41

14 21 30 47

10 16 19 23 28 38

7 12 15 20 35 39

121
3 26

3 17 2 41

14 21 30 1 47
2 2 2
10 16 19 23 28 38
2 1 1 1 1 1
7 12 15 20 35 39
1 1 1 1 1 1
3
1

122
Rotações
—  As operações INSERE e REMOVE (slides 69 e 77, respectivamente) quando executadas sobre uma
AVP podem violar suas propriedades.
—  Para restabelecer estas propriedades, mudam-se as cores de alguns nós na árvore e mudam-se
as estruturas de ponteiros, através de rotações.

x ROTAÇÃO_ESQUERDA(T, x) y

a y ROTAÇÃO_DIREITA(T, y) x c

123
b c a b
Rotação à Esquerda
ROTAÇÃO_ESQUERDA( T, x)
y ← esquerda(x)
direita(x) ← esquerda(y)
pai(esquerda(y)) ← x
pai(y) ← pai(x)
SE pai(x) = nil ENTÃO raiz(T) ← y
SENÃO
SE x = esquerda(pai(x)) ENTÃO esquerda(pai(x)) ← y
SENÃO direita(pai(x)) ← y
esquerda(y) ← x
pai(x) ← y

124
Rotação à Direita
ROTAÇÃO_DIREITA( T, x)
y ← direita(x)
esquerda(x) ← direita(y)
pai(direita(y)) ← x
pai(y) ← pai(x)
SE pai(x) = nil ENTÃO raiz(T) ← y
SENÃO
SE x = direita(pai(x)) ENTÃO direita(pai(x)) ← y
SENÃO esquerda(pai(x)) ← y
direita(y) ← x
pai(x) ← y

125
Inserção
—  Utiliza-se uma versão modificada do INSERE (slide 69) para inserir o nó z na árvore T como se ela
fosse uma APB comum, e depois colore-se o nó de vermelho.
—  Para garantir que as propriedades vermelho-preto serão preservadas, chama-se um
procedimento RECOLORE para recolorir os nós e executar rotações.
—  A chamada INSERE_AVP(T, z) insere o nó z, cujo campo chave já está preenchido na AVP T.

126
INSERE_AVP( T, z)
y ← nil
x ← raiz(T)
ENQUANTO x != nil FAÇA
y←x
SE chave(z) < chave(x)
ENTÃO x ← esquerda(x)
SENÃO x ← direita(x)
pai(z) ← y
SE y = nil
ENTÃO raiz(T) ← z
SENÃO SE chave(z) < chave(y)
ENTÃO esquerda(y) ← z
SENÃO direita(y) ← z
esquerda(z) ← nil
direita(z) ← nil
cor(z) ← VERMELHO
RECOLORE( T, z)

127
RECOLORE( T, z)
ENQUANTO cor(pai(z)) = VERMELHO FAÇA
SE pai(z) = esquerda(pai(pai(z)))
ENTÃO
y ← direita(pai(pai(z)))
SE cor(y) = VERMELHO
ENTÃO
cor(pai(z)) ← PRETO
cor(y) ← PRETO
cor(pai(pai(z))) ← VERMELHO
z ← pai(pai(z))
SENÃO
SE z = direita(pai(z)) ENTÃO
z ← pai(z)
ROTAÇÃO_ESQUERDA( T, z)
cor(pai(z)) ← PRETO
cor(pai(pai(z)) ← VERMELHO
ROTAÇÃO_DIREITA( T, pai(pai(z)))

128
SENÃO
(igual ao bloco do ENTÃO com “direita” e “esquerda” trocadas)
cor(raiz(T)) ← PRETO
RECOLORE( T, z)
ENQUANTO cor(pai(z)) = VERMELHO FAÇA
SE pai(z) = esquerda(pai(pai(z)))
ENTÃO
y ← direita(pai(pai(z)))
SE cor(y) = VERMELHO
ENTÃO
cor(pai(z)) ← PRETO
cor(y) ← PRETO
cor(pai(pai(z))) ← VERMELHO Caso 1
z ← pai(pai(z))
SENÃO
SE z = direita(pai(z)) ENTÃO
z ← pai(z)
ROTAÇÃO_ESQUERDA( T, z) Caso 2
cor(pai(z)) ← PRETO
cor(pai(pai(z)) ← VERMELHO Caso 3
ROTAÇÃO_DIREITA( T, pai(pai(z)))

129
SENÃO
(igual ao bloco do ENTÃO com “direita” e “esquerda” trocadas)
cor(raiz(T)) ← PRETO
Violações das Propriedades
—  Após a inserção, mas antes de executar RECOLORE, apenas as propriedades 2 e 4 podem estar
violadas: a propriedade 2 é violada se z é a raiz e a propriedade 4 é violada se o pai de z é
vermelho.
—  O algoritmo RECOLORE repara essa eventual violação.
—  No início da iteração do ENQUANTO temos 3 invariantes:
1.  Nó z é vermelho.
2.  Se pai(z) é a raiz, então pai(z) é preto.
3.  Apenas uma propriedade (2 ou 4) pode estar violada. Se for a propriedade 2, então é porque
z (vermelho) é a raiz. Se a propriedade violada é a 4, então é porque z e pai(z) são ambos
vermelhos.

130
-  Há três casos a considerar quando z e pai(z) são vermelhos. Existe pai(pai(z)) pois pai(z) sendo
vermelho não pode ser a raiz.
-  Caso 1:
z tem um tio y vermelho.
-  Caso 2:
z tem um tio y preto e z é filho direito.
-  Caso 3:
z tem um tio y preto e z é filho esquerdo.
-  No caso 1, mudam-se algumas cores e sobe-se z para ser pai(pai(z)) e assim sucessivamente,
podendo chegar até a raiz.
-  No caso 2 e 3 mudam-se algumas cores e faz-se uma ou duas rotações.
11

2 14

1 7 15

y
5 8

132
Inserir: 4
Caso 1: z tem um tio y vermelho
11

2 14

1 7 15

y
5 8

133
4
Caso 1: z tem um tio y vermelho
11

2 14

1 7 15

y
5 8

134
4
Colore-se pai(z) e tio y de preto; e, pai(pai(z)) de vermelho que passa a ser o novo z
Caso 1: z tem um tio y vermelho
11

2 14

z
1 7 15

5 8

135
4
Caso 2: z tem um tio y preto e z é filho direito
11

2 y 14

z
1 7 15

5 8

136
4
Caso 2: z tem um tio y preto e z é filho direito
11

2 y 14

z
1 7 15

5 8

137
4
Executa-se ROTAÇÃO_ESQUERDA no z transformando para caso 3
Caso 2: z tem um tio y preto e z é filho direito
11

7 y 14

z
2 8 15

1 5

138
4
Caso 3: z tem um tio y preto e z é filho esquerdo
11

7 y 14

z
2 8 15

1 5

139
4
Caso 3: z tem um tio y preto e z é filho esquerdo
11

7 y 14

z
2 8 15

1 5

140
4
Colore-se pai(z) de preto e pai(pai(z)) de vermelho; e, executa-se ROTAÇÃO_DIREITA no pai(pai(z)).
Caso 3: z tem um tio y preto e z é filho esquerdo
11

7 14

z
2 8 15

1 5

141
4
Caso 3: z tem um tio y preto e z é filho esquerdo
7

z
2 11

1 5 8 14

4 15

142
Exercícios
1.  Utilizando o Applet de árvores, reproduza o exemplo de inserção do nó 4 na árvore do slide
132.
2.  Faça o estudo do algoritmo de remoção em AVP apresentado no livro do Cormen, nas páginas
231-235, necessário para os exercícios abaixo.
3.  Apresente as árvores para a remoção do nó 11 da árvore do slide 142. (indique qual é o caso)
4.  Apresente as árvores para a remoção do nó 8 da árvore do slide 142. (indique qual é o caso)
5.  Apresente as árvores para a remoção do nó 1 da árvore do slide 142. (indique qual é o caso)
6.  Apresente as árvores para a remoção do nó 5 da árvore do slide 142. (indique qual é o caso)

143
Árvores B
—  Árvores binárias de busca, balanceadas ou não, não são adequadas para o armazenamento e busca de
dados em memória secundária (como disco rígido).
—  O acesso a disco envolve um posicionamento da cabeça do disco, além da transferência de dados
propriamente ditos. O posicionamento (depende do tempo de rotação do disco) e o acesso a disco estão
na ordem de milissegundos, o que é considerável em comparação com o tempo de acesso à memória
primária (RAM), na ordem de nanossegundos.
—  No tempo para acessar uma vez o disco, pode-se fazer cerca de 100.000 acessos à memória.
—  Mesmo em árvores balanceadas, de n chaves, O(log n) acessos a disco podem ser excessivos. Para uma
árvore binária de busca balanceada de n = 1 milhão de chaves armazenadas em disco, log n = 20 acessos
a disco podem ser considerados custosos demais.

144
M

D H Q T X

B C F G J K L N P R S V W Y Z

145
Propriedades
—  Uma AB de ordem b possui as seguintes propriedades:
1.  Cada página (nó) contém no máximo 2b chaves
2.  Cada página, exceto a página raiz, contém no mínimo b chaves
3.  Cada página, com m chaves k1< k2< …< km possui m+1 ponteiros p0, p1, …, pm. Só há duas situações possíveis:
—  A página é uma folha e não tem filhos: todos os ponteiros pi, 0 ≤ i ≤ m apontam para nil.
—  A página não é folha e possui m+1 filhos apontados por pi, 0 ≤ i ≤ m. Nenhum ponteiro é nil.
k1 k2 k3 k4 km

p0 p1 p2 p3 p4 pm-1 pm
—  Para toda chave k na subárvore apontada por p0, k < k1.
—  Para toda chave k na subárvore apontada por pm, k > km.
—  Para toda chave k na subárvore apontada por pi, 1 ≤ i < m, ki < k < ki+1.

146
4.  Todas as páginas folhas aparecem no mesmo nível
Exemplo
—  A raiz de uma AB de ordem b=2 pode ter de 1 a 2b = 4 chaves; as demais páginas podem ter de
b = 2 a 2b = 4 chaves.
25

10 20 30 40

2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

147
Árvore-2-3
—  Um caso particular de AB é a chamada árvore-2-3.
—  Uma árvore-2-3 é uma AB de ordem b = 1.

—  Cada nó da árvore-2-3 tem 1 ou 2 chaves.

—  Cada nó da árvore-2-3 tem 2 ou 3 filhos, daí o nome.

—  A árvore-2-3 é uma árvore usada fazer busca de dados armazenados na memória principal.

—  Para armazenamento e busca em disco, uma AB usa uma ordem b grande, tipicamente de

alguma centenas de chaves.

148
Busca em uma AB de ordem b
—  A busca de uma dada chave x numa AB é análoga à busca na árvore binária de busca. A busca
começa pela página raiz. É usual manter a raiz sempre na memória, evitando um acesso ao disco.
—  Estando em uma página da AB, procede-se assim:
k1 k2 k3 k4 km

p0 p1 p2 p3 p4 pm-1 pm
—  Busca-se x na página corrente, usando um método de busca sequencial ou busca binária,
dependendo do valor de b.
—  Se x estiver na página, então a busca termina.
—  Se x < k1, então continua a busca na página apontada por p0.
—  Se ki < x < ki+1, então continua a busca na página apontada por pi.
—  Se x > km, então continua a busca na página apontada por pm.

149
Exemplo: buscar chave 36

36 ?
25

10 20 30 40

2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

150
Exemplo: buscar 36 (cont.)

25

10 20 36 ? 30 40

2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

151
Exemplo: buscar 36 (cont.)

25

10 20 30 40
36 ?

2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

152
Exemplo: buscar 36 (cont.)

25

10 20 30 40

36 ?
2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

153
Exemplo: buscar 36 (cont.)

25

10 20 30 40

36 ?
2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

154
Exemplo: buscar 36 (cont.)

25

10 20 30 40

36 ?
2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

155
Exemplo: buscar 36 (cont.)

25

10 20 30 40

2578 13 14 15 18 22 24 26 27 28 32 35 38 41 42 45 46

36 não encontrado

156
Inserção
—  Relembrando: em uma AB de ordem b cada nó (exceto a raiz) contém entre b a 2b chaves. A raiz
pode conter entre 1 a 2b chaves.
—  Para inserir uma nova chave x, primeiro localiza-se a página folha onde será feita a inserção.
—  Por exemplo: para inserir 18, primeiro localiza-se a página (em vermelho).

20

157
7 10 15 26 30 35 40
Inserção
—  Verifica-se quantas chaves já estão na página antes de adicionar a chave x na mesma.
—  Caso 1: A página contém menos que 2b chaves, então basta inserir a nova chave x na página.

20

7 10 15 18 26 30 35 40

158
Inserção
—  Caso 2: Antes de inserir a nova chave, a página já contém 2b chaves. Adicionando mais a chave x iria
resultar numa página com 2b + 1 chaves.
—  Considera-se as 2b + 1 chaves (incluindo a nova chave x) em ordem crescente. Insere-se a chave do meio
(mediana) na página pai, alocando as primeiras b chaves numa página e as últimas b chaves noutra.
—  Diz-se que há uma divisão ou cisão da página (em duas).
—  Exemplo: Inserir 22

20

159
7 10 15 18 26 30 35 40
Inserção
—  Caso 2: Antes de inserir a nova chave, a página já contém 2b chaves. Adicionando mais a chave x iria
resultar numa página com 2b + 1 chaves.
—  Considera-se as 2b + 1 chaves (incluindo a nova chave x) em ordem crescente. Insere-se a chave do meio
(mediana) na página pai, alocando as primeiras b chaves numa página e as últimas b chaves noutra.
—  Diz-se que há uma divisão ou cisão da página (em duas).
—  Exemplo: Inserir 22

20

160
7 10 15 18 22 26 30 35 40
Inserção
—  Caso 2: Antes de inserir a nova chave, a página já contém 2b chaves. Adicionando mais a chave x iria
resultar numa página com 2b + 1 chaves.
—  Considera-se as 2b + 1 chaves (incluindo a nova chave x) em ordem crescente. Insere-se a chave do meio
(mediana) na página pai, alocando as primeiras b chaves numa página e as últimas b chaves noutra.
—  Diz-se que há uma divisão ou cisão da página (em duas).
—  Exemplo: Inserir 22

20

161
7 10 15 18 22 26 30 35 40
Inserção
—  Caso 2: Antes de inserir a nova chave, a página já contém 2b chaves. Adicionando mais a chave x iria
resultar numa página com 2b + 1 chaves.
—  Considera-se as 2b + 1 chaves (incluindo a nova chave x) em ordem crescente. Insere-se a chave do meio
(mediana) na página pai, alocando as primeiras b chaves numa página e as últimas b chaves noutra.
—  Diz-se que há uma divisão ou cisão da página (em duas).
—  Exemplo: Inserir 22

20 30

162
7 10 15 18 22 26 35 40
Inserção
—  Caso 2: Antes de inserir a nova chave, a página já contém 2b chaves. Adicionando mais a chave x iria
resultar numa página com 2b + 1 chaves.
—  Considera-se as 2b + 1 chaves (incluindo a nova chave x) em ordem crescente. Insere-se a chave do meio
(mediana) na página pai, alocando as primeiras b chaves numa página e as últimas b chaves noutra.
—  Diz-se que há uma divisão ou cisão da página (em duas).
—  Exemplo: Inserir 22

20 30

163
7 10 15 18 22 26 35 40
Inserção
—  No caso 2, a inserção de uma chave na página pai pode por sua vez, de forma recursiva,
necessitar de uma divisão da página pai caso esta também já esteja cheia e isso
sucessivamente até chegar à página raiz.
—  Se a página raiz também já estiver cheia e tiver que dividir em duas, então cria-se uma nova raiz.
Desse modo a AB aumenta de altura.

164
Exemplo: AB de ordem 2.
-  Inserir: 20, 40, 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

165
Exemplo
-  Inserir: 40, 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

166
Exemplo
—  Inserir: 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20 40

167
Exemplo
—  Inserir: 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

10 20 40

168
Exemplo
—  Inserir: 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

10 20 30 40

169
Exemplo
—  Inserir: 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

10 15 20 30 40

170
Exemplo
—  Inserir: 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

10 15 30 40

171
Exemplo
—  Inserir: 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

10 15 30 35 40

172
Exemplo
—  Inserir: 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

7 10 15 30 35 40

173
Exemplo
—  Inserir: 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

7 10 15 26 30 35 40

174
Exemplo
—  Inserir: 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

7 10 15 18 26 30 35 40

175
Exemplo
—  Inserir: 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20

7 10 15 18 22 26 30 35 40

176
Exemplo
—  Inserir: 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20 30

7 10 15 18 22 26 35 40

177
Exemplo
—  Inserir: 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

20 30

5 7 10 15 18 22 26 35 40

178
Exemplo
—  Inserir: 42, 13, 46, 27, 8, 32, 38, 24, 45 e 25.

10 20 30

5 7 15 18 22 26 35 40

179
Exemplo
—  Inserir: 13, 46, 27, 8, 32, 38, 24, 45 e 25.

10 20 30

5 7 15 18 22 26 35 40 42

180
Exemplo
—  Inserir: 46, 27, 8, 32, 38, 24, 45 e 25.

10 20 30

5 7 13 15 18 22 26 35 40 42

181
Exemplo
—  Inserir: 27, 8, 32, 38, 24, 45 e 25.

10 20 30

5 7 13 15 18 22 26 35 40 42 46

182
Exemplo
—  Inserir: 8, 32, 38, 24, 45 e 25.

10 20 30

5 7 13 15 18 22 26 27 35 40 42 46

183
Exemplo
—  Inserir: 32, 38, 24, 45 e 25.

10 20 30

5 7 8 13 15 18 22 26 27 35 40 42 46

184
Exemplo
—  Inserir: 38, 24, 45 e 25.

10 20 30

5 7 8 13 15 18 22 26 27 32 35 40 42 46

185
Exemplo
—  Inserir: 38, 24, 45 e 25.

10 20 30 40

5 7 8 13 15 18 22 26 27 32 35 42 46

186
Exemplo
—  Inserir: 24, 45 e 25.

10 20 30 40

5 7 8 13 15 18 22 26 27 32 35 38 42 46

187
Exemplo
—  Inserir: 45 e 25.

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 46

188
Exemplo
—  Inserir: 25.

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

189
Exemplo

10 20 30 40

5 7 8 13 15 18 22 24 25 26 27 32 35 38 42 45 46

190
Exemplo

10 20 25 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

191
Exemplo

25

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

192
Remoção
—  Relembrando: em uma AB de ordem b cada nó (exceto a raiz) contém entre b a 2b chaves. A raiz pode conter
entre 1 a 2b chaves.
—  Para remover uma chave x de uma AB de ordem b:
—  Primeiro localiza-se a página P onde fica a chave x. P pode ser uma página folha ou uma página interna.

—  Caso 1: P é uma página folha. Retira-se a chave x. Se após retirar a chave x, P contém menos de b chaves,

então repara-se a árvore da seguinte forma.


—  Caso 1.1: O número total de chaves de P e de uma página irmã Q é maior ou igual a 2b: então faz-se uma

redistribuição de chaves de Q em direção à P, mantendo as propriedades da AB.


—  Caso 1.2: O número total de chaves de P e de uma página irmã Q é menor do que 2b: então faz-se uma

concatenação das páginas P e Q, juntando as suas chaves, mais uma chave da página pai. A página pai
tem uma chave retirada, podendo passar a conter menos de b chaves. O processo de redistribuição ou
concatenação precisa ser repetido, recursivamente, até o pior caso de se chegar ao topo da AB, quando
a raiz original desaparece. E dessa forma a AB diminui de altura.

193
Remoção
—  Relembrando: em uma AB de ordem b cada nó (exceto a raiz) contém entre b a 2b chaves. A raiz
pode conter entre 1 a 2b chaves.
—  Para remover uma chave x de uma AB de ordem b:
—  Primeiro localiza-se a página P onde fica a chave x. P pode ser uma página folha ou uma

página interna.
—  Caso 2: P é uma página interna. Substitui-se x pela chave y de maior valor da subárvore

esquerda. Essa chave y é a chave mais à direita na página mais à direita da subárvore
esquerda de x. A chave y se localiza numa página folha. A retirada de y de uma folha faz
recair no caso 1.

194
Exemplo
-  Remover: 25, 45 e 24.

25

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

195
Exemplo
-  Remover: 45 e 24.

25

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

25 está em uma página interna.

196
Exemplo
-  Remover: 45 e 24.

25

10 20 30 40

5 7 8 13 15 18 22 24 26 27 32 35 38 42 45 46

Substituir pela maior chave da subárvore esquerda.

197
Exemplo
-  Remover: 45 e 24.

24

10 20 30 40

5 7 8 13 15 18 22 26 27 32 35 38 42 45 46

Agora a página com chave 22 tem poucas chaves.

198
Exemplo
-  Remover: 45 e 24.

24

10 20 30 40

5 7 8 13 15 18 22 26 27 32 35 38 42 45 46

Redistribuir as chaves com a página irmã.

199
Exemplo
-  Remover: 45 e 24.

24

10 18 30 40

5 7 8 13 15 20 22 26 27 32 35 38 42 45 46

Agora a página tem número suficiente de chaves.

200
Exemplo
-  Remover: 24.

24

10 18 30 40

5 7 8 13 15 20 22 26 27 32 35 38 42 46

201
Exemplo
-  Remover: 24.

24

10 18 30 40

5 7 8 13 15 20 22 26 27 32 35 38 42 46

Substituir pela maior chave da subárvore esquerda.

202
Exemplo

22

10 18 30 40

5 7 8 13 15 20 26 27 32 35 38 42 46

Agora a página com chave 20 tem poucas chaves.

203
Exemplo

22

10 18 30 40

5 7 8 13 15 20 26 27 32 35 38 42 46

A página irmã tem poucas chaves para redistribuir.

204
Exemplo

22

10 18 30 40

5 7 8 13 15 20 26 27 32 35 38 42 46

Então concatenar com as chaves da página irmã.

205
Exemplo

22

10 30 40

5 7 8 13 15 18 20 26 27 32 35 38 42 46

Resolveu o problema, mas agora a página com a chave 10 tem poucas chaves.

206
Exemplo

22

10 30 40

5 7 8 13 15 18 20 26 27 32 35 38 42 46

Não dá para redistribuir chaves da página irmã.

207
Exemplo

22

10 30 40

5 7 8 13 15 18 20 26 27 32 35 38 42 46

Então concatenam-se as chaves.

208
Exemplo

10 22 30 40

5 7 8 13 15 18 20 26 27 32 35 38 42 46

209
Exercícios
1.  Considere a AB do slide anterior. Desenhe as novas ABs depois de remover cada uma das
seguintes chaves: 38, 32, 8, 27,46, 13, 42, 5, 22, 18, 26, 7, 35 e 15.

210
Parte 3

GRAFOS

211
Grafo
—  Um grafo G(V,A) é definido pelo par de conjuntos V e A, onde:
—  V: conjunto não vazio, correspondendo aos vértices ou nodos do grafo;

—  A: conjunto de pares ordenados a=(v,w), v e w V, denotando as arestas do grafo.

212
Exemplo
—  Seja, por exemplo, o grafo G(V,A) dado por: G1:
—  V = { p | p é uma pessoa }
Maria Pedro
—  A = { (v,w) | < v é amigo de w > }

—  Esta definição representa toda uma família


de grafos. Um exemplo de elemento desta
família (ver G1) é dado por: Joana Luiz
—  V = { Maria, Pedro, Joana, Luiz }

—  A = { (Maria, Pedro), (Pedro, Maria),

(Joana, Maria), (Maria, Joana), (Pedro,


Luiz), (Luiz, Pedro), (Joana, Pedro) ,
(Pedro, Joana) }

213
Exemplo
—  Seja, por exemplo, o grafo G(V,A) dado por: G1:
—  V = { p | p é uma pessoa }
Maria Pedro
—  A = { (v,w) | < v é amigo de w > }

—  Esta definição representa toda uma família


de grafos. Um exemplo de elemento desta
família (ver G1) é dado por: Joana Luiz
—  V = { Maria, Pedro, Joana, Luiz }

—  A = { (Maria, Pedro), (Pedro, Maria),


Neste exemplo, considera-se que a relação <v é amigo de w>
(Joana, Maria), (Maria, Joana), (Pedro, é uma relação simétrica, ou seja, se <v é amigo de w> então
Luiz), (Luiz, Pedro), (Joana, Pedro) , <w é amigo de v>.
(Pedro, Joana) }

214
Como consequência, as arestas que ligam os vértices não
Possuem qualquer orientação.
Grafo Orientado ou Dirigido ou Digrafo
- Considere, agora, o grafo definido por: G2:
- V = { p | p é uma pessoa da família Castro } Renata
- A = { (v,w) | < v é pai/mãe de w > }

- Um exemplo de grafo orientado(ver G2) é: Emerson Antonio


—  V = { Emerson, Isadora, Renata, Antonio,

Rosane, Cecília, Alfredo } Isadora Alfredo Cecília


—  A = {(Isadora, Emerson), (Antonio, Renata),

(Alfredo, Emerson), (Cecília, Antonio),


(Alfredo, Antonio)}

215
Grafo Orientado ou Dirigido ou Digrafo
- Considere, agora, o grafo definido por: G2:
- V = { p | p é uma pessoa da família Castro } Renata
- A = { (v,w) | < v é pai/mãe de w > }

- Um exemplo de grafo orientado(ver G2) é: Emerson Antonio


—  V = { Emerson, Isadora, Renata, Antonio,

Rosane, Cecília, Alfredo } Isadora Alfredo Cecília


—  A = {(Isadora, Emerson), (Antonio, Renata),

(Alfredo, Emerson), (Cecília, Antonio), A relação definida por A não é simétrica pois se <v é pai/mãe de w>,
(Alfredo, Antonio)} não é o caso de <w é pai/mãe de v>.

216
Há, portanto, uma orientação na relação, com um correspondente
efeito na representação gráfica de G.
Ordem e Adjacência
—  A ordem de um grafo G é dada pela —  No caso do grafo ser dirigido (a exemplo de
cardinalidade do conjunto de vértices, ou G2), a adjacência (vizinhança) é especializada
seja, pelo número de vértices de G. Nos em:
exemplos anteriores: —  Sucessor: um vértice w é sucessor de v

ordem(G1) = 4 se há um arco que parte de v e chega em


ordem(G2) = 6 w.
—  Em um grafo simples (a exemplo de G1) dois —  Antecessor: um vértice v é antecessor de

vértices v e w são adjacentes (ou vizinhos) w se há um arco que parte de v e chega


se há uma aresta a=(v,w) em G. Esta aresta é em w.
dita ser incidente a ambos, v e w.

217
Grau
—  O grau de um vértice é dado pelo número —  Grau de emissão: o grau de emissão de um
de arestas que lhe são incidentes. Em G1, vértice v corresponde ao número de arcos
que partem de v. Em G2, por exemplo:
por exemplo: grauDeEmissão(Antonio) = 1
grau(Pedro) = 3 grauDeEmissao(Alfredo) = 2
grau(Maria) = 2 grauDeEmissao(Renata) = 0
—  No caso do grafo ser dirigido (a exemplo de —  Grau de recepção: o grau de recepção de
um vértice v corresponde ao número de
G2), a noção de grau é especializada em: arcos que chegam a v. Em G2, por exemplo:
grau de emissão e grau de recepção. grauDeRecepção(Antonio) = 2
grauDeRecepção(Alfredo) = 0
grauDeRecepção(Renata) = 1

218
Fonte, Sumidouro e Laço
—  Fonte: Um vértice v é uma fonte se grauDeRecepção(v) = 0. É o caso dos vértices Isadora,
Alfredo e Cecília em G2.
—  Sumidouro: Um vértice v é um sumidouro se grauDeEmissão(v) = 0. É o caso dos vértices Renata
e Emerson em G2.
—  Laço: é uma aresta ou arco do tipo a=(v,v), ou seja, que relaciona um vértice a ele próprio. Em
G3 há três ocorrências de laços para um grafo não orientado.
G3:

A# B#
C#

D#
219
Grafo Regular
—  Um grafo é dito ser regular quando todos os seus vértices tem o mesmo grau.
—  O grafo G4, por exemplo, é dito ser um grafo regular-3 pois todos os seus vértices tem grau 3.
G4:

220
Grafo Completo
—  Um grafo é dito ser completo quando há uma aresta entre cada par de seus vértices. Estes
grafos são designados por Kn, onde n é a ordem do grafo.
—  Um grafo Kn possui o número máximo possível de arestas para um dado n. Ele é, também
regular-(n-1) pois todos os seus vértices tem grau n-1.

K1 K2 K3 K4 K5

221
Grafo Bipartido
—  Um grafo é dito ser bipartido quando seu G5:
conjunto de vértices V puder ser
particionado em dois subconjuntos V1 e V2,
tais que toda aresta de G une um vértice de Maria Joana Carla
V1 a outro de V2.
M
—  Para exemplificar, sejam os conjuntos H={h |

h é um homem} e M={m | m é uma mulher} Pedro Luiz


e o grafo G(V,A) (ver o exemplo G5) onde:
V=H∪M H
A = {(v,w) | (v H e w M) ou (v M e w
H) e <v foi namorado de w>}

222
Grafo Bipartido Completo
—  O grafo G6 é uma K3,3, ou seja, um grafo bipartido completo que contém duas partições de 3
vértices cada. Ele é completo pois todos os vértices de uma partição estão ligados a todos os
vértices da outra partição.
G6:

223
Grafo Rotulado e Grafo Valorado
—  Um grafo G(V,A) é dito ser rotulado em vértices (ou arestas) quando a cada vértice (ou aresta)
estiver associado um rótulo. G5 é um exemplo de grafo rotulado.
—  Um grafo G(V,A) é dito ser valorado quando existe uma ou mais funções relacionando V e/ou A
com um conjunto de números.
—  Para exemplificar (ver o grafo G7), seja G(V,A) onde:
20
V = {v | v é uma cidade com aeroporto} São Paulo Curitiba
A = {(v,w,t) | <há linha aérea ligando v a w, 50
sendo t o tempo esperado de voo>} 60 30
45
POA Floripa

224
Cadeia
—  Uma cadeia é uma sequência qualquer de arestas adjacentes que ligam dois vértices. O conceito
de cadeia vale também para grafos orientados, bastando que se ignore o sentido da orientação
dos arcos. A sequência de vértices (x6, x5, x4, x1) é um exemplo de cadeia em G8.
—  Uma cadeia é dita ser elementar se não passa duas vezes pelo mesmo vértice.
—  É dita ser simples se não passa duas vezes pela mesma aresta (arco).
—  O comprimento de uma cadeia é o número de arestas (arcos) que a compõe.
G8:
x1
x2 x3

225
x5
x4
x6
Caminho, Ciclo e Circuito
—  Um caminho é uma cadeia na qual todos os arcos possuem a mesma orientação. Aplica-se,
portanto, somente a grafos orientados. A sequência de vértices (x1, x2, x5, x6, x3) é um exemplo
de caminho em G8.
—  Um ciclo é uma cadeia simples e fechada (o vértice inicial é o mesmo que o vértice final). A
sequência de vértices (x1, x2, x3, x6, x5, x4, x1) é um exemplo de ciclo elementar em G8.
—  Um circuito é um caminho simples e fechado. A sequência de vértices (x1, x2, x5, x4, x1) é um
exemplo de circuito elementar em G8.

226
Fecho Transitivo
—  O fecho transitivo direto (ftd) de um G9:
vértice v é o conjunto de todos os vértices x1
que podem ser atingidos por algum x2 x3
caminho iniciando em v.
—  O ftd do vértice x5 do grafo G9, por x5 x6
x4
exemplo, é o conjunto:
{x1, x2, x3, x4, x5, x6}.
—  Note que o próprio vértice faz parte do ftd
já que ele é alcançável partindo-se dele x7
mesmo.

227
Fecho Transitivo
—  O fecho transitivo inverso (fti) de um G9:
vértice v é o conjunto de todos os vértices x1
a partir dos quais se pode atingir v por x2 x3
algum caminho.
—  O fti do vértice x5 do grafo G9, por exemplo, x5 x6
x4
é o conjunto:
{x1, x2, x4, x5, x7}.
—  Note que o próprio vértice faz parte do fti
já que dele se pode alcançar ele mesmo. x7

228
Grafo Conexo e Desconexo
—  Um grafo G(V,A) é dito ser conexo se há pelo menos uma cadeia ligando cada par de vértices
deste grafo G.
—  Um grafo G(V,A) é dito ser desconexo se há pelo menos um par de vértices que não está ligado
por nenhuma cadeia.
—  Um grafo G(V,A) desconexo é formado por pelo menos dois subgrafos conexos, disjuntos em
relação aos vértices e maximais em relação à inclusão. Cada um destes subgrafos conexos é
disto ser uma componente conexa de G.

229
Base e Anti-Base
—  Uma base de um grafo G(V,A) é um subconjunto B V, tal que:
—  dois vértices quaisquer de B não são ligados por nenhum caminho;

—  todo vértice não pertencente a B pode ser atingido por um caminho partindo de B.

—  Uma anti-base de um grafo G(V,A) é um subconjunto A V, tal que:


—  dois vértices quaisquer de A não são ligados por nenhum caminho;

—  de todo vértice não pertencente a A pode ser atingir A por um caminho.

230
B A
Raiz e Anti-Raiz
—  Se a base de um grafo G(V,A) é um conjunto unitário, então esta base é a raiz de G.
—  Se a anti-base de um grafo G(V,A) é um conjunto unitário, então esta anti-base é a anti-raiz de G.

Anti-Raiz

Raiz

231
Grafo Planar
—  Um grafo G(V,A) é dito ser planar quando existe alguma forma de se dispor seus vértices em um
plano de tal modo que nenhum par de arestas se cruze.
—  Um K4 (grafo completo de ordem 4) é um grafo planar pois admite pelo menos uma
representação num plano sem que haja cruzamento de arestas.

232
Grafo Planar
—  Um grafo G(V,A) é dito ser planar quando existe alguma forma de se dispor seus vértices em um
plano de tal modo que nenhum par de arestas se cruze.
—  Um K4 (grafo completo de ordem 4) é um grafo planar pois admite pelo menos uma
representação num plano sem que haja cruzamento de arestas.

233
Grafo Planar
—  Um grafo G(V,A) é dito ser planar quando existe alguma forma de se dispor seus vértices em um
plano de tal modo que nenhum par de arestas se cruze.
—  Já uma K5 e uma K3,3 são exemplos de grafos não planares. Estes dois grafos não admitem
representações planares.

234
Representações
—  Existem duas maneiras padrão para representar um grado G=(V,A): como uma coleção de listas
de adjacências ou como uma matriz de adjacências.
—  A representação de listas de adjacências em geral é preferida porque ela fornece um modo
compacto de representar grafos esparsos – aqueles para os quais |A| é muito menor que |V|2.
—  Contudo, uma representação de matriz de adjacências pode ser preferível quando grafo é denso
|A| é próximo a |V|2.

235
Representações
1 2 5 /
2 1 5 3 4 /
3 2 4 /
4
2 5 3 /
5
1 2 4 1 2 /

3
1 2 3 4 5
5 4 1 0 1 0 0 1
2 1 0 1 1 1
3 0 1 0 1 0
4 0 1 1 0 1
5 1 1 0 1 0

236
Representações
1 2 4 /
2 5 /
3 6 5 /
4
2 /
5
1 2 3 4 /
6
6 /

4 5 6 1 2 3 4 5 6
1 0 1 0 1 0 0
2 0 0 0 0 1 0
3 0 0 0 0 1 1
4 0 1 0 0 0 0
5 0 0 0 1 0 0
6 0 0 0 0 0 1

237
Busca em Largura
—  BFS, do inglês Breadth-first search.
—  É um dos algoritmos mais simples para se pesquisar um grafo.
—  O algoritmo de caminhos mais curtos de origem única de Dijkstra e o algoritmo de árvore de
amplitude mínima de Prim utilizam ideias semelhantes.
—  Dado um grafo G=(V,A) e um vértice de origem distinta s, a pesquisa primeiro na extensão
explora sistematicamente as arestas de G até descobrir cada vértice acessível a partir de s.
—  O algoritmo também calcula a distância desde s até todos os vértices acessíveis.
—  Ele também produz uma árvore com raiz s que contém todos os vértices acessíveis.

238
-  Para controlar o andamento, a pesquisa primeiro na extensão pinta cada vértice de branco,
cinza ou preto.
-  No início, todos os vértices são brancos, e mais tarde eles podem se tornar cinzas e depois
pretos, conforme o processo de descoberta é realizado.
-  O procedimento BFS pressupõe que o grafo de entrada G seja representado com o uso de listas
de adjacências. A cor de cada vértice u V é representada por cor(u) e o predecessor de u é
representado como pred(u). A distância desde a origem s até o vértice u fica em d(u). O
algoritmo também usa uma fila Q para gerenciar o conjunto de vértices em descoberta.
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
∞ ∞ ∞
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 ∞ ∞ ∞ ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q:
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

240
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
∞ 0 ∞ ∞
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 ∞ ∞ ∞ ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: s
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

241
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 ∞ ∞
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 ∞ 1 ∞ ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: w r
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

242
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 ∞
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 ∞ 1 2 ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: r t x
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

243
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 ∞
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: t x v
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

244
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 3
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 ∞
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: x v u
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

245
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 3
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 3
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: v u y
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

246
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 3
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 3
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: u y
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

247
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 3
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 3
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q: y
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

248
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
BFS( G, s)
PARA cada vértice u ∈ V - {s} FAÇA
cor(u) ← BRANCO
d(u) ← ∞
r s t u
1 0 2 3
pred(u) ← nil
cor(s) ← CINZA
d(s) ← 0 2 1 2 3
pred(s)←nil
Q←∅ v w x y
INSERE_FILA(Q, s)
ENQUANTO Q≠∅ FAÇA Q:
u ← REMOVE_FILA(Q)
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
cor(v) ← CINZA
d(v) ← d(u)+1

249
pred(v) ← u
INSERE_FILA(Q,v)
cor(u) ← PRETO
Exercícios
1.  Mostre o resultado da busca em largura sobre o grafo orientado do slide 237, usando o vértice
3 como partida.
2.  Mostre o resultado da busca em largura sobre o grafo não orientado do slide 240, usando o
vértice u como partida.

250
Busca em Profundidade
—  DFS, do inglês Depth-first search.
—  Procura “mais fundo” em um grafo sempre que possível.
—  As arestas são exploradas a partir do vértice v mais recentemente descoberto que ainda tem
arestas inexploradas saindo dele.
—  Quando todas as arestas de v são exploradas, a busca “regressa” para explorar as arestas que
deixam o vértice a partir do qual v foi descoberto.
—  Esse processo continua até descobrir todos os vértices acessíveis a partir do vértice de origem
inicial.
—  Se restarem quaisquer vértices não descobertos, então um deles será selecionado como uma
nova origem, e a busca se repetirá a partir daquela origem. E esse processo será repetido até
que todos os vértices sejam descobertos.
—  Em f(u) fica registrado o tempo de fim da descoberta do vértice u.
251
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

252
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

253
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

254
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 3/
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

255
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/ 3/
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

256
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

257
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

258
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/ 2/7
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

259
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

260
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7 9/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

261
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7 9/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

262
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7 9/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6 10/
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

263
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7 9/
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6 10/11
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

264
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
DFS( G)
PARA cada vértice u ∈ V FAÇA
u v w
cor(u) ← BRANCO 1/8 2/7 9/12
pred(u) ← nil
tempo ← 0
PARA cada vértice u ∈ V FAÇA 4/5 3/6 10/11
SE cor(u) = BRANCO ENTÃO DFS-VISIT(u) x y z
DFS-VISIT(u)
cor(u) ← CINZA
tempo ← tempo + 1
d(u) ← tempo
PARA cada v ∈ adj(u) FAÇA
SE cor(v) = BRANCO ENTÃO
pred(v) ← u
DFS-VISIT(v)

265
cor(u) ← PRETO
tempo ← tempo + 1
f(u) ← tempo
Exercícios
1.  Mostre como a busca em profundidade funciona sobre o grafo abaixo. Suponha que o loop
PARA ( o segundo) do procedimento DFS considera os vértices em ordem alfabética, e
suponha que a lista de adjacências esteja em ordem alfabética. Mostre os tempos de
descoberta e término de cada vértice. Utilize q como vértice inicial.
r
q
u
s t

v w x y

266
z
Ordenação Topológica
—  A DFS pode ser utilizada para a ordenação topológica de grafos acíclicos orientados (GAOs).
—  Uma ordenação topológica de um GAO é uma ordenação linear de todos os seus vértices, tal que
se G contém um aresta (u,v), então u aparece antes de v na ordenação.
—  GAOs são utilizados em muitas aplicações para indicar precedências entre eventos.

267
Ordenação Topológica
—  A DFS pode ser utilizada para a ordenação topológica de grafos acíclicos orientados (GAOs).
—  Uma ordenação topológica de um GAO é uma ordenação linear de todos os seus vértices, tal que
se G contém um aresta (u,v), então u aparece antes de v na ordenação.
—  GAOs são utilizados em muitas aplicações para indicar precedências entre eventos.

TOPOLOGICAL-SORT(G)
1.  aplicar DFS(G) para calcular o tempo de término f(v) para cada vértice v
2.  à medida que cada vértice é terminado, insira o vértice à frente em uma lista ligada
3.  DEVOLVE a lista ligada de vértices

268
Exemplo
meias
cueca relógio

sapatos
calça
camisa

cinto
gravata

paletó

269
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
gravata

paletó

270
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
gravata
2/

paletó

271
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
gravata
2/

paletó
3/

272
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
gravata
2/

paletó
3/4

Lista: paletó
273
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
gravata
2/5

paletó
3/4

Lista: gravata paletó


274
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
6/ gravata
2/5

paletó
3/4

Lista: gravata paletó


275
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/
cinto
6/7 gravata
2/5

paletó
3/4

Lista: cinto gravata paletó


276
Exemplo
meias
cueca relógio

sapatos
calça
camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: camisa cinto gravata paletó


277
Exemplo
meias
cueca relógio
9/
sapatos
calça
camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: camisa cinto gravata paletó


278
Exemplo
meias
cueca relógio
9/10
sapatos
calça
camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: relógio camisa cinto gravata paletó


279
Exemplo
meias
cueca relógio
11/
9/10
sapatos
calça
camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: relógio camisa cinto gravata paletó


280
Exemplo
meias
cueca relógio
11/
9/10
sapatos
calça
12/ camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: relógio camisa cinto gravata paletó


281
Exemplo
meias
cueca relógio
11/
9/10
sapatos
calça 13/
12/ camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: relógio camisa cinto gravata paletó


282
Exemplo
meias
cueca relógio
11/
9/10
sapatos
calça 13/14
12/ camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: sapatos relógio camisa cinto gravata paletó


283
Exemplo
meias
cueca relógio
11/
9/10
sapatos
calça 13/14
12/15 camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: calça sapatos relógio camisa cinto gravata paletó


284
Exemplo
meias
cueca relógio
11/16
9/10
sapatos
calça 13/14
12/15 camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: cueca calça sapatos relógio camisa cinto gravata paletó


285
Exemplo
meias
cueca 17/
relógio
11/16
9/10
sapatos
calça 13/14
12/15 camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: cueca calça sapatos relógio camisa cinto gravata paletó


286
Exemplo
meias
cueca 17/18
relógio
11/16
9/10
sapatos
calça 13/14
12/15 camisa
1/8
cinto
6/7 gravata
2/5

paletó
3/4

Lista: meias cueca calça sapatos relógio camisa cinto gravata paletó
287
Exemplo

meias cueca calça sapatos relógio camisa cinto gravata paletó

288
Componentes Fortemente Conectados
—  Um componente fortemente conectado de um grafo orientado G=(V,A) é um conjunto máximo
de vértices C⊆V tal que, para todo par de vértices u e v em C, tem-se ao mesmo tempo u!v e
v!u; isto é, u e v são acessíveis um a partir do outro.
—  O algoritmo para localização de componentes fortemente conectados de um grafo G=(V,A) usa
a transposta de G, ou seja, o grafo GT=(V,AT), onde AT={(u,v): (v,u) ∈A}.
—  Ou seja, AT consiste das arestas de G com seus sentidos invertidos.

289
COMPONENTES-FORTEMENTE-CONECTADOS(G)
1.  aplicar DFS(G) para calcular o tempo de término f(u) de cada vértice u
2.  calcular GT
3.  aplicar DFS(GT) considerando os vértices em ordem decrescente de f(u)
4.  dar saída aos vértices de cada árvore DFS formada no item 3. como um componente
fortemente conectado separado.
Exemplo
G:

a b c d

e f g h

291
Exemplo

a b c d
1/

e f g h

292
Exemplo

a b c d
1/

2/
e f g h

293
Exemplo

a b c d
1/

3/ 2/
e f g h

294
Exemplo

a b c d
1/

3/4 2/
e f g h

295
Exemplo

a b c d
1/

3/4 2/ 5/
e f g h

296
Exemplo

a b c d
1/

3/4 2/ 5/6
e f g h

297
Exemplo

a b c d
1/

3/4 2/7 5/6


e f g h

298
Exemplo

a b c d
1/ 8/

3/4 2/7 5/6


e f g h

299
Exemplo

a b c d
1/ 8/9

3/4 2/7 5/6


e f g h

300
Exemplo

a b c d
1/10 8/9

3/4 2/7 5/6


e f g h

301
Exemplo

a b c d
11/ 1/10 8/9

3/4 2/7 5/6


e f g h

302
Exemplo

a b c d
11/ 1/10 8/9

12/ 3/4 2/7 5/6


e f g h

303
Exemplo

a b c d
13/ 11/ 1/10 8/9

12/ 3/4 2/7 5/6


e f g h

304
Exemplo

a b c d
13/14 11/ 1/10 8/9

12/ 3/4 2/7 5/6


e f g h

305
Exemplo

a b c d
13/14 11/ 1/10 8/9

12/15 3/4 2/7 5/6


e f g h

306
Exemplo

a b c d
13/14 11/16 1/10 8/9

12/15 3/4 2/7 5/6


e f g h

307
Exemplo
GT:

a b c d

e f g h

308
Exemplo

a b c d
1/

e f g h

309
Exemplo

a b c d
2/ 1/

e f g h

310
Exemplo

a b c d
2/ 1/

3/
e f g h

311
Exemplo

a b c d
2/ 1/

3/4
e f g h

312
Exemplo

a b c d
2/5 1/

3/4
e f g h

313
Exemplo

a b c d
2/5 1/6

3/4
e f g h

314
Exemplo f(c)=10
f(d)=9
f(f)=4
f(g)=7
a b c d f(h)=6
2/5 1/6

3/4
e f g h

315
Exemplo f(c)=10
f(d)=9
f(f)=4
f(g)=7
a b c d f(h)=6
2/5 1/6 7/

3/4
e f g h

316
Exemplo

a b c d
2/5 1/6 7/ 8/

3/4
e f g h

317
Exemplo

a b c d
2/5 1/6 7/ 8/9

3/4
e f g h

318
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4
e f g h

319
Exemplo f(f)=4
f(g)=7
f(h)=6

a b c d
2/5 1/6 7/10 8/9

3/4
e f g h

320
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 11/
e f g h

321
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 12/ 11/


e f g h

322
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 12/13 11/


e f g h

323
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 12/13 11/14


e f g h

324
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 12/13 11/14 15/


e f g h

325
Exemplo

a b c d
2/5 1/6 7/10 8/9

3/4 12/13 11/14 15/16


e f g h

326
Exemplo
a b c d
2/5 1/6 7/10 8/9

3/4 12/13 11/14 15/16


e f g h

b c g h
| | |
a d f
|

327
e
Exemplo
G:
a b c d

e f g h

328
Exemplo
G:
a b c d

e f g h

b c g h
| | |
a d f
|
e
329
Exemplo
G:
a b c d

e f g h

b c g h
| | | abe cd
a d f
|

330
fg h
e
Exemplo
G:
a b c d

e f g h

b c g h
| | | abe cd
a d f
|

331
fg h
e
Exemplo
G:
a b c d

e f g h

b c g h
| | | abe cd
a d f
|

332
fg h
e
Árvores Espalhadas Mínimas
—  No projeto de circuitos eletrônicos, frequentemente é necessário tornar os pinos de vários
componentes eletricamente equivalentes, juntando a fiação de todos eles.
—  Para conectar um conjunto de n pinos, pode-se utilizar um arranjo de n-1 fios, cada qual
conectando dois pinos.
—  De todos os arranjos possíveis, aquele que utiliza a quantidade mínima de fio é normalmente o
mais desejável.

333
Árvores Espalhadas Mínimas
—  Pode-se modelar este problema de fiação com um grafo conectado não orientado G=(V,A),
onde V é o conjunto de pinos, A é o conjunto de interconexões possíveis entre pares de pinos e,
para cada aresta (u,v)∈A, tem-se um peso w(u,v) especificando um custo (quantidade de fio
necessária) para conectar u a v.
—  Então deseja-se encontrar um subconjunto acíclico T⊆A que conecte todos os vértices e cujo

peso total seja minimizado.


-  Tendo em vista que T é acíclico e conecta todos os vértices, ele deve formar uma árvore,
chamada de árvore espalhada, pois se estende pela amplitude do grafo G.
-  Chama-se o problema de determinar a árvore T de problema da árvore espalhada mínima (do
inglês, minimum spanning tree).

334
Algoritmo de Kruskal

b c d

a i e

h g f

Parte-se da aresta com menor peso.

335
Algoritmo de Kruskal

b c d

a i e

h g f

h e g pertencem à mesma árvore?

336
Algoritmo de Kruskal

b c d

a i e

h g f

i e c pertencem à mesma árvore?

337
Algoritmo de Kruskal

b c d

a i e

h g f

g e f pertencem à mesma árvore?

338
Algoritmo de Kruskal

b c d

a i e

h g f

a e b pertencem à mesma árvore?

339
Algoritmo de Kruskal

b c d

a i e

h g f

c e f pertencem à mesma árvore?

340
Algoritmo de Kruskal

b c d

a i e

h g f

i e g pertencem à mesma árvore? Sim, não adicione.

341
Algoritmo de Kruskal

b c d

a i e

h g f

c e d pertencem à mesma árvore?

342
Algoritmo de Kruskal

b c d

a i e

h g f

h e i pertencem à mesma árvore?

343
Algoritmo de Kruskal

b c d

a i e

h g f

a e h pertencem à mesma árvore?

344
Algoritmo de Kruskal

b c d

a i e

h g f

b e c pertencem à mesma árvore?

345
Algoritmo de Kruskal

b c d

a i e

h g f

d e e pertencem à mesma árvore?

346
Algoritmo de Kruskal

b c d

a i e

h g f

O algoritmo para porque não há mais

347
vértices de G que não estão na MST.
Algoritmo de Prim

b c d

a i e

h g f

a é a raiz da MST (vértice de partida)

348
qual aresta que conecta algum vértice/nó da MST tem menor peso?
Algoritmo de Prim

b c d

a i e

h g f

b está na MST? Não.

349
Então adicione-o à MST.
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

350
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

351
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

352
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

353
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

354
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

355
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

356
MAS, i e g já estão na MST.
Não os adicione porque formará um ciclo.
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

357
Algoritmo de Prim

b c d

a i e

h g f

qual aresta que conecta algum vértice/nó da MST tem menor peso?

358
Algoritmo de Prim

b c d

a i e

h g f

O algoritmo para porque não há mais vértices de G fora da MST.

359
Parte 4

CLASSIFICAÇÃO DE DADOS

360
Insertion Sort
—  É um algoritmo eficiente para ordenar um número pequeno
de elementos. Trabalha imitando o modo como as pessoas
ordenam uma mão de cartas de baralho.
—  Inicia-se com a mão esquerda vazia e as cartas voltadas
para baixo na mesa.
—  Remove-se uma carta por vez da mesa e a insere na posição
correta na mão esquerda. Para encontrar a posição correta
de uma carta, compara-se a carta retirada da mesa com
cada carta da mão esquerda, da direita para a esquerda.
—  Em todas as vezes, as cartas seguradas estão sorteadas, que
em momento anterior estavam no topo da pilha na mesa.

361
O Algoritmo
INSERTION-SORT(A)
PARA j 2 A comprimento(A) FAÇA
key A[j]
i j-1
ENQUANTO i > 0 E A[i] > key FAÇA
A[i + 1] A[i]
i i-1
A[i + 1] key

362
Merge Sort
—  O algoritmo de ordenação por intercalação obedece ao paradigma de dividir e conquistar.
Intuitivamente ele opera do seguinte modo:
—  Dividir: divide a sequência de n elementos a serem ordenados em duas subsequências de n/

2 elementos cada uma.


—  Conquistar: classifica as duas subsequências recursivamente, utilizando a ordenação por

intercalação.
—  Combinar: faz a intercalação das duas sequências ordenadas, de modo a produzir a resposta

ordenada.
—  A recursão não funciona quando a sequência a ser ordenada tem comprimento 1, pois nesse
caso não há nenhum trabalho a ser feito, tendo em vista que toda sequência de comprimento 1
já está ordenada.

363
O Algoritmo
MERGE(A, p, q, r)
MERGE-SORT(A, p, r) n1 q - p + 1
SE p < r ENTÃO n2 r - q
PARA i 1 A n1 FAÇA
q #(p + r)/2$ L[i] A[p + i - 1]
PARA j 1 A n2 FAÇA
MERGE-SORT(A, p, q) R[j] A[q + j]
L[n1 + 1]
MERGE-SORT(A, q + 1, r) R[n2 + 1]
i 1
MERGE(A, p, q, r) j 1
PARA k p A r FAÇA
SE L[i] R[j] ENTÃO
A[k] L[i]
i i+1
SENÃO
A[k] R[j]
j j+1

364
Bubble Sort
—  O bubble sort é um algoritmo de ordenação popular.
—  Funciona permutando repetidamente elementos adjacentes que estão fora de ordem.

BUBBLESORT(A)
PARA i 1 A comprimento(A) FAÇA
PARA j comprimento(A) A i + 1 FAÇA
SE A[j] < A[j – 1] ENTÃO
aux A[j]
A[j] A[j – 1]
A[j – 1] aux

365
Heap Sort
—  A estrutura de dados heap (binário) é um objeto array (arranjo) que pode ser visto como uma
árvore binária quase completa, como mostra a figura.

16

14 10
16 14 10 8 7 9 3 2 4 1
8 7 9 3

2 4 1

366
Heap Sort
—  A estrutura de dados heap (binário) é um objeto array (arranjo) que pode ser visto como uma
árvore binária quase completa, como mostra a figura.

1
16
2 3
14 10 1 2 3 4 5 6 7 8 9 10
4 5 6 7 16 14 10 8 7 9 3 2 4 1
8 7 9 3
8 9 10
2 4 1

367
Heap
—  Cada nó da árvore corresponde a um elemento do arranjo que armazena o valor do nó.
—  A árvore está completamente preenchida em todos os níveis, exceto talvez o nível mais baixo,
que é preenchido a partir da esquerda até certo ponto.
—  Um arranjo A que representa um heap é um objeto com dois atributos: comprimento(A), que é o
número de elementos no arranjo, e tamanho-heap(A), o número de elementos no heap
armazenado dentro do arranjo A.
tamanho-heap(A) comprimento(A)

368
-  A raiz da árvore é A[1] e, dado o índice i de um nó, os índices do seu pai PAI(i), do filho da
esquerda ESQUERDA(i) e do filho da direita DIREITA(i) podem ser calculados de forma simples:

PAI(i)
DEVOLVE #i/2$

ESQUERDA(i)
DEVOLVE 2i

DIREITA(i)
DEVOLVE 2i+1
-  Existem dois tipos de heaps binários: heaps máximos e heaps mínimos. Em ambos tipos, os
valores nos nós satisfazem a uma propriedade de heap, cujos detalhes específicos dependem
do tipo de heap.
-  Em um heap máximo, a propriedade de heap máximo é que, para todo nó i diferente da raiz,
A[PAI(i)] A[i], isto é, o valor de um nó é no máximo o valor do seu pai.
-  Desse modo, o maior elemento de um heap máximo é armazenado na raiz, e a subárvore que
tem raiz em um nó contém valores menores que o próprio nó.
Manutenção da Propriedade de Heap
—  A sub-rotina MAX-HEAPIFY é utilizada na manutenção da propriedade de heaps máximos. Recebe
com entrada um arranjo A e um índice i.
—  Quando MAX-HEAPIFY é chamada, supõe-se que as árvores binárias com raízes em ESQUERDA(i) e
DIREITA(i) são heaps máximos, mas que A[i] pode ser menor que seus filhos, violando a
propriedade.
—  A função de MAX-HEAPIFY é deixar que o valor de A[i] flutue para baixo no heap máximo, de tal
forma que a subárvore com raiz no índice i se torne um heap máximo.

371
MAX-HEAPIFY(A, i)
l ESQUERDA(i)
r DIREITA(i) 1
SE l tamanho-heap(A) E A[l] > A[i] 16
ENTÃO maior l 2 i 3
SENÃO maior i 4 10
SE r tamanho-heap(A) E A[r] > A[maior] 4 5 6 7
ENTÃO maior r 14 7 9 3
SE maior i ENTÃO 8 9 10
aux A[i] 2 8 1
A[i] A[maior]
A[maior] aux
MAX-HEAPIFY(A, maior)
MAX-HEAPIFY(A, i)
l ESQUERDA(i)
r DIREITA(i) 1
SE l tamanho-heap(A) E A[l] > A[i] 16
ENTÃO maior l 2 3
SENÃO maior i 14 10
SE r tamanho-heap(A) E A[r] > A[maior] 4 i 5 6 7
ENTÃO maior r 4 7 9 3
SE maior i ENTÃO 8 9 10
aux A[i] 2 8 1
A[i] A[maior]
A[maior] aux
MAX-HEAPIFY(A, maior)
MAX-HEAPIFY(A, i)
l ESQUERDA(i)
r DIREITA(i) 1
SE l tamanho-heap(A) E A[l] > A[i] 16
ENTÃO maior l 2 3
SENÃO maior i 14 10
SE r tamanho-heap(A) E A[r] > A[maior] 4 5 6 7
ENTÃO maior r 8 7 9 3
SE maior i ENTÃO 8 i 9 10
aux A[i] 2 4 1
A[i] A[maior]
A[maior] aux
MAX-HEAPIFY(A, maior)
Construção de um Heap
—  Pode-se usar o procedimento MAX-HEAPIFY de baixo para cima, a fim de converter um arranjo
em um heap máximo.
—  Os elementos no subarranjo A[( #n/2$ + 1)..n] são todos folhas da árvore, e então cada um
deles é um heap de 1 elemento.
—  O procedimento BUILD-MAX-HEAP percorre os nós restantes da árvore e executa MAX-HEAPIFY
sobre cada um.

BUILD-MAX-HEAP(A)
tamanho-heap(A) comprimento(A)
PARA i #comprimento(A)/2$ A 1 FAÇA
MAX-HEAPIFY(A,i)

375
A 4 1 3 2 16 9 10 14 8 7

1 3

2 16 9 10

14 8 7

376
A 4 1 3 2 16 9 10 14 8 7

1 3

2 16 9 10

14 8 7

377
A 4 1 3 2 16 9 10 14 8 7

1 3

2 16 9 10

14 8 7

378
A 4 1 3 14 16 9 10 2 8 7

1 3

14 16 9 10

2 8 7

379
A 4 1 10 14 16 9 3 2 8 7

1 10

14 16 9 3

2 8 7

380
A 4 16 10 14 7 9 3 2 8 1

16 10

14 7 9 3

2 8 1

381
A 16 14 10 8 7 9 3 2 4 1

16

14 10

8 7 9 3

2 4 1

382
O Algoritmo
HEAPSORT(A)
BUILD-MAX-HEAP(A)
PARA i comprimento(A) A 2 FAÇA
aux A[1]
A[1] A[i]
A[i] aux
tamanho-heap(A) tamanho-heap(A) - 1
MAX-HEAPIFY(A, 1)

383
A 4 1 3 2 16 9 10 14 8 7

1 3

2 16 9 10

14 8 7

O Arranjo ORIGINAL

384
A 16 14 10 8 7 9 3 2 4 1

16

14 10

8 7 9 3

2 4 1

O Arranjo resultante do BUILD-MAX-HEAP

385
14

8 10

4 7 9 3

2 1 16

386
10

8 9

4 7 1 3

2 14 16

387
9

8 3

4 7 1 2

10 14 16

388
8

7 3

4 2 1 9

10 14 16

389
7

4 3

1 2 8 9

10 14 16

390
4

2 3

1 7 8 9

10 14 16

391
3

2 1

4 7 8 9

10 14 16

392
2

1 3

4 7 8 9

10 14 16

393
A 1 2 3 4 7 8 9 10 14 16

2 3

4 7 8 9

10 14 16

394
Exercícios
1.  A sequência 23, 17, 14, 6, 13, 10, 1, 5, 7, 12 é um heap máximo?
2.  Ilustre a operação MAX-HEAPIFY(A,3) sobre o arranjo A = <27,17,3,16,13,10,1,5,7,12,4,8,9,0>.
3.  Ilustre a operação BUILD-MAX-HEAP(A) sobre o arranjo A = <5,3,17,10,84,19,6,22,9>.
4.  Ilustre a operação HEAPSORT(A) sobre o arranjo A=<10,9,8,7,6,5,4,3,2,1>.

395
Quick Sort
—  O quicksort, assim como o mergesort, baseia-se no paradigma de dividir para conquistar, como
mostrado a seguir:
—  Dividir: O arranjo A[p..r] é particionado em dois subarranjos (possivelmente vazios) A[p..q-1]

e A[q+1..r] tais que cada elemento de A[p..q-1] é menor que ou igual a A[q] que, por sua vez,
é igual ou menor que cada elemento de A[q+1..r]. O índice q é calculado como parte desse
procedimento de particionamento.
—  Conquistar: Os dois subarranjos A[p..q-1] e A[q+1..r] são ordenados por chamadas recursivas

de QUICKSORT.
—  Combinar: Como os subarranjos são ordenados localmente, não é necessário trabalho para

combiná-los.

396
O Algoritmo
QUICKSORT(A, p, r) PARTITION(A, p, r)
SE p < r ENTÃO x A[r]
i p-1
q PARTITION(A, p, r) PARA j p A r – 1 FAÇA
QUICKSORT(A, p, q - 1) SE A[j] x ENTÃO
QUICKSORT(A, q + 1, r) i i+1
aux A[i]
A[i] A[j]
A[j] aux
aux A[i + 1]
A[i + 1] A[r]
A[r] aux
DEVOLVE i + 1

397
O Procedimento
i p,j r p i j r
2 8 7 1 3 5 6 4 2 1 3 8 7 5 6 4

p,i j r p i j r
2 8 7 1 3 5 6 4 2 1 3 8 7 5 6 4

p,i j r p i r
2 8 7 1 3 5 6 4 2 1 3 8 7 5 6 4

p,i j r p i r
2 8 7 1 3 5 6 4 2 1 3 4 7 5 6 8

p i j r

398
2 1 7 8 3 5 6 4
Quick#Sort#–#Versão#Aleatória#
RANDOMIZED-PARTITION(A, p, r)
i RANDOM(p, r)
aux A[r]
A[r] A[i]
A[i] aux
DEVOLVE PARTITION(A, p, r)
RANDOMIZED-QUICKSORT(A, p, r)
SE p < r ENTÃO
q RANDOMIZED-PARTITION(A, p, r)
RANDOMIZED-QUICKSORT(A, p, q - 1)
RANDOMIZED-QUICKSORT(A, q + 1, r)

399
Counting Sort
—  A ordenação por contagem pressupõe que cada um dos n elementos de entrada é um inteiro no
intervalo de 1 a k, para algum inteiro k.
—  A ideia básica deste tipo de ordenação é determinar, para cada elemento de entrada x, o
número de elementos menores que x. Essa informação pode ser utilizada para inserir o
elemento x diretamente em sua posição no arranjo de saída.
—  Por exemplo, se há 17 elementos menores que x, então x é colocado na posição de saída 18.

400
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0
PARA j 1 A comprimento(A) FAÇA
C[A[j]] C[A[j]] + 1
PARA i 1 A k FAÇA
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

401
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 0 1 2 3 4 5

PARA j 1 A comprimento(A) FAÇA C 2 0 2 3 0 1


C[A[j]] C[A[j]] + 1
PARA i 1 A k FAÇA
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

402
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0
PARA j 1 A comprimento(A) FAÇA
C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 2 2 4 7 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

403
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 2 2 4 6 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

404
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 1 2 4 6 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

405
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 3 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 1 2 4 5 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

406
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 2 3 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 1 2 3 5 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

407
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 0 2 3 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 0 2 3 5 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

408
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 0 2 3 3 3


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 0 2 3 4 7 8
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

409
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 0 2 3 3 3 5


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 0 2 3 4 7 7
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

410
O Algoritmo
1 2 3 4 5 6 7 8
COUNTING-SORT(A, B, k)
A 2 5 3 0 2 3 0 3
PARA i 0 A k FAÇA
C[i] 0 1 2 3 4 5 6 7 8

PARA j 1 A comprimento(A) FAÇA B 0 0 2 2 3 3 3 5


C[A[j]] C[A[j]] + 1 0 1 2 3 4 5

PARA i 1 A k FAÇA C 0 2 2 4 7 7
C[i] C[i] + C[i - 1]
PARA j comprimento(A) A 1 FAÇA
B[C[A[j]]] A[j]
C[A[j]] C[A[j]] - 1

411
Radix Sort
—  A ordenação da raiz é o algoritmo usado pelas máquinas de ordenação de cartões que agora
são encontradas apenas nos museus de informática.

RADIXSORT(A,d)
PARA i 1 A d FAÇA 3 2 9
ordene o arranjo A para o dígito i 4 5 7
6 5 7
8 3 9
4 3 6
7 2 0
3 5 5

412
Radix Sort
—  A ordenação da raiz é o algoritmo usado pelas máquinas de ordenação de cartões que agora
são encontradas apenas nos museus de informática.

RADIXSORT(A,d)
PARA i 1 A d FAÇA 3 2 9 7 2 0
ordene o arranjo A para o dígito i 4 5 7 3 5 5
6 5 7 4 3 6
8 3 9 4 5 7
4 3 6 6 5 7
7 2 0 3 2 9
3 5 5 8 3 9

413
Radix Sort
—  A ordenação da raiz é o algoritmo usado pelas máquinas de ordenação de cartões que agora
são encontradas apenas nos museus de informática.

RADIXSORT(A,d)
PARA i 1 A d FAÇA 3 2 9 7 2 0 7 2 0
ordene o arranjo A para o dígito i 4 5 7 3 5 5 3 2 9
6 5 7 4 3 6 4 3 6
8 3 9 4 5 7 8 3 9
4 3 6 6 5 7 3 5 5
7 2 0 3 2 9 4 5 7
3 5 5 8 3 9 6 5 7

414
Radix Sort
—  A ordenação da raiz é o algoritmo usado pelas máquinas de ordenação de cartões que agora
são encontradas apenas nos museus de informática.

RADIXSORT(A,d)
PARA i 1 A d FAÇA 3 2 9 7 2 0 7 2 0 3 2 9
ordene o arranjo A para o dígito i 4 5 7 3 5 5 3 2 9 3 5 5
6 5 7 4 3 6 4 3 6 4 3 6
8 3 9 4 5 7 8 3 9 4 5 7
4 3 6 6 5 7 3 5 5 6 5 7
7 2 0 3 2 9 4 5 7 7 2 0
3 5 5 8 3 9 6 5 7 8 3 9

415
Bucket Sort
—  A ordenação por balde funciona em tempo linear quando a entrada é gerada a partir de uma
distribuição uniforme.
—  Como a ordenação por contagem, a bucketsort presume que a entrada é gerada por um
processo aleatório que distribui elementos uniformemente sobre o intervalo [0,1).
—  A ideia é dividir o intervalo [0,1) em n subintervalos de igual tamanho, ou baldes, e depois
distribuir os n números de entrada entre os baldes.
—  Para produzir a saída, simplesmente ordenam-se os números em cada balde, e depois
percorrem-se os baldes em ordem, listando os elementos em cada um.

416
O algoritmo
BUCKET-SORT(A)
n comprimento(A)
PARA i 1 A n FAÇA
insere A[i] na lista B[ #n A[i]$ ]
PARA i 0 A n – 1 FAÇA
ordene a lista B[i] com o INSERTION-SORT
concatene as listas B[0..n - 1] em ordem

417
A
1 0.78
2 0.17
3 0.39
4 0.26
5 0.72
6 0.94
7 0.21
8 0.12
9 0.23
10 0.68

418
A B
1 0.78 0 /
2 0.17 1 .12 .17 /
3 0.39 2 .21 .23 .26 /
4 0.26 3 .39 /
5 0.72 4 /
6 0.94 5 /
7 0.21 6 .68 /
8 0.12 7 .72 .78 /
9 0.23 8 /
10 0.68 9 .94 /

419
Exercícios
1.  Ilustre a operação PARTITION sobre o arranjo A=<13, 19,9,5,12,8,7,4,11,2,6,21>.
2.  Qual valor de q a função PARTITION devolve quando todos os elementos no arranjo A[p..r] têm
o mesmo valor?
3.  Ilustre a operação COUNTING-SORT sobre o arranjo A=<6,0,2,0,1,3,4,6,1,3,2>.
4.  Ilustre a operação do RADIX-SORT sobre a seguinte lista de palavras em inglês: COW, DOG, SEA,
RUG, ROW, MOB, BOX, TAB, BAR, EAR, TAR, DIG, BIG, TEA, NOW, FOX.
5.  Ilustre a operação do BUCKET-SORT no arranjo A=<0.79, 0.13, 0.16, 0.64, 0.39, 0.20, 0.89, 0.53,
0.71, 0.42>

420
Parte 5

COMPRESSÃO DE DADOS

421
Compressão de Dados
—  A compressão de dados é o ato de reduzir o espaço ocupado por dados num determinado
dispositivo. Essa operação é realizada através de diversos algoritmos de compressão, reduzindo
a quantidade de informação necessária para representar um dado, sendo esse dado uma
imagem, um texto, ou um arquivo qualquer.
—  Comprimir dados destina-se também a retirar a redundância, baseando-se que muitos dados
contêm informações redundantes que podem ou precisam ser eliminadas de alguma forma.

422
Compressão de Dados
—  Além da eliminação da redundância, os dados são comprimidos pelos mais diversos motivos.
Entre os mais conhecidos estão economizar espaço em dispositivos de armazenamento, como
discos rígidos, ou ganhar desempenho (diminuir tempo) em transmissões.
—  Embora possam parecer sinônimos, compressão e compactação de dados são processos
distintos. A compressão, como visto, reduz a quantidade de bits para representar algum dado,
enquanto a compactação tem a função de unir dados que não estejam unidos. Um exemplo
clássico de compactação de dados é a desfragmentação de discos.

423
Classes de Compressão
—  Existem diversas formas de se classificar os métodos de compressão de dados. O mais conhecido é pela
ocorrência ou não de perda de dados durante o processo. Entretanto diversas outras formas de classificação são
úteis para se avaliar e comparar os métodos de compressão de dados, e sua aplicação em problemas
específicos.
—  Esta é a forma mais conhecida de se classificar os métodos de compressão de dados.
—  Diz-se que um método de compressão é sem perdas (em inglês, lossless) se os dados obtidos após a
compressão são idênticos aos dados originais, ou os dados que se desejou comprimir. Esses métodos são úteis
para dados que são obtidos diretamente por meios digitais onde uma pequena perda de dados acarreta o não
funcionamento ou torna os dados incompreensíveis.
—  Algumas imagens e sons precisam ser reproduzidos de forma exata, como imagens e gravações para perícias,
impressões digitais, etc.
-  Por outro lado, algumas situações permitem que perdas de dados poucos significativos ocorram.

424
Classes de Compressão
-  Em geral quando digitalizamos informações que normalmente existem de forma analógica, como
fotografias, sons e filmes, podemos considerar algumas perdas que não seriam percebidas pelo olho ou
ouvido humano.
-  Sons de frequências muito altas ou muito baixas que os humanos não ouvem, detalhes muito sutis como a
diferença de cor entre duas folhas de uma árvore, movimentos muito rápidos que não conseguimos
acompanhar num filme, todos estes detalhes podem ser omitidos sem que as pessoas percebam que eles
não estão lá.
-  Nesses casos, podemos comprimir os dados simplesmente por omitir tais detalhes. Assim, os dados
obtidos após a compressão não são idênticos aos originais, pois "perderam" as informações irrelevantes, e
dizemos então que é um método de compressão com perdas (em inglês, lossy).

425
Entropia
—  Assuma que há n diferentes símbolos para codificar uma mensagem. Assuma também que todos
os símbolos mi, , que formam o conjunto M, tem probabilidade de ocorrência P(mi), e os
símbolos são codificados com strings binárias 0s e 1s.
—  Então P(m1)+P(m2)+...+P(mn)=1.
—  A informação contida no conjunto M, chamada entropia da fonte M, é definida por
Lave=P(m1)L(m1)+...+P(mn)L(mn)
onde, L(mi) = -lg(P(mi)) (ou L(mi) = lg(1/P(mi))), que é o comprimento mínimo do símbolo mi
codificado.
—  Nenhum algoritmo de compressão de dados pode ser melhor que Lave, e quanto mais perto
deste número, melhor é a taxa de compressão.
Taxa = (Lentrada – Lsaída ) / Lentrada

426
Exemplo
—  Mensagem com três símbolos, a, b e c, com probabilidades 0.25, 0.25 e 0.5 respectivamente.
—  Então os comprimentos dos códigos de cada símbolo são:
—  lg(1/.25) = lg(4) = 2 (para a e b) e,

—  lg(1/.5) = lg(2) = 1 (para c)

—  E o comprimento médio é:
—  L
ave = .25*2 + .25*2 + .5*1 = 1.5

427
Codificação de Huffman
—  A codificação de Huffman é um método de compressão que usa as probabilidades de ocorrência
dos símbolos no conjunto de dados a ser comprimido para determinar códigos de tamanho
variável para cada símbolo.
—  Uma árvore binária completa, chamada de árvore de Huffman é construída recursivamente a
partir da junção dos dois símbolos de menor probabilidade, que são então somados em
símbolos auxiliares e estes símbolos auxiliares recolocados no conjunto de símbolos.
—  O processo termina quando todos os símbolos foram unidos em símbolos auxiliares, formando
uma árvore binária. A árvore é então percorrida, atribuindo-se valores binários de 1 ou 0 para
cada aresta, e os códigos são gerados a partir desse percurso.

428
O Algoritmo
PARA cada símbolo
cria árvore e ordena de acordo com a frequência do símbolo
ENQUANTO houver árvores (mais de 1)
tome as duas árvores com as mais baixas probabilidades
crie uma outra árvore com ambas como seus filhos e com as probabilidades somadas
associe 0 ao ramo esquerdo e 1 ao ramo direito
CRIE um único código para cada símbolo percorrendo a partir da raiz até a folha

429
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

430
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

E:9 D:12 C:19 B:21 A:39

431
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

C:19 21 B:21 A:39

E:9 D:12

432
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

B:21 A:39 40

C:19 21

E:9 D:12

433
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

40 60

C:19 21 B:21 A:39

E:9 D:12

434
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

100

40 60

C:19 21 B:21 A:39

E:9 D:12

435
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

100
0 1
40 60
0 1 0 1
C:19 21 B:21 A:39
0 1
E:9 D:12

436
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

100 A: 11
0 1 B: 10
40 60 C: 00
0 1 0 1 D: 011
C:19 21 B:21 A:39
E: 010
0 1
E:9 D:12

437
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

Lhuf =
100 A: 11
0 1 2*0.39+
B: 10 2*0.21+
40 60 C: 00
0 1 0 1 2*0.19+
D: 011
C:19 21 B:21 A:39 3*0.12+
E: 010
0 1 3*0.09=
E:9 D:12 2,21

438
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

Lhuf = Lave =
100 A: 11
0 1 2*0.39+ lg(1/0.39)*0.39+
B: 10 2*0.21+ lg(1/0.21)*0.21+
40 60 C: 00
0 1 0 1 2*0.19+ lg(1/0.19)*0.19+
D: 011
C:19 21 B:21 A:39 3*0.12+ lg(1/0.12)*0.12+
E: 010
0 1 3*0.09= lg(1/0.09)*0.09=
E:9 D:12 2,21 2,137567689

439
Codificação de Shannon-Fano
—  A codificação de Shannon-Fano é um método de estatístico de compressão sem perda de dados
que gera códigos de tamanho variável para cada símbolo dos conjunto de dados a ser
comprimido de acordo com sua probabilidade de ocorrência.
—  O método é anterior ao de codificação de Huffman, e apesar de bastante eficiente e prático,
gera resultados sub-ótimos.

440
A Ideia
—  A construção do código a ser usado para a compressão segue um algoritmo bastante simples.
Inicia-se com o levantamento das probabilidades de ocorrência de cada símbolo. Para efeitos
práticos, a contagem do número de ocorrências de cada símbolo nos dados a serem
comprimidos é o suficiente.
—  Ordena-se então esta lista de probabilidades em ordem decrescente e separa-se a lista em duas
partes de forma que cada uma dessas partes tenha aproximadamente a mesma probabilidade
(i.e. a soma da probabilidade de cada símbolo de uma parte seja o mais próximo possível de
50%).

441
A Ideia
—  A cada uma dessas partes atribui-se o primeiro dígito como sendo 0 (primeira parte) ou 1
(segunda parte). A cada metade que tiver mais de um dígito aplica-se o mesmo processo,
concatenando os dígitos atribuídos em cada etapa.
—  A sequência de dígitos que cada símbolo obteve nesse processo (os dígitos correspondentes a
cada metade de que ele fez parte) são concatenados em ordem para formar o seu código.

442
O Algoritmo
ORDENE o conjunto de símbolos de acordo com a frequência
SF(s)
SE s tem 2 elementos ENTÃO
anexe 0 ao código de um elemento
anexe 1 ao código de outro elemento
SENÃO
SE s tem mais de dois elementos ENTÃO
divida s em s1 e s2 com diferença mínima de probabilidades
estenda a codificação para cada símbolo de s1 anexando 0
estenda a codificação para cada símbolo de s2 anexando 1
SF(s1)
SF(s2)
443
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

A: 0.39
B: 0.21
C: 0.19
D: 0.12
E: 0.09

444
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

A: 0.39
0
B: 0.21
C: 0.19
D: 0.12 1
E: 0.09

445
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

A: 0.39 0
0
B: 0.21 1
C: 0.19 0
D: 0.12 1
1
E: 0.09

446
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

A: 0.39 0
0
B: 0.21 1
C: 0.19 0
D: 0.12 1 0
1
E: 0.09 1

447
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

A: 0.39 0 A: 00
0
B: 0.21 1 B: 01
C: 0.19 C: 10
0
0 D: 110
D: 0.12 1
1 E: 111
E: 0.09 1

448
Exemplo
—  Sejam os símbolos A, B, C, D e E, com as seguintes probabilidades: 0.39, 0.21, 0.19, 0.12 e 0.09.

Lsf =
A: 0.39 0 A: 00
0 2*0.39+
B: 0.21 1 B: 01 2*0.21+
C: 0.19 C: 10 2*0.19+
0
0 D: 110
D: 0.12 1 3*0.12+
1 E: 111
E: 0.09 1 3*0.09=
2,21

449
Exercícios
1.  Defina as codificações por Huffmann e Shannon-Fano, calculando Lave, Lhuf e Lsf, para os conjuntos
de símbolos e probabilidades abaixo:
1.  P, Q, R, S e T. Com probabilidades 0.1, 0.1, 0.1, 0.2 e 0.5.
2.  X, Y e Z. Com probabilidades 0.1, 0.1 e 0.8.
3.  XX, XY, XZ, YX, YY, YZ, ZX, ZY e ZZ. Com probabilidades 0.01, 0.01, 0.08, 0.01, 0.01, 0.08, 0.08, 0.08 e
0.64.
4.  X, Y e Z. Com probabilidades 0.15, 0.25 e 0.6.
5.  A, B, C. Com probabilidades 0.3, 0.33 e 0.37.
6.  A, E, I, O, U. Com probabilidades 0.45, 0.27, 0.07, 0.18 e 0.03.
7.  P, Q, R, S. Com probabilidades 0.1, 0.12, 0.16 e 0.62.

450
Codificação de Huffman Adaptativo
—  Passada preliminar para cálculo das frequências:
—  arquivos podem ser muito grandes

—  conteúdo pode ser desconhecido no momento da transmissão

—  ou fornecido em lotes (linha a linha, por exemplo)

—  Utiliza-se Huffman Adaptativo quando as probabilidades de ocorrência não são conhecidas.

451
Exemplo
—  Sequência a ser codificada: aafcccbd

452
Exemplo
—  Sequência a ser codificada: aafcccbd

(a,b,c,d,e,f)

453
Exemplo
—  Sequência a ser codificada: aafcccbd

(a,b,c,d,e,f)

454
Exemplo
—  Sequência a ser codificada: aafcccbd

0 1

(f,b,c,d,e) a

Saída: 10

455
Exemplo
—  Sequência a ser codificada: aafcccbd

0 1

(f,b,c,d,e) a

Saída: 10

456
Exemplo
—  Sequência a ser codificada: aafcccbd

0 2

(f,b,c,d,e) a

Saída: 101

457
Exemplo
—  Sequência a ser codificada: aafcccbd

0 2

(f,b,c,d,e) a

Saída: 101

458
Exemplo
—  Sequência a ser codificada: aafcccbd

1 2

a
0 1

(e,b,c,d) f

Saída: 101010

459
Exemplo
—  Sequência a ser codificada: aafcccbd

1 2

a
0 1

(e,b,c,d) f

Saída: 101010

460
Exemplo
—  Sequência a ser codificada: aafcccbd

2 2

a
1 1
f
0 1

(e,b,d) c
Saída: 101010001110

461
Exemplo
—  Sequência a ser codificada: aafcccbd

2 2

a
1 1
f
0 1

(e,b,d) c
Saída: 101010001110

462
Exemplo
—  Sequência a ser codificada: aafcccbd

2 2

a
1 1
f
0 2

(e,b,d) c
Saída: 101010001110001

463
Exemplo
—  Sequência a ser codificada: aafcccbd

2 2

a
1 1
f
0 2

(e,b,d) c
Saída: 101010001110001

464
Exemplo
—  Sequência a ser codificada: aafcccbd

3 2

a
1 2
c
0 1

(e,b,d) f
Saída: 101010001110001

465
Exemplo
—  Sequência a ser codificada: aafcccbd

3 2

a
1 2
c
0 1

(e,b,d) f
Saída: 101010001110001

466
Exemplo
—  Sequência a ser codificada: aafcccbd

2 3

a
1 2
c
0 1

(e,b,d) f
Saída: 101010001110001

467
Exemplo
—  Sequência a ser codificada: aafcccbd

2 4

a
1 3
c
0 1

(e,b,d) f
Saída: 10101000111000111

468
Exemplo
—  Sequência a ser codificada: aafcccbd

3 3

c
1 2
a
0 1

(e,b,d) f
Saída: 10101000111000111

469
Exemplo
—  Sequência a ser codificada: aafcccbd

3 3

c
1 2
a
0 1

(e,b,d) f
Saída: 10101000111000111

470
Exemplo
—  Sequência a ser codificada: aafcccbd

3 3

c
1 2
a
0 1

(e,b,d) f
Saída: 10101000111000111100110

471
Exemplo
—  Sequência a ser codificada: aafcccbd

3 4

c
2 2
a
1 1
f
0 1

(e,d) b

Saída: 10101000111000111100110
472
Exemplo
—  Sequência a ser codificada: aafcccbd

3 4

c
2 2
a
1 1
f
0 1

(e,d) b

Saída: 10101000111000111100110
473
Exemplo
—  Sequência a ser codificada: aafcccbd 8

3 5

c
3 2
a
2 1
f
1 1
b
0 1
e d

Saída: 101010001110001111001101000110
474
Exemplo
—  Sequência a ser codificada: aafcccbd 8

3 5

c
3 2
a
2 1
f
1 1
b
0 1
e d

Saída: 101010001110001111001101000110
475
Exemplo
—  Sequência a ser codificada: aafcccbd 8

3 5

c
3 2
a
1 2
f
1 1
b
0 1
e d

Saída: 101010001110001111001101000110
476
Exemplo
—  Sequência a ser codificada: aafcccbd 8

3 5

c
2 3
a
1 2
f
1 1
b
0 1
e d

Saída: 101010001110001111001101000110
477
Codificação LZW
—  O algoritmo LZW (Lempel-Ziv-Welch) é uma variante do LZ78 que visa eliminar a necessidade de
se emitir um caractere literal junto com o endereço de dicionário.
—  Para isso, o dicionário é inicializado com todos os símbolos do alfabeto (ao se usar codificação
ASCII são 256 símbolos, de 0 a 255).
—  A entrada é lida e acumulada em uma cadeia de caracteres. Sempre que a sequência não estiver
presente no dicionário emite-se o código correspondente a versão anterior (ou seja, sem o
último caractere) e adiciona-se esta nova sequência ao dicionário.
—  O processo continua até que não haja mais caracteres na entrada.

478
O Algoritmo
1.  Inicializar o dicionário contendo todos os blocos de comprimento 1.
2.  Procurar pelo maior bloco w que aparece no dicionário
3.  Codificar w pelo seu índice no dicionário
4.  Adicionar w seguido pelo 1o. símbolo do próximo bloco ao dicionário.
5.  Vá para o passo 2.

479
Exemplo
String a ser codificada: Dicionário:
0–a
abbaabbaababbaaaabaabba 1–b

480
Exemplo
String a ser codificada: Dicionário:
0–a
a|bbaabbaababbaaaabaabba 1–b
0| 2 – ab

481
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|baabbaababbaaaabaabba 1–b
0|1| 2 – ab
3 – bb

482
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|aabbaababbaaaabaabba 1–b
0|1|1| 2 – ab
3 – bb
4 – ba

483
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|abbaababbaaaabaabba 1–b
0|1|1|0| 2 – ab
3 – bb
4 – ba
5 – aa

484
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|baababbaaaabaabba 1–b
0|1|1|0|2| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb

485
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ababbaaaabaabba 1–b
0|1|1|0|2|4| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa

486
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abbaaaabaabba 1–b
0|1|1|0|2|4|2| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba

487
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aaaabaabba 1–b
0|1|1|0|2|4|2|6| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba

488
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aa|aabaabba 1–b
0|1|1|0|2|4|2|6|5| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba
10 – aaa

489
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aa|aa|baabba 1–b
0|1|1|0|2|4|2|6|5|5| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba
10 – aaa
11 - aab

490
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aa|aa|baa|bba 1–b
0|1|1|0|2|4|2|6|5|5|7| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba
10 – aaa
11 – aab
12 – baab

491
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aa|aa|baa|bb|a 1–b
0|1|1|0|2|4|2|6|5|5|7|3| 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba
10 – aaa
11 – aab
12 – baab
13 – bba

492
Exemplo
String a ser codificada: Dicionário:
0–a
a|b|b|a|ab|ba|ab|abb|aa|aa|baa|bb|a 1–b
0|1|1|0|2|4|2|6|5|5|7|3|0 2 – ab
3 – bb
4 – ba
5 – aa
6 – abb
7 – baa
8 – aba
9 – abba
10 – aaa
11 – aab
12 – baab
13 – bba

493

Вам также может понравиться