Algoritmos de Ordenação Baseados em Árvores e Seleção

Ordenação por Seleção (Seelction Sort)

O conceito fundamental da ordenação por seleção consiste em identificar o menor (ou maior) elemento dentro de um conjunto de dados e posicioná-lo em sua localização correta, repetindo esse processo para o restante dos elementos até que toda a sequência esteja organizada.

Seleção Direta

Neste método, realizamos múltiplas passagens pela lista. Em cada iteração i, buscamos o menor elemento entre a posição i e o final da lista, trocando-o com o elemento da posição i.


def ordenacao_selecao_direta(lista):
    """
    Implementação clássica do Selection Sort.
    """
    total_elementos = len(lista)
    for i in range(total_elementos):
        # Assume que o primeiro elemento não ordenado é o menor
        indice_menor = i
        
        # Procura o menor elemento no restante da lista
        for j in range(i + 1, total_elementos):
            if lista[j] < lista[indice_menor]:
                indice_menor = j
        
        # Realiza a troca se um novo menor for encontrado
        if indice_menor != i:
            lista[i], lista[indice_menor] = lista[indice_menor], lista[i]

if __name__ == "__main__":
    numeros = [84, 62, 35, 77, 55, 14, 35, 98]
    ordenacao_selecao_direta(numeros)
    print(f"Resultado da Seleção Direta: {numeros}")

Ordenação por Torneio (Tree Selection Sort)

Este algoritmo, também conhecido como Tournament Sort, utiliza uma estrutura de árvore binária para reduzir o número de comparações. A ideia é simular um torneio onde os elementos competem em pares, e o vencedor (o menor valor) sobe para o próximo nível da árvore até atingir a raiz.


import math

class OrdenadorTorneio:
    def __init__(self, dados):
        self.original = dados
        self.tamanho = len(dados)
        if self.tamanho > 0:
            self.n_folhas = 1 << (self.tamanho - 1).bit_length()
            self.arvore = [float('inf')] * (2 * self.n_folhas - 1)
            self._construir_arvore()

    def _construir_arvore(self):
        # Preenche as folhas com os dados originais
        inicio_folhas = self.n_folhas - 1
        for i in range(self.tamanho):
            self.arvore[inicio_folhas + i] = self.original[i]
        
        # Sobe na árvore decidindo os vencedores
        for i in range(inicio_folhas - 1, -1, -1):
            self.arvore[i] = min(self.arvore[2 * i + 1], self.arvore[2 * i + 2])

    def extrair_vencedor(self):
        if self.tamanho == 0:
            return None
        
        vencedor = self.arvore[0]
        if vencedor == float('inf'):
            return None

        # Localiza a folha do vencedor e a invalida
        idx = self.n_folhas - 1
        for i in range(self.n_folhas):
            if self.arvore[idx + i] == vencedor:
                self.arvore[idx + i] = float('inf')
                atual = idx + i
                # Atualiza o caminho até a raiz
                while atual > 0:
                    pai = (atual - 1) // 2
                    irmao = atual + 1 if atual % 2 == 1 else atual - 1
                    self.arvore[pai] = min(self.arvore[atual], self.arvore[irmao])
                    atual = pai
                break
        return vencedor

    def executar_sort(self):
        resultado = []
        for _ in range(self.tamanho):
            val = self.extrair_vencedor()
            if val is not None:
                resultado.append(val)
        return resultado

if __name__ == "__main__":
    exemplo = [3, 1, 4, 1, 5, 9, 2, 6]
    torneio = OrdenadorTorneio(exemplo)
    print(f"Resultado do Torneio: {torneio.executar_sort()}")

Ordenação por Heap (Heap Sort)

O Heap Sort é uma evolução otimizada da seleção em árvore. Ele utiliza uma estrutura de dados chamada Heap, que é uma árvore binária completa com propriedades específicas de ordenação entre pais e filhos.

Definições de Heap

  • Heap de Mínimo (Min-Heap): O valor de cada nó pai é menor ou igual aos valores de seus filhos. A raiz contém o menor elemento.
  • Heap de Máximo (Max-Heap): O valor de cada nó pai é maior ou igual aos valores de seus filhos. A raiz contém o maior elemento.

Estrutura e Manipulação de Heap

Os Heaps são geralmente implementados em arrays para maior eficiência de memória. Para um nó no índice i, seu filho esquerdo está em 2i + 1 e o direito em 2i + 2.


class GestorHeap:
    def __init__(self, elementos=None):
        self.heap = elementos if elementos else []
        if self.heap:
            self._construir_heap_minimo()

    def _ajustar_abaixo(self, pai, limite):
        menor = pai
        esquerda = 2 * pai + 1
        direita = 2 * pai + 2

        if esquerda < limite and self.heap[esquerda] < self.heap[menor]:
            menor = esquerda
        if direita < limite and self.heap[direita] < self.heap[menor]:
            menor = direita

        if menor != pai:
            self.heap[pai], self.heap[menor] = self.heap[menor], self.heap[pai]
            self._ajustar_abaixo(menor, limite)

    def _construir_heap_minimo(self):
        n = len(self.heap)
        for i in range(n // 2 - 1, -1, -1):
            self._ajustar_abaixo(i, n)

    def inserir(self, valor):
        self.heap.append(valor)
        atual = len(self.heap) - 1
        while atual > 0:
            pai = (atual - 1) // 2
            if self.heap[atual] < self.heap[pai]:
                self.heap[atual], self.heap[pai] = self.heap[pai], self.heap[atual]
                atual = pai
            else:
                break

    def remover_topo(self):
        if not self.heap:
            return None
        topo = self.heap[0]
        self.heap[0] = self.heap[-1]
        self.heap.pop()
        if self.heap:
            self._ajustar_abaixo(0, len(self.heap))
        return topo

if __name__ == "__main__":
    h = GestorHeap([10, 25, 15, 40, 20])
    h.inserir(5)
    print(f"Heap após inserção de 5: {h.heap}")
    print(f"Removendo topo: {h.remover_topo()}")

Implementação do Algoritmo Heap Sort

Para ordenar em ordem crescente, construímos um Max-Heap. O maior elemento é movido para o final do array, o tamanho do heap é reduzido e a propriedade de Max-Heap é restaurada na raiz.


def heap_sort_ascendente(dados):
    n = len(dados)

    def reorganizar(arr, i, tamanho_heap):
        maior = i
        esq = 2 * i + 1
        dir = 2 * i + 2

        if esq < tamanho_heap and arr[esq] > arr[maior]:
            maior = esq
        if dir < tamanho_heap and arr[dir] > arr[maior]:
            maior = dir

        if maior != i:
            arr[i], arr[maior] = arr[maior], arr[i]
            reorganizar(arr, maior, tamanho_heap)

    # 1. Cria o Max-Heap inicial
    for i in range(n // 2 - 1, -1, -1):
        reorganizar(dados, i, n)

    # 2. Extrai elementos um a um
    for i in range(n - 1, 0, -1):
        dados[0], dados[i] = dados[i], dados[0]
        reorganizar(dados, 0, i)

if __name__ == "__main__":
    lista_teste = [40, 55, 73, 12, 98, 27]
    heap_sort_ascendente(lista_teste)
    print(f"Lista Ordenada com Heap Sort: {lista_teste}")

Tags: algorithms sorting-algorithms Binary-Tree HeapSort Python

Publicado em 7-22 13:17