Políticas de acesso - Pilhas & Filas
Contato
- Jean Paulo Martins (jeanmartins utfpr edu br)
- Sala 105, Bloco S (UTFPR - Campus Pato Branco)
Conteúdo
Políticas de acesso e manipulação
Algumas estruturas de dados não requerem novas formas de armazenamento dos dados em memória. Nesses casos, tais estruturas são definidas apenas pela forma com a qual elas permitem o acesso e manipulação dos dados armazenados, o que chamei de política de acesso e manipulação.
Para melhor compreendermos essa ideia, iniciaremos o estudo de duas estruturas de dados (ou políticas de acesso e manipulação) de grande relevância: pilhas (stack) e filas (queue). Por serem apenas políticas de acesso, pilhas e filas podem ser implementadas utilizando como base diferentes tipos de estruturas de dados (contêiners). Neste primeiro momento, no entanto, assumiremos apenas a implementação de pilhas e filas utilizando o contêiner vector
como base.
Pilhas (stack)
A estrutura de dados Pilha recebe este nome em analogia ao processo de empilhamento. De acordo com o dicionário web Michaelis, empilhar tem o seguinte significado:
-
em·pi·lhar Dispor em pilha ou ficar amontoado em pilha; amontoar(-se):
- Empilhou os pratos que havia acabado de enxugar.
- “[…] erguia o que estava pelo chão e empilhava as cadeiras sobre as mesinhas de mármore” (AA2).
- “Entrou no seu escritório e foi sentar-se à secretária. Defronte dele, com uma gravidade oficial, empilhavam-se grandes livros de escrituração mercantil” (AA2).
De acordo com esta definição, empilhar significa inserir um objeto em cima de outro. Desempilhar, portanto, se refere a remoção do objeto no topo da pilha.
Dada a analogia, define-se que uma estrutura de dados pilha é caracterizada pelo fato de que novos elementos somente podem ser inseridos em seu topo. A remoção de elementos da pilha, similarmente, somente pode acontecer para elementos no topo. O acesso ao elemento no topo da pilha, sem a remoção do mesmo, é muitas vezes necessário, desta forma um operador se faz necessário. Em resumo:
- Novos elementos são inseridos em apenas uma direção.
- O último elemento inserido é chamado o topo da pilha
- Essa operação é chamada
push
- Elementos são removidos em direção oposta à inserção
- Somente o elemento no topo pode ser removido diretamente.
- Essa operação é chamada
pop
- Este tipo de política de acesso ficou conhecido pela sigla:
- LIFO, do inglês last in, first out. O último elemento inserido é necessáriamente o primeiro a ser removido.
Estruturas do tipo stack
são de grande utilidade em aspectos fundamentais da ciência da computação. Seja na construção de compiladores e linguagens de programação, por uma perspectiv teórica, ou no gerenciamento de memória da pilha de chamadas (stack call)
Implementações de pilhas em linguagens de programação:
- C++/STL, stack
- http://www.cplusplus.com/reference/stack/stack/
- Python, Using lists as stacks
- https://docs.python.org/2/tutorial/datastructures.html#using-lists-as-stacks
- C#, Stack
- https://msdn.microsoft.com/pt-br/library/system.collections.stack(v=vs.110).aspxs
Filas (queue)
A estrutura de dados fila (ou política de acesso) também recebe este nome por analogia ao processo de enfileiramento
- fi·la (Michaelis)
Sequência de pessoas ou coisas alinhadas uma atrás da outra, organizada geralmente por ordem cronológica de chegada ou por diferentes critérios…
- “Havia agora Betinha, Aureluce, Tanara e outras amigas barulhentas em volta, uma fila inteira delas no Cine Cruzeiro do Sul” (CFA).
- “Conjunto de soldados em fileira”
Por essa analogia é fácil notarmos que a estrutura de dados fila deve suportar operações análogas ao enfileiramento e o desenfileiramento. Em termos mais diretos, isso significa que a inserção de novos elementos em uma fila deve ser feita sempre em uma das pontas da estrutua (push_back
ou push_front
) enquanto a remoção poderia apenas ser feita na ponta oposta da estrutura (pop_front
ou pop_back
). Em resumo:
- Novos elementos são inseridos em apenas uma das pontas.
- Essa operação é chamada
push
- Essa operação é chamada
- Elementos são removidos da ponta oposta à inserção.
- Somente o elemento inicial pode ser removido diretamente.
- Essa operação é chamada
pop
- Este tipo de política de acesso ficou conhecido pela sigla:
- FIFO, do inglês first in, first out. O primeiro elemento inserido é necessáriamente o primeiro a ser removido.
Implementação sobre vector
Pilhas e filas, sendo apenas políticas de acesso, podem ser implementadas utilizando diferentes estruturas de dados como base (contêiner). Neste primeiro momento, utilizaremos como contêiner a estrutura vector
implementada anteriormente.
Pilhas
É fácil notar que todas as funcionalidades necessárias para manipular uma pilha estão implementadas para vector
. De fato, precisamos apenas de um subconjunto das funcionalidades disponíveis em vector
, ou seja, pilhas são mais restritivas.
// **** stack.h ****
#include "vector.h"
#include <stdbool.h>
typedef vector stack;
#define STACK_INIT_SIZE 10
// Alocação desalocação
stack* new_stack();
void free_stack(stack* v);
// Inserção e remoção de elementos
Type stack_pop(stack* v);
void stack_push(stack* v, Type value);
// Observer o topo da pilha, sem remoção
Type* stack_top(stack* v);
bool stack_empty(stack* v);
Vejamos quais funcionalidades de vector
serão úteis para implementarmos uma estrutura pilha (stack
).
Opção 1
void vector_push_back(vector* v, Type value);
Type vector_pop_back(vector* v);
Opção 2
void vector_push_front(vector* v, Type value);
Type vector_pop_front(vector* v);
Cada uma dessas opções insere e remove elementos de uma das pontas de vector
. Por razões de eficiência, utilizaremos a opção 1 a seguir.
- Questão: Porque devemos preferir a opção 1 para implementação de
stack
se o contêiner base forvector
?
// **** stack.c ****
#include "stack.h"
// Alocação desalocação
stack* new_stack() {
stack* s = new_vector(STACK_INIT_SIZE);
return s;
}
void free_stack(stack* v){
free_vector(v);
}
// Inserção e remoção de elementos
Type stack_pop(stack* v) {
return vector_pop_back(v);
}
void stack_push(stack* v, Type value) {
vector_push_back(v, value);
}
// Retorna um ponteiro para o topo da pilha
Type* stack_top(stack* v) {
return vector_at(v, v->size-1);
}
bool stack_empty(stack* v) {
return v->size == 0;
}
Filas
É fácil notar que todas as funcionalidades necessárias para manipular uma estão implementadas para vector
. De fato, precisamos apenas de um subconjunto das funcionalidades disponíveis em vector
, ou seja, filas são mais restritivas.
Vejamos quais funcionalidades de vector
serão úteis para implementarmos uma estrutura pilha (queue
).
Opção 1
void vector_push_back(vector* v, Type value);
Type vector_pop_front(vector* v);
Opção 2
void vector_push_front(vector* v, Type value);
Type vector_pop_back(vector* v);
Cada uma dessas opções insere elementos em uma das pontas de vector
e remove da outra.
- Questão: Quais as deficiências em termos da eficiência de cada uma das opções?
// **** queue.c ***
#include "queue.h"
// Alocação desalocação
queue* new_queue() {
queue* s = new_vector(QUEUE_INIT_SIZE);
return s;
}
void free_queue(queue* v){
free_vector(v);
}
// Inserção e remoção de elementos
Type queue_pop(queue* v) {
return vector_pop_back(v);
}
void queue_push(queue* v, Type value) {
vector_push_back(v, value);
}
// Retorna um ponteiro para o topo da pilha
Type* queue_begin(queue* v) {
return vector_at(v, 0);
}
Eficiência das implementações
- Qual a ineficiência da implementação de
queue
utilizandovector
diretamente. - Implementação circular (pg. 207 Tenenbaum)