Heap Sort em Java

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:

  1. o valor de cada nó deve ser menor ou igual a todos os valores armazenados em seus filhos

  2. é 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:

  1. se um nó tiver apenas um filho, esse deve ser seu filho esquerdo

  2. somente o nó mais à direita no nível mais profundo pode ter exatamente um filho

  3. 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:

  1. crie uma nova folha que seja o slot disponível mais à direita no nível mais profundo e armazene o item nesse nó

  2. se o elemento for menor que o pai, nós os trocamos

  3. 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