Você está na página 1de 4

Pilhas

.......................................
Universidade Federal do Semiárido – Mossoró, RN
Ciência da Computação, 2011.1
.......................................
Tibérius O. Bonates – tbonates@ufersa.edu.br

1 Introdução
Pilhas estão entre as estruturas de dados mais simples, e provavelmente mais frequentes, em Com-
putação, juntamente com filas e listas. Uma estrutura de pilha oferece as operações de empilhar
(adicionar) e desempilhar (remover) um elemento, análogas às operacoes de empilhar um livro sobre
uma pilha de livros, ou desempilhar o livro que se encontra na posição mais alta da pilha. Conforme
veremos nos exemplos e exercı́cios deste capı́tulo, pilhas surgem naturalmente em diversas aplicações.

Seja E um conjunto de elementos quaisquer (números inteiros, por exemplo), alguns dos quais
desejamos armazenar para posterior acesso ou modificação. Uma pilha de elementos de E é uma
estrutura que armazena uma sequência de objetos provenientes de E. A ideia de sequência entre os
objetos é essencial na definição de uma pilha, pois a ordem dos elementos nesta sequência determina
a ordem em que os elementos poderão ser acessados posteriormente: os elementos inseridos na pilha
só podem ser recuperados de acordo com a ordem inversa de sua inserção, tal como na pilha de livros
utilizada como analogia. É importante destacar que, de acordo com nossa definição, uma pilha pode
conter elementos repetidos.

Seja P uma estrutura de dados pilha. Se P não for vazia, o elemento que foi adicionado mais
recentemente a P é denominado de topo de P . Caso contrário, dizemos que P não possui topo.
Durante uma operação empilhar aplicada sobre P , um novo elemento x é adicionado a P e passa
a ser o novo topo da pilha. Durante uma operação desempilhar aplicada sobre P , três objetivos
principais são atingidos: (i) o elemento y que era o topo da pilha é removido de P ; (ii) caso P ainda
possua algum elemento, aquele que foi adicionado mais recentemente a P passa a ser o novo topo
de P ; e (iii) o elemento y é retornado ao código que invocou a operação desempilhar.

Em resumo, podemos dizer que o seguinte conjunto de regras define as operações de empil-
har/desempilhar sobre uma pilha P :
(ii) ao empilhar um elemento x, este elemento passa a ser o topo da pilha;
(ii) ao desempilhar o topo da pilha (assumindo que a pilha não se encontra vazia), o elemento
(topo) é removido de P e retornado ao código que requisitou a operação de desempilhar, e a
informação sobre o topo de P é atualizada;
(iii) uma vez que o elemento x foi inserido na pilha, ele somente será removido se uma operação
de desempilhar for realizada enquanto x for o topo de P .

1
Exemplo 1.1 Considere uma pilha de números inteiros P contendo três elementos, conforme mos-
trado na Figura 1(a). Cada elemento é representado por um retângulo que contém o número inteiro
em questão. O topo da pilha é apontado por uma seta. A Figura 1(b) ilustra o resultado da operação
de desempilhar sobre a pilha da Figura 1(a). Similarmente, a Figura 1(c) mostra o resultado das
aplicações sucessivas das operações de empilhar o elemento 5 e empilhar o elemento 8 sobre a pilha
da Figura 1(b).

(a) Pilha original (b) Após operação de desempilhar (c) Após operações de empilhar

Figure 1: Exemplo de operações de empilhar e desempilhar.

É importante reforçar a ideia de que apenas um dos objetos armazenados na pilha está realmente
acessı́vel em um determinado momento, e pode ser obtido prontamente por meio de uma operação de
desempilhar. Este fato pode parecer uma limitação artificial e desnecessária, mas a realidade é que a
estrutura de pilha pode ser muito conveniente para assegurar que o acesso a um determinado conjunto
de dados aconteça de acordo com uma ordem bastante especı́fica, garantindo que o processamento
dos dados armazenados se dará na ordem desejada. Como exemplo, considere a funcionalidade tı́pica
de um editor de texto, discutida a seguir no Exemplo 1.2.

Exemplo 1.2 Desfazer/Refazer. Considere as operacoes de Desfazer/Refazer (Undo/Redo, em


Inglês) de um editor de texto padrão. Imagine que as seguinte operações foram realizadas sobre um
texto, nesta ordem: justificar parágrafo, adicionar palavra, mudar cor do texto. Imagine que, após
a terceira operação, a função Desfazer foi utilizada. O sistema deve garantir que a operação mais
recentemente efetuada sobre o texto (e somente ela) seja desfeita. Ou seja, a cor original do texto
será restaurada. Similarmente, se a operação Desfazer for novamente utilizada, a operação mais
recentemente aplicada sobre o texto atual (“adicionar palavra”) será desfeita. A função Desfazer de
um editor de texto deve seguir, portanto, a regra de funcionamento de uma pilha. Da mesma forma,
a operação de Refazer deve utilizar uma segunda pilha, permitindo que um conjunto de operações
previamente “desfeitas” sejam “refeitas”, ou seja, reaplicada sobre o texto, na ordem em que haviam
sido originalmente aplicadas.

2 Implementação por vetor


A maneira mais simples de se implementar uma estrutura de pilha é por meio de um vetor. Considere
o exemplo de uma pilha de inteiros. Neste caso, um vetor de inteiros será utilizado para armazenar
de forma sequencial os elementos armazenados na pilha. Juntamente com este vetor, uma variável
inteira, chamada topo, armazenará a posição do elemento armazenado no topo da pilha em um
dado momento. Por fim, uma variável inteira adicional, chamada tamanho, será utilizada para
guardar o número máximo de elementos que a pilha poderá armazenar. Por questão de organização,
utilizaremos um registro para armazenar as variáveis topo e tamanho, além de um ponteiro para
inteiro dados, que irá apontar para o vetor de dados, conforme mostrado a seguir.

2
struct Pilha {
int tamanho;
int topo;
int * dados;
};

2.1 Iniciando a pilha


Antes de se usar uma variável do tipo Pilha, é necessário iniciá-la adequadamente. A razão para isto
é que a pilha deve estar inicialmente vazia (sem nenhum elemento armazenado), e a variável topo
deve assumir um valor que reflita esta situação. Utilizaremos a convenção de que a variável topo
tem valor −1 em um pilha vazia. De forma geral, a variável terá valor k − 1 em uma pilha contendo
k elementos. O primeiro elemento armazenado na pilha ocupará a posição 0 do vetor apontado por
dados, o segundo elemento ocupará a posição 1, e assim por diante. Desta forma, se a estrutura
for corretamente iniciada (e as operações subsequentes corretamente implementadas) é possı́vel se
determinar o número de elementos atualmente armazenados na pilha por meio da inspeção do valor
de topo.

O seguinte código pode ser utilizado para se iniciar uma variável do tipo Pilha, passada como
parâmetro, por referência:
void iniciar(Pilha &p, int tamanho) {
p.tamanho = tamanho;
p.topo = -1; // convencao: topo -1 significa que a pilha esta’ vazia
p.dados = new int[p.tamanho]; // vetor de dados alocado dinamicamente
}

2.2 Empilhando um elemento


Conforme descrito anteriormente, a operação de empilhar um elemento em um pilha envolve o
armazenamento do elemento e a atualização da informação sobre o topo da pilha. Em nossa im-
plementação por vetor, antes de armazenar o novo elemento no vetor dados e atualizar a variável
topo, é importante verificar se a pilha ainda comporta mais um elemento, ou seja, se o tamanho da
pilha ainda não atingiu seu limite, fornecido pelo campo tamanho. No código a seguir, o parâmetro
valor armazena o novo elemento que se deseja armazenar na pilha.
void empilhar(Pilha &p, int dado) {
if (p.topo < (p.tamanho-1)) {
++ p.topo;
p.dados[p.topo] = dado;
}
else
cout << "Pilha esta’ cheia!" << endl;
}
Perceba que, se o valor de topo for p.tamanho-1, então a pilha contém tamanho elementos e não
podemos realizar a operação empilhar.

2.3 Desempilhando um elemento


Assim como na operação de empilhar, é necessário assegurar que o tamanho da pilha é válido antes
de se aplicar a operação desempilhar. Para tanto, é suficiente verificar se a variável topo não indica

3
uma pilha vazia (ou seja, topo é diferente de −1). Como a função deve retornar o elemento que
ocupava o topo da pilha, é preciso definir o valor que será retornado caso a pilha esteja vazia. Uma
convenção bastante utilizada em tais casos é a de retornar um valor que não esteja no domı́nio dos
valores possı́veis para a pilha. Em nossa implementação, iremos convencionar um valor de retorno
igual a -1 para sinalizar que a pilha estava vazia e nada foi desempilhado.

Caso a pilha possua algum elemento armazenado, a função deve remover o elemento do topo
e atualizar a informação sobre a posição do elemento que se encontrará no topo após a operação
ser concluı́da. Em nossa implementação por vetor, a remoção do elemento no topo é desnecessária,
sendo suficiente atualizar o valor da variável topo. É possı́vel que a pilha se torne vazia após a
operação.

int desempilhar(Pilha &p) {


int valor = -999999;
if (p.topo >= 0) {
valor = p.dados[p.topo];
-- p.topo;
}
else
cout << "Pilha esta’ vazia!" << endl;
return valor;
}

É recomendado, portanto, verificar se a pilha encontra-se vazia antes de se requisitar uma


operaçc̃ao de desempilhamento. Caso contrário, dependendo dos valores que se pretende armazenar
na pilha, é possı́vel que o valor -999999 retornado seja confundido com um valor v alido que estava
armazenado no topo da pilha. A operação de verificar se a pilha encontra-se vazia é de imple-
mentação simples e omitida aqui. Uma maneira de se fazer uso seguro do código de desempilhar
seria:

if (! vazia(P)) {
int valor = desempilhar(P);
// processamento do dado...
}

Outra maneira, talvez mais elegante, de se implementar a função desempilhar seria conforme
descrito a seguir. A função retornaria um valor booleano, informando se houve ou não o desempi-
lhamento. Além disso, a função receberia um parâmetro inteiro por referência, o qual seria utilizado
para armazenar o valor que se encontrava no topo da pilha antes do desempilhamento. O seguinte
código ilustra um uso seguro desta implementação alternativa:

int valor;
if (desempilhar(P, valor)) {
// processamento do dado...
}

Você também pode gostar