Pilhas em Python: Guia Completo com Implementações Práticas

As estruturas de dados são essenciais na programação, funcionando como alicerces para organizar informações de forma eficaz. Entre elas, a pilha se destaca por sua simplicidade e utilidade.

Neste artigo, exploraremos a fundo o conceito de pilha e suas implementações práticas em Python.

O que Define uma Pilha?

Pense em uma pilha como um monte de objetos, como livros ou cadeiras, onde o último item adicionado é o primeiro a ser removido. Esse é o princípio fundamental da pilha: Último a Entrar, Primeiro a Sair (LIFO, do inglês Last-In/First-Out). Para ilustrar, imagine a seguinte situação:

Imagine uma pilha de livros.

Para acessar o terceiro livro de cima, você precisa remover os dois primeiros. O livro que chegou por último à pilha é o primeiro a ser retirado. Essa lógica LIFO é a base da estrutura de dados pilha na programação.

Operações Básicas em Pilhas

As pilhas operam principalmente por meio de duas ações:

1. push(dado)

Essa operação insere um novo elemento no topo da pilha, como adicionar um livro no topo da pilha física.

2. pop()

Essa operação remove o elemento do topo da pilha, como tirar o livro mais recente de uma pilha de livros.

As ilustrações abaixo demonstram essas ações visualmente.

Além dessas operações, algumas funções auxiliares são úteis para obter informações sobre o estado da pilha.

Vejamos algumas delas:

olhadinha()

Essa função permite verificar qual elemento está atualmente no topo da pilha, sem removê-lo.

está_vazia()

Essa função verifica se a pilha está vazia ou se contém algum elemento.

Agora que compreendemos os conceitos teóricos da pilha, vamos à sua implementação prática. Vamos supor que você já tenha o Python instalado ou que esteja usando um compilador online.

Implementando Pilhas em Python

A implementação de pilhas é relativamente simples em comparação com outras estruturas de dados. Em Python, podemos implementar uma pilha de várias maneiras.

A seguir, exploraremos algumas abordagens:

#1. Usando Listas

Neste método, criaremos uma classe para encapsular a lógica de pilha usando listas. Vamos detalhar cada passo da implementação.

Passo 1: Definir a classe Pilha.

class Pilha:
	pass

Passo 2: Criar uma lista vazia para armazenar os elementos.

class Pilha:
	def __init__(self):
		self.elementos = []

Passo 3: Implementar a função `push` para adicionar elementos à lista.

class Pilha:
	def __init__(self):
		self.elementos = []

	def push(self, dado):
		self.elementos.append(dado)
		return dado

Passo 4: Implementar a função `pop` para remover o elemento do topo da lista.

class Pilha:
	## ...
	def pop(self):
		return self.elementos.pop()

Até este ponto, implementamos a pilha com as operações básicas. Agora, adicionaremos as funções auxiliares para obter mais informações sobre o estado da pilha.

Passo 5: Implementar a função `olhadinha` para verificar o elemento do topo sem removê-lo.

class Pilha:
	## ...

	def olhadinha(self):
		return self.elementos[-1]

Passo 6: Implementar a função `está_vazia` para verificar se a pilha está vazia.

class Pilha:
	## ...
	def está_vazia(self):
		return len(self.elementos) == 0

Concluímos a implementação da pilha usando listas como base. Agora, vamos aprender a usar nossa implementação.

Para utilizar a classe `Pilha`, precisamos criar um objeto dessa classe. A seguir, veremos como fazer isso.

class Pilha: 
    ## ...

if __name__ == '__main__':
    pilha = Pilha()

Criamos o objeto pilha e agora estamos prontos para usar as operações. Para testar, vamos seguir os seguintes passos:

  • Verificar se a pilha está vazia usando `está_vazia`. O resultado deve ser `True`.
  • Inserir os números 1, 2, 3, 4 e 5 na pilha usando `push`.
  • Verificar se a pilha ainda está vazia usando `está_vazia`. O resultado deve ser `False`.
  • Exibir o elemento do topo usando `olhadinha`.
  • Remover o elemento do topo usando `pop`.
  • Verificar novamente o elemento do topo usando `olhadinha`. O resultado deve ser 4.
  • Remover todos os elementos da pilha usando `pop`.
  • Verificar se a pilha está vazia pela última vez usando `está_vazia`. O resultado deve ser `True`.

Se todos os passos forem executados corretamente, nossa implementação da pilha estará concluída. Tente escrever o código para esses passos.

Se você precisar de ajuda, aqui está o código completo:

class Pilha: 
    def __init__(self): 
        self.elementos = [] 
    
    def push(self, dado): 
        self.elementos.append(dado) 
        return dado 
    
    def pop(self): 
        return self.elementos.pop() 
        
    def olhadinha(self): 
        return self.elementos[-1] 
        
    def está_vazia(self): 
        return len(self.elementos) == 0

if __name__ == '__main__':
    pilha = Pilha()
    
    ## verificando se está vazia -> true
    print(pilha.está_vazia())

    ## inserindo elementos
    pilha.push(1)
    pilha.push(2)
    pilha.push(3)
    pilha.push(4)
    pilha.push(5)

    ## verificando novamente se está vazia -> false
    print(pilha.está_vazia())

    ## mostrando o elemento do topo -> 5
    print(pilha.olhadinha())

    ## removendo o elemento do topo -> 5
    pilha.pop()

    ## verificando o elemento do topo novamente -> 4
    print(pilha.olhadinha())

    ## removendo todos os elementos
    pilha.pop()
    pilha.pop() 
    pilha.pop() 
    pilha.pop() 

    ## verificando se está vazia pela última vez -> true
    print(pilha.está_vazia())

Parabéns! Implementamos uma pilha do zero usando listas em Python. Se você executar o código acima, obterá a saída a seguir:

True
False
5
4
True

Podemos usar listas diretamente como pilhas, mas a implementação acima ajuda a entender o conceito de pilha em outras linguagens de programação.

Além disso, você pode consultar estes artigos relacionados a listas.

Agora, vamos explorar o `deque`, que é fornecido pelo módulo `collections` do Python, como alternativa para implementar pilhas.

#2. Usando `deque` do módulo `collections`

O `deque` é implementado como uma fila de duas extremidades, permitindo adicionar e remover elementos em ambas as pontas. Essa característica o torna útil para implementarmos uma pilha seguindo o princípio LIFO.

Internamente, ele é implementado usando listas duplamente encadeadas. Isso garante um desempenho consistente na inserção e remoção de elementos. A desvantagem das listas encadeadas é o acesso aos elementos do meio, que levam tempo O(n). No entanto, como em uma pilha raramente precisamos acessar os elementos intermediários, isso não é um problema.

Antes de implementarmos a pilha, vejamos os métodos do `deque` que utilizaremos:

  • `append(dado)` – Insere o elemento no topo da pilha (fim do deque).
  • `pop()` – Remove o elemento do topo da pilha (fim do deque).

Não existem métodos diretos para `olhadinha` e `está_vazia`, mas podemos imprimir todo o deque para “ver” o topo e usar `len()` para verificar se está vazio. Vejamos a implementação:

from collections import deque

## criando objeto deque
pilha = deque()

## verificando se a pilha está vazia -> true
print(len(pilha) == 0)

## inserindo os elementos
pilha.append(1)
pilha.append(2)
pilha.append(3)
pilha.append(4)
pilha.append(5)

## verificando novamente se a pilha está vazia -> false
print(len(pilha) == 0)

## mostrando a pilha
print(pilha)

## removendo o elemento do topo -> 5
pilha.pop()

## mostrando a pilha novamente
print(pilha)

## removendo todos os elementos
pilha.pop()
pilha.pop() 
pilha.pop() 
pilha.pop() 

## verificando se a pilha está vazia pela última vez -> true
print(len(pilha) == 0)

Aprendemos como implementar uma pilha usando `deque`. A saída deste código será:

True
False
deque([1, 2, 3, 4, 5])
deque([1, 2, 3, 4])
True

Até agora, vimos duas formas de implementar pilhas. Há ainda uma terceira forma de implementar pilhas que exploraremos a seguir.

#3. Usando `LifoQueue` do módulo `queue`

O nome `LifoQueue` indica que ela segue o princípio LIFO, sendo perfeita para implementar pilhas. Ela é fornecida pelo módulo `queue` do Python e oferece alguns métodos convenientes:

  • `put(dado)` – Adiciona um elemento à pilha.
  • `get()` – Remove o elemento do topo da pilha.
  • `empty()` – Verifica se a pilha está vazia.
  • `qsize()` – Retorna o tamanho da pilha.

Vejamos como implementar pilhas usando `LifoQueue`:

from queue import LifoQueue

## criando objeto LifoQueue
pilha = LifoQueue()

## verificando se a pilha está vazia -> true
print(pilha.empty())

## adicionando os elementos
pilha.put(1)
pilha.put(2)
pilha.put(3)
pilha.put(4)
pilha.put(5)

## verificando se a pilha está vazia novamente -> false
print(pilha.empty())

## removendo todos os elementos
print(pilha.get())
print(pilha.get())
print(pilha.get())

## mostrando o tamanho da pilha
print("Tamanho", pilha.qsize())

print(pilha.get())
print(pilha.get())

## verificando se a pilha está vazia pela última vez -> true
print(pilha.empty())

A saída deste código será:

True
False
5
4
3
Tamanho 2
2
1
True

Aplicações Práticas de Pilhas

Agora que você conhece bem as pilhas, vamos explorar como aplicá-las em problemas reais de programação. Considere o problema de verificar o balanceamento de parênteses, chaves e colchetes em uma expressão.

Por exemplo:

Entrada: “[{}]([])”

Saída: Equilibrado

Entrada: “{[}]([])”

Saída: Não Equilibrado

Podemos usar uma pilha para resolver esse problema. Veja os passos:

  • Criar uma pilha para armazenar os caracteres.
  • Se o comprimento da expressão for ímpar, a expressão não é balanceada.
  • Iterar sobre a expressão:
    • Se o caractere atual for um parêntese, chave ou colchete de abertura, insira-o na pilha.
    • Caso contrário, se o caractere for de fechamento, remova um elemento da pilha.
    • Se o caractere removido corresponder ao de abertura, continue, caso contrário, a expressão é desbalanceada.
  • Ao finalizar a iteração, se a pilha estiver vazia, a expressão estará balanceada, caso contrário, não estará.

Podemos utilizar a implementação de pilha com listas para a verificação do balanceamento:

## pilha
class Pilha: 
    def __init__(self): 
        self.elementos = [] 
    
    def push(self, dado): 
        self.elementos.append(dado) 
        return dado 
        
    def pop(self): 
        return self.elementos.pop() 
    
    def olhadinha(self): 
        return self.elementos[-1] 
        
    def está_vazia(self): 
        return len(self.elementos) == 0

def verificar_balanceamento(expressão):
    ## verificando o comprimento da expressão
    if len(expressão) % 2 != 0:
        ## se for ímpar não é balanceada
        return False
    
    ## para verificação
    abertura = set('([{') 
    pares = set([ ('(',')'), ('[',']'), ('{','}') ]) 
    
    ## inicialização da pilha
    pilha = Pilha()
    
    ## iteração sobre a expressão
    for caractere in expressão:

        ## verifica se o caractere é de abertura        
        if caractere in abertura:
            ## adiciona na pilha 
            pilha.push(caractere)
        else:
            ## remove da pilha o último caractere
            caractere_removido = pilha.pop()
        
            ## verifica se o caractere removido e o atual formam um par
            if (caractere_removido, caractere) not in pares:
                return False
    
    return pilha.está_vazia()

if __name__ == '__main__':
    if verificar_balanceamento('[{}]([])'):
        print("Balanceado")
    else:
        print("Não Balanceado")
    
    if verificar_balanceamento('{[}]([])'):
        print("Balanceado")
    else:
        print("Não Balanceado")

As pilhas são úteis em muitos outros problemas de programação. Este exemplo é apenas um deles. Experimente aplicar o conceito de pilhas sempre que possível.

Conclusão

Parabéns, você concluiu este tutorial sobre pilhas. Espero que tenha sido útil para você. Este é o fim deste tutorial.

Bons estudos 🙂 👨‍💻