Ed. Parte III
Ed. Parte III
Ordenação
Ordenação (Sorting)
●
Em diversas aplicações devemos ordenar os
dados para operar de forma mais eficiente
●
Duas Abordagens:
– Inserir os elementos respeitando a ordenação
(Ordenação por Construção)
– Aplicar um algoritmo de ordenação em um
conjunto já criado
●
Os algoritmos de ordenação devem ser
eficientes em termos de tempo e espaço
Ordenação de Vetores
●
Entrada: vetor com os elementos a serem
ordenados
●
Saída: o mesmo vetor com os seus elementos
na ordem especificada
●
Ordenação: pode ser aplicada a qualquer
dado com ordem bem definida
– Vetor com dados simples (int, float, etc)
Entrada: 4 2 5 1
+ = Saída: 1 2 4 5
Relação de Ordem: >
Ordenação de Vetores (Cont.)
●
Ordenação: pode ser aplicada a qualquer
dado com ordem bem definida
– Vetor com dados complexos (structs)
●
Chave da ordenação escolhida entre os
campos
●
Em geral, ineficiente pois toda a estrutura
deve ser trocada
– Vetor de ponteiros para dados complexos
●
Troca da Ordem dos Elementos = Troca
de ponteiros
Algoritmos de Ordenação
●
Algoritmos Iterativos de Ordenação:
– Ordenação Bolha (Bubble Sort)
– Ordenação por Inserção (Insertion Sort)
– etc.
●
Algoritmos Recursivos de Ordenação
(Abordagem Dividir para Conquistar):
– Ordenação Rápida (Quick Sort)
– Ordenação por Intercalação (Merge Sort)
– etc.
Ordenação Bolha (Bubble Sort)
A idéia geral da ordenação bolha é colocar os
elementos "maiores" nos seus lugares corretos
4 2 5 1 1 2 4 5
Quando dois elementos estão fora de ordem, troque-os de posição
até que o i-ésimo elemento de maior valor do vetor seja levado para
as posições finais do vetor
Maior Elemento 2o. Maior Elemento 3o. Maior Elemento
4 2 5 1 2 4 1 5 2 1 4 5
2 4 5 1 2 4 1 5 1 2 4 5
2 4 5 1 2 1 4 5
2 4 1 5
Ordenação Bolha
void bolha(int n, int *v){
int i, j, temp;
for(i=n-1; i>0; i--)
for(j=0; j<i; j++)
if(v[j]> v[j+1]){
temp = v[j]; // Troca
v[j] = v[j+1];
v[j+1] = temp;
}
}
i=3 i=2 i=1
j=0 4 2 5 1 2 4 1 5 2 1 4 5
j=1 2 4 5 1 2 4 1 5 1 2 4 5
j=2 2 4 5 1 2 1 4 5
2 4 1 5
Ordenação Bolha (Versão II)
Quando o vetor já está ordenado em alguma passagem
void bolha(int n, int *v){
int i, j, temp, troca;
for(i=n-1; i>0; i--){
troca = 0;
for(j=0; j<i; j++)
if(v[j]> v[j+1]){
temp = v[j]; // Troca
v[j] = v[j+1];
v[j+1] = temp;
troca = 1;
}
if(troca==0)
return;
}
}
Ordenação Bolha pelo campo IRA
#include<stdio.h>
typedef struct aluno{
char nome[81];
float ira;
} Aluno;
#define MAX 10000
void inicializa(int n, Aluno **alunos);
void imprime(int n, Aluno **alunos, int i);
void bolhaIRA(int n, Aluno **alunos);
void imprime_todos(int n, Aluno **alunos);
void atualiza(int n, Aluno **alunos, int i);
void exclui(int n, Aluno **alunos, int i);
int main (void){
Aluno* alunos[MAX]; int i;
inicializa(MAX,alunos);
for(i=0;i<MAX;i++)
atualiza(MAX,alunos,i);
bolhaIRA(MAX,alunos);
imprime_todos(MAX,alunos);
for(i=0;i<MAX;i++)
exclui(MAX,alunos,i);
return 0; }
Ordenação Bolha pelo campo IRA
void bolhaIRA(int n, Aluno **alunos){
int i, j;
int troca = 0;
Aluno *tempAluno;
for(i=n-1; i>0; i--){
troca = 0;
for(j=0; j<i; j++)
if(alunos[j]->ira > alunos[j+1]->ira){
tempAluno = alunos[j];
alunos[j] = alunos[j+1];
alunos[j+1] = tempAluno;
troca = 1;
}
if(troca==0)
return;
}
}
Complexidade
●
A complexidade de um algoritmo pode ser medida
pelo esforço computacional
– Número de Comparações, Atribuições etc.
●
No caso da ordenação do método bolha, a
complexidade pode ser estimada pelo número de
comparações (semelhante ao número de trocas)
– Na primeira passada: n-1 comparações
– Na segunda passada: n-2 comparações
– ...
●
T = (n-1)+ (n-2)+...+1 = n*(n-1)/2
●
O Algoritmo é no pior caso de Ordem Quadrática
O(n2) e no melhor caso Linear O(n)
●
Na prática, a ordenação bolha não é utilizada
Ordenação Rápida (Quick Sort)
●
Escolha um elemento arbitrário x, o pivô
●
Rearrume o vetor de tal forma que x fique na
posição correta v[i]
– x deve ocupar a posição i do vetor sse
todos os elementos v[0], … v[i-1] são menores que x e
todos os elementos v[i+1], …, v[n-1] são maiores que x
●
Chame recursivamente o algoritmo para
ordenar os (sub-)vetores v[0], … v[i-1] e v[i+1],
…, v[n-1]
●
Continue até que os vetores que devem ser
ordenados tenham 0 ou 1 elemento
Ordenação Rápida (Quick Sort)
a=1
Rearrumação do vetor para o pivô de x=v[0] 4 2 5 1 b=3
do início para o final, compare x com v[1], a=2
v[2],… até encontrar v[a]>x 4 2 5 1 b=3
a=2
do final para o início, compare x com v[n-1], 4 2 5 1 b=3
v[n-2] … até encontrar v[b]<=x
a=3
troque v[a] e v[b] 4 2 1 5 b=2
●
Ordenação Rápida (Quick sort)
– coloque um elemento arbitrário x, o pivô, em sua posição k
– chame recursivamente o algoritmo para ordenar os
(sub-)vetores v[0], … v[k-1] e v[k+1], …, v[n-1]
– continue até que os vetores que devem ser ordenados
tenham 0 ou 1 elemento
– Complexidade no Caso Médio O(n log(n)), porém no Pior
Caso O(n2)
Referência
Introdução a Estruturas de Dados, Waldemar Celes,
Renato Cerqueira e José Lucas Rangel, Editora Campus,
2004.
Estruturas de Dados
Mergesort
Mergesort
●
Algoritmo baseado na estratégia dividir para
conquistar, assim como o quicksort
●
Complexidade: O(n log n)
●
Requer memória adicional - O(n)
Mergesort
O(log n)
●
Fonte: [Link]
Mergesort
void mergeSort(int *v, int n) {
mergeSort_ordena(v, 0, n-1);
}
[Link]
} edia/uploads/2013-1/bcc202/slides/
14._mergesort.pdf
Estruturas de Dados
Heapsort
Heapsort
●
A estrutura de dados heap é um objeto
arranjo que pode ser visto como uma árvore
binária praticamente completa. Cada nó da
árvore corresponde a um elemento do
arranjo que armazena o valor no nó.
●
A árvore está completamente preenchida em
todos os níveis, exceto talvez no nível mais
baixo, que é preenchido a partir da esquerda
até certo ponto.
Heapsort
●
Um arranjo A que representa um heap é um
objeto com dois atributos: comprimento[A],
que é o número de elementos do arranjo e
tamanho-do-heap[A], o número de elementos
no heap armazenado dentro do arranjo A.
●
Ou seja, embora A[1..comprimento[A]] possa
conter números válidos, nenhum elemento além
de A[tamanho-do-heap[A]] onde tamanho-do-
heap[A] ≤ comprimento[A], é um elemento do
heap.
Heapsort
●
A raiz da árvore é A[1] e, dado o índice i de um
nó, os índices de seu pai PARENT(i), do filho
da esquerda LEFT(i) e do filho da direita
RIGHT(i) podem ser calculados de modo
simples:
●
Parent(i)
return i
●
Left(i) Right(i)
return 2i return 2i + 1
Heapsort
16
16 14 10 8 7 9 3 2 4 1
14 10
8 7 9 3
2 4 1
●
De modo semelhante, o procedimento right
pode calcular rapidamente 2i+1 deslocando
a representação binária de i uma posição de
bit para a esquerda e inserindo 1 como valor
de bit de baixa ordem.
Heapsort
●
O procedimento PARENT pode calcular i/2
deslocando i uma posição de bit para a
direita.
●
Existem dois tipos de heaps binários: heaps
máximos e heaps mínimos. Em ambos os
tipos, os valores nos nós satisfazem a uma
propriedade de heap, cujos detalhes
específicos dependem do tipo de heap.
Heapsort
●
Em um heap máximo, a propriedade de heap
máximo é que para todo nó i diferente da raiz
●
Um heap mínimo é organizado de modo
oposto; a propriedade de heap mínimo é
que, para todo nó i diferente da raiz
A[PARENT(i)] ≤ A[i] o menor elemento em
um heap mínimo está na raiz.
Heapsort
●
Visualizando um heap como uma árvore, definimos a
altura de um nó em um heap como o número de arestas
no caminho descendente simples mais longo desde o nó
até uma folha, e definimos a altura do heap como a
altura de sua raiz.
●
Tendo em vista que um heap de n elementos é baseado
em uma árvore binária completa, sua altura é O(lg n)
MAX-HEAPIFY
Max-Heapify(A,i)
l ← Left(i)
r ← Right(i)
if l≤tamanho-do-heap[A] e A[l]>A[i]
then maior←l
else maior←i
if r≤tamanho-do-heap[A] e A[r]>A[maior]
then maior←r
if maior≠i
then trocar A[i]↔A[maior]
MAX-HEAPIFY(A,maior)
Manutenção da propriedade
de heap
●
MAX-HEAPIFY é uma sub-rotina importante para a
manipulação de heaps máximos. Suas entradas são um
arranjo A e um índice i para o arranjo. Quando MAX-
HEAPIFY é chamado, supomos que as árvores binárias
com raízes em LEFT(i) e RIGHT(i) são heaps máximos,
mas que A[i] pode ser menor que seus filhos, violando
assim a propriedade de heap máximo. A função de MAX-
HEAPIFY é deixar que o valor em 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.
MAX-HEAPIFY
16 16
4 10 14 10
14 7 9 3 4 7 9 3
2 8 1 2 8 1
MAX-HEAPIFY
16
Em cada passo, o maior entre os
elementos A[i], A[LEFT], A[RIGHT]
é determinado e seu índice é
14 10 armazenado em maior.
8 7 9 3
2 4 1
Construção de um Heap
●
Podemos usar o procedimento MAX-
HEAPIFY de baixo para cima, a fim de
converter um arranjo A[1..n], onde n=
comprimento[A] em um heap máximo.
BUILD-MAX-HEAP(A)
tamanho-do-heap[A]←comprimento[A]
for i← comprimento[A/2] downto 1
MAX-HEAPIFY(A,i)
BUILD-MAX-HEAP
4 1 3 2 16 9 10 14 8 7
4
4
1 3
1 3
2 16 9 10
2 16 9 10
14 8 7
14 8 7
BUILD-MAX-HEAP
4 4
1 3 1 10
3
14
2 16 9 10 14
2 16 9 10
3
14
2 8 7 14
2 8 7
BUILD-MAX-HEAP
16
4
14
1 10
3
16
1 10
3
2
8 16
7 9 10
3
14
2 16
7 9 10
3
2 8
4 7
1
14
2 8 7
1
Heap-Sort
O algoritmo heapsort começa usando
BUILD-MAX-HEAP para construir um heap
no arranjo de entrada A[1..n], onde n =
comprimento[A].
14
1 10
3 1
8 10
3
2
8 16
7 9 10
3 2
4 16
7 9 10
3
2 8
4 7
1 2 8
1 16
Heap-Sort
10 9
1
8 3
9 1
8 3
2
4 16
7 9
1 10
3 2
4 16
7 9
1 10
2
2 14 16 10 14 16
Heap-Sort
7
8
1
4 3
1
7 3
2
1 16
2 8 9
2
4 16
2 9
1 9
10 14 16
10 14 16
Heap-Sort
3
4
1
2 3
1
1
2 3
2
4 16
7 8 9
2
1 16
7 8 9
10 14 16
10 14 16
Heap-Sort
1
2
1
2 3
1 3
2
4 16
7 8 9
2
4 16
7 8 9
10 14 16
10 14 16
1 2 3 4 7 8 9 10 14 16
Slides baseados no livro Algoritmos, Teoria e Prática,
de Cormen, T.H; Leiserson, C. E.; Rivest, R. L. e Stein,
C.
Estruturas de Dados
Filas de Prioridades
Filas de Prioridades
●
O heapsort é um algoritmo excelente, mas
uma boa implementação de quicksort
normalmente o supera na prática.
●
Não obstante, a estrutura de dados de heap
pode ser empregada como uma fila de
prioridades eficiente.
●
Como ocorre no caso dos heaps, existem
dois tipos de filas de prioridades: máxima e
mínima.
Filas de Prioridades
Uma fila de prioridades é uma estrutura de
dados para manutenção de um conjunto S de
elementos, cada qual com um valor associado
chamado chave. Uma fila de prioridade
máxima admite as operações a seguir:
●
INSERT(S,x)
●
MAXIMUM(S)
●
EXTRACT-MAX(S) remove e retorna o
elemento de S com a maior chave
●
INCREASE-KEY(S,x,k) aumenta o valor da
chave do elemento x para o novo valor k.
Filas de Prioridades
HEAP-MAXIMUM(A)
return A[1]
HEAP-EXTRACT-MAX(A)
if tamanho-do-heap[A]<1
then erro 'heap underflow'
max←A[1]
A[1]←A[tamanho-do-heap[A]]
tamanho-do-heap[A]←tamanho-do-heap[A]-1
MAX-HEAPIFY(A,1)
return max
Filas de Prioridades
HEAP-INCREASE-KEY(A,i,chave)
if chave < A[i]
then erro 'nova chave é menor que chave atual'
A[i]←chave
while i>1 e A[PARENT(i)]<A[i]
troca A[i]↔A[PARENT(i)]
i←PARENT(i)
HEAP-INCREASE-KEY
16
16
14 10
14 10
8 7 9 3
8 7 9 3
2 15 1
2 4 1
HEAP-INCREASE-KEY
16
16
15 10
14 10
14 7 9 3
15 7 9 3
2 8 1
2 8 1
HEAP-INCREASE-KEY
O procedimento HEAP-INCREASE-KEY implementa a
operação INCREASE-KEY. O elemento da fila de
prioridades cuja chave deve ser aumentada é
identificado por um índice i no arranjo.
MAX-HEAP-INSERT(A,chave)
tamanho-do-heap[A]←tamanho-do-heap[A] + 1
A[tamanho-do-heap[A]]←-∞
HEAP-INCREASE-KEY(A,tamanho-do-heap[A], chave)
MAX-HEAP-INSERT
O procedimento MAX-HEAP-INSERT implementa
a operação INSERT. Ele toma como uma entrada
a chave do novo elemento a ser inserido no heap
máximo A. Primeiro, o procedimento expande o
heap máximo, adicionando à árvore uma nova
folha cuja chave é -∞. Em seguida, ele chama
HEAP-INCREASE-KEY para definir a chave
desse novo nó com seu valor correto e manter a
propriedade do heap máximo.
MAX-HEAP-INSERT
O tempo de execução de MAX-HEAP-INSERT
sobre um heap de n elementos é O(lg n).
Situação
indesejável:
inserção em
ordem
alfabética
Solução por árvores AVL
8 páginas
filhas
8 páginas
filhas
82 páginas
filhas
Eficiência da árvore paginada
C S DTAM PI B W N G U R K E H O L
JYQZFXV
Exemplo
Exemplo
Observe:
◦ A construção top-down faz com que as primeiras
chaves fiquem na página-raiz.
◦ No exemplo, C e D (consecutivas e no início do
alfabeto) não são boas escolhas para a raiz, pois
fatalmente fazem a árvore tender à direita.
E se rotacionássemos as páginas (como
fazemos com os nós)?
◦ Tente formular um algoritmo para isso
Muito complexo!
Problemas a resolver
1. Como garantir que as chaves da raiz
sejam boas separadoras, tal que
dividam o conjunto mais ou menos ao
meio?
2. Como evitar agrupamento de certas
chaves (como C, D e S) na página raiz?
3. Como garantir que cada página
contenha um certo número mínimo de
chaves?
A invenção das árvores-B
Arvores-B são uma generalização da
ideia de ABB paginada
◦ Não são binárias
◦ Conteúdo de uma página não é mantido como
uma árvore
◦ A construção é bottom-up
Um pouco de história
◦ 1972: Bayer and McGreight publicam o artigo
Organization and Maintenance of Large Ordered
Indexes
◦ 1979: árvores-B viram praticamente padrão em
sistemas de arquivos de propósito geral
Árvore-B
Características
◦ Completamente balanceadas
◦ criação bottom-up (em disco)
◦ nós folhas nó raiz
Inovação
◦ Não é necessário construir a árvore a partir do
nó raiz, como é feito para árvores em
memória principal e para as árvores
anteriores
Construção Bottom-up
Consequências
◦ Chaves ‘’erradas’’ não são mais alocadas no
nó raiz
Elimina as questões em aberto de chaves
separadoras e de chaves extremas
Não é necessário tratar o problema de
desbalanceamento
Árvore B (Parte II)
Estruturas de Dados
Engenharia de Computação