Heap Sort em Java
1. Introdução
Neste tutorial, veremos como o Heap Sort funciona e implementamos em Java.
*A Classificação de Heap é baseada na estrutura de dados da Heap.* Para entender a Classificação de Heap corretamente, primeiro examinaremos os Heaps e como eles são implementados.
2. Estrutura de dados de heap
Um Heap é uma estrutura de dados baseada em árvore especializada . Portanto, é composto de nós. Atribuímos os elementos aos nós: cada nó contém exatamente um elemento.
Além disso, os nós podem ter filhos. Se um nó não tem filhos, chamamos de folha.
O que a Heap torna especial são duas coisas:
-
o valor de cada nó deve ser menor ou igual a todos os valores armazenados em seus filhos
-
é uma árvore completa , o que significa que tem a menor altura possível
Por causa da 1ª regra, o menor elemento sempre estará na raiz da árvore .
Como aplicamos essas regras depende da implementação.
Geralmente, os heaps são usados para implementar filas de prioridade porque o Heap é uma implementação muito eficiente de extrair o menor (ou maior) elemento.
2.1. Variantes de heap
O heap tem muitas variantes, todas elas diferem em alguns detalhes de implementação.
Por exemplo, o que descrevemos acima é um Min-Heap, porque um pai é sempre menor que todos os seus filhos . Como alternativa, poderíamos ter definido o Max-Heap, caso em que um pai é sempre maior que seus filhos. Portanto, o maior elemento estará no nó raiz.
Podemos escolher entre muitas implementações em árvore. O mais direto é uma árvore binária. Em uma Árvore Binária, cada nó pode ter no máximo dois filhos. Nós os chamamos de filho esquerdo e filho direito .
A maneira mais fácil de aplicar a 2ª regra é usar uma Árvore Binária Completa. Uma árvore binária completa segue algumas regras simples:
-
se um nó tiver apenas um filho, esse deve ser seu filho esquerdo
-
somente o nó mais à direita no nível mais profundo pode ter exatamente um filho
-
folhas só podem estar no nível mais profundo
Vamos ver estas regras com alguns exemplos:
1 2 3 4 5 6 7 8 9 10
() () () () () () () () () ()
/ \ / \ / \ / \ / \ / / / \
() () () () () () () () () () () () () ()
/ \ / \ / \ / / \
() () () () () () () () ()
/
()
As árvores 1, 2, 4, 5 e 7 seguem as regras.
As árvores 3 e 6 violam a 1ª regra, 8 e 9 a 2ª regra e 10 violam a 3ª regra.
Neste tutorial, nos concentraremos no Min-Heap com uma implementação de Árvore Binária .
2.2. Inserindo elementos
Devemos implementar todas as operações de uma maneira que mantenha os invariantes do Heap. Dessa forma, podemos construir o Heap com inserções repetidas , portanto, focaremos na operação de inserção única.
Podemos inserir um elemento com as seguintes etapas:
-
crie uma nova folha que seja o slot disponível mais à direita no nível mais profundo e armazene o item nesse nó
-
se o elemento for menor que o pai, nós os trocamos
-
continue na etapa 2, até que o elemento seja menor que o pai ou se torne a nova raiz
Observe que a etapa 2 não violará a regra Heap, porque se substituirmos o valor de um nó por um menor, ele ainda será menor que seus filhos.
Vamos ver um exemplo! Queremos inserir 4 neste Heap:
2
/\
/ \
3 6
/\
5 7
O primeiro passo é criar uma nova folha que armazena 4:
2
/\
/ \
3 6
/\ /
5 7 4
Como 4 é menor que seu pai, 6, trocamos:
2
/\
/ \
3 4
/\ /
5 7 6
Agora verificamos se 4 é menor que seu pai ou não. Como seu pai é 2, paramos. O Heap ainda é válido e inserimos o número 4.
Vamos inserir 1:
2
/\
/ \
3 4
/\ /\
5 7 6 1
Temos que trocar 1 e 4:
2
/\
/ \
3 1
/\ /\
5 7 6 4
Agora devemos trocar 1 e 2:
1
/\
/ \
3 2
/\ /\
5 7 6 4
Como 1 é a nova raiz, paramos.
3. Implementação de heap em Java
Como usamos uma Árvore binária completa, podemos implementá-la com uma matriz : um elemento na matriz será um nó na árvore. Marcamos todos os nós com os índices da matriz da esquerda para a direita, de cima para baixo da seguinte maneira:
0
/\
/ \
1 2
/\ /
3 4 5
A única coisa que precisamos é acompanhar quantos elementos armazenamos na árvore. Dessa forma, o índice do próximo elemento que queremos inserir será o tamanho da matriz.
Usando essa indexação, podemos calcular o índice dos nós pai e filho:
-
pai: _ (índice - 1)/2_ filho esquerdo: 2 index + 1