Você está na página 1de 18
Departamento de Informática Curso de Ciência da Computação ESTRUTURAS DE DADOS Professora: Alessandra Dahmer Santa
Departamento de Informática Curso de Ciência da Computação ESTRUTURAS DE DADOS Professora: Alessandra Dahmer Santa

Departamento de Informática Curso de Ciência da Computação

ESTRUTURAS DE DADOS

Professora: Alessandra Dahmer

Santa Cruz do Sul, março de 2000.

1

INTRODUÇÃO

O objetivo desta disciplina é introduzir as principais Estruturas de Dados e seus algoritmos.

Mas antes de mais nada, deve-se responder a uma questão: Qual o papel das Estruturas de Dados no processo de desenvolvimento de software?

Em um projeto de software, existem dois aspectos que devem ser estudados: os procedimentos que devem ser previstos pelo software e sobre que dados estes procedimentos irão atuar.

Nas técnicas estruturadas de projeto de software era dada ênfase aos procedimentos, com a identificação dos aspectos funcionais na primeira etapa de desenvolvimento do software. Com as técnicas para especificação dos dados a nível conceitual, a importância dos procedimentos e dados tornou-se equilibrada. Atualmente, já existem técnicas de programação com ênfase nos dados (programação baseada em objetos).

Mas, independentemente das técnicas de análise e programação utilizadas, programas precisam manipular dados e é extremamente importante o conhecimento de conceitos e detalhes da implementação das diversas estruturas de dados que manipulam estes dados.

Neste texto, inicialmente, alguns conceitos básicos são apresentados. À seguir, são descritas as principais Estruturas de Dados, com seus respectivos algoritmos.

2 CONCEITOS BÁSICOS

Neste capítulo, serão apresentados conceitos essenciais para o desenvolvimento desta disciplina: Tipos de Dados, Tipos Abstratos de Dados e Estruturas de Dados.

2.1 TIPOS DE DADOS

Em computação precisamos identificar os tipos de dados que o computador, a linguagem de programação ou mesmo um algoritmo são capazes de entender. De uma forma geral, os Tipos de Dados são diferenciados pelos valores que podem assumir e pelo conjunto de operações que podemos efetuar com eles.

Em linguagens de programação o Tipo de Dados de uma variável define o conjunto de valores que esta variável pode assumir. Uma variável do tipo lógico, por exemplo, pode assumir dois valores: verdadeiro ou falso. As possibilidades do hardware são previstas pela linguagem.

2

Os tipos de dados são divididos em: Tipos Primitivos de Dados e Tipos Estruturados de Dados.

Os Tipos Primitivos de Dados são os tipos básicos, a partir dos quais podemos definir

os demais tipos e estruturas de dados. Estes tipos de dados são os mais freqüentes nas linguagens de programação e tem um conjunto de valores e operações restrito. São considerados Tipos Primitivos de Dados:

Inteiro - representa uma quantidade “que pode ser contada”.

Real - Representa um valor que pode ser fracionado.

Lógico - Pode representar dois estados (verdadeiro ou falso).

Caracter - Pode representar dígitos, letras ou sinais.

Ponteiro - Representa o endereço de um dado na memória.

Os Tipos Estruturados de Dados são construídos a partir dos tipos primitivos. Estes tipos são previstos por muitas linguagens de programação e devem ser definidos pelo programador. Exemplos de tipos estruturados: array e registro. Estes dois tipos são formados por tipos básicos como inteiros, caracteres, reais, etc.

Uma declaração de variável em uma linguagem de programação como Pascal especifica duas coisas:

Quantos bytes devem ser reservados para armazenar esta variável (Ex: no caso de uma variável inteira, deve ser reservado um espaço que garanta que o maior inteiro permitido poderá ser representado) Como estes bytes devem ser interpretados (Ex: uma cadeia de bits pode ser interpretada como um inteiro ou um real).

Assim, Tipos de Dados podem ser vistos como métodos para interpretar o conteúdo da memória do computador.

2.2 TIPOS ABSTRATOS DE DADOS

O conceito de tipo de dados pode ser visto de uma outra perspectiva: levando em conta, não o que um computador pode fazer, mas o que os usuários desejam fazer. Este conceito de Tipo de Dados independente do hardware é chamado de Tipo Abstrato de Dados - TAD.

Um Tipo Abstrato de Dados é composto por um modelo matemático acompanhado de um conjunto de operações definidas sobre este modelo.

Uma vez que um

TAD é

definido e as

operações associadas a este tipo são

especificadas, pode-se implementar este tipo de dado.

3

A característica essencial de um TAD é a separação entre conceito e implementação. A descrição dos valores e o conjunto de operações do TAD são fornecidos ao usuário, mas a implementação é invisível e inacessível. A separação da definição do TAD da sua implementação permite que uma mudança de implementação não altere o programa que usa o TAD.

Exemplos de TAD:

Árvores

Listas

Pilhas

Filas

2.3 ESTRUTURAS DE DADOS

Um algoritmo é projetado em termos de tipos abstratos de dados. Para implementá-los numa linguagem de programação é necessário encontrar uma forma de representá-los nessa linguagem, utilizando tipos e operações suportadas pelo computador. Para representar o modelo matemático do TAD, em uma linguagem de programação, emprega-se uma Estrutura de Dados.

As Estruturas de Dados diferem umas das outras pela disposição ou manipulação de

seus dados. A disposição dos dados em uma estrutura obedece a condições preestabelecidas e caracteriza a estrutura.

Assim, Estrutura de Dados é um método particular de se implementar um TAD. A implementação de um TAD escolhe uma estrutura de dados(ED) para representá-lo. Cada ED pode ser construída a partir de tipos básicos (inteiro, real, caracter) ou estruturados (array, registro) de uma determinada linguagem de programação.

O estudo de Estruturas de Dados não pode ser desvinculado de seus aspectos

algorítmicos. A escolha correta da estrutura adequada a cada caso depende diretamente do conhecimento de algoritmos para manipular a estrutura de maneira eficiente.

Por muito tempo os projetos de software consideraram importante identificar as estruturas de dados e os algoritmos bem cedo no ciclo de vida de um software. Com o surgimento de linguagens como ADA, tornou-se possível deixar as decisões sobre as ED para bem mais tarde no projeto de desenvolvimento de software.

Nos próximos capítulos serão apresentados as principais Estruturas de Dados utilizadas para representar listas, pilhas, filas e árvores. Além disso, serão apresentados os algoritmos que devem ser utilizados para manipular estas estruturas.

4

3

LISTAS LINEARES

Definição: Dizemos que um conjunto de registros R={ R1, R2, R3,

, Rn }, onde n >

0, tem uma estrutura de lista linear para uma determinada relação de precedência, quando:

R 1 é o 1 elemento; R n é o último elemento; R k precede R k+1 para 1 £ k < n;

As listas lineares são estruturas de tamanho variável e podem ter seus elementos ordenados ou não. Cada elemento de uma lista linear pode também ser chamado de nodo ou nó.

As operações mais frequentes em listas lineares são as seguintes::

1) Acessar o k-ésimo elemento (Exemplo: Acessar o segundo elemento - k=2 )

2) Inserir nodo antes/depois do k-ésimo elemento

3) Retirar o k-ésimo elemento (Exemplo: Acessar o terceiro elemento - k=3 )

4) Contar o número de nodos (A contagem do número de nodos define o tamanho, ou comprimento, da lista)

5) Pesquisar por um valor (A pesquisa por um valor busca encontrar a posição do nodo que contém a informação que está sendo procurada)

6) Combinar (Combinar duas listas lineares é colocar os elementos das duas listas em uma só, de forma ordenada ou não)

7) Dividir (Listas lineares podem ser divididas em duas ou mais listas, de acordo com algum critério pré-definido. Exemplo: Dividir a lista ao meio)

8) Classificar (Os elementos de uma lista linear podem ser classificados. Exemplo:

Elementos em ordem alfabética)

Casos particulares de listas, definidos basicamente com restrições para inclusões e remoções nas listas, serão detalhados nos próximos capítulos. São eles: pilhas, filas e deques.

As listas lineares são classificadas de acordo com o tipo de armazenamento em: listas lineares sequenciais e as listas lineares encadeadas. A seguir serão apresentados estes dois tipos de listas e os respectivos algoritmos para implementar as principais operações.

5

3.1

LISTAS SEQUENCIAIS

As listas lineares sequenciais utilizam alocação sequencial, neste tipo de alocação deve- se estabelecer previamente o tamanho definitivo da lista. Nas linguagens de programação a maneira mais utilizada para implementar este tipo de lista é o vetor.

Os espaços da lista são ocupados sequencialmente na memória. A identificação dos registros ocupados é gerenciada pela linguagem ou pelo próprio programador. Geralmente utiliza-se um ponteiro para indicar a última posição “ocupada” ( na figura abaixo é representado por f).

“ocupada” ( na figura abaixo é representado por f). A seguir serão apresentados os algoritmos para

A seguir serão apresentados os algoritmos para implementar as principais operações

com listas lineares sequencias, como a representada pela figura acima.

1) Acessar o k-ésimo elemento:

if k > f or k < 1 then “Elemento não existe” else VALOR ¨ INFO k

2) Inserir Nodo depois do k-ésimo elemento:

if k > f or k < 1

then “K-ÉSIMO ELEMENTO NÃO EXISTE”

else if f = n then “LISTA CHEIA” else i ¨ f while i >
else
if
f = n
then “LISTA CHEIA”
else
i ¨
f
while i > k
INFO i+1 ¨ INFO i
i ¨
i - 1
INFO k+1 ¨ VALOR
f ¨ f + 1

6

3) Inserir Nodo antes do k-ésimo elemento:

if k > f or k < 1

then “K-ÉSIMO ELEMENTO NÃO EXISTE”

else if f = n then “LISTA CHEIA” else i ¨ f while i k
else
if
f = n
then “LISTA CHEIA”
else
i ¨
f
while i k
INFO i+1 ¨ INFO i
i ¨
INFO k ¨ VALOR
f ¨ f + 1
i - 1

4) Retirar o k-ésimo elemento:

if k > f or k < 1

then “K-ÉSIMO ELEMENTO NÃO EXISTE”

else

VALOR ¨ INFO k

while k < f

NÃO EXISTE” else VALOR ¨ INFO k while k < f INFO k ¨ INFO k

INFO k ¨ INFO k+1 k ¨ k+1

f ¨

f - 1

5) Contar o Número de Nodos:

CONTA ¨ 0 if f ^ then CONTA ¨ f

6) Pesquisar por um valor:

k ¨

while k £ f and INFO k VALOR k ¨ k + 1

if

1

k £ f then “ACHEI NA POSIÇÃO k” else “NÃO ACHEI”

7

7) Inserir Nodo na posição correta em uma lista sequencial ordenada, sem repetições de elementos:

if

f = n then “LISTA CHEIA” else

f ¨ f + 1 INFO f ¨ VALOR

k ¨

while k ^ and INFO k > VALOR

f

k ¨

k

- 1

if k = ^ or INFO k < VALOR then

i ¨ f while i > k INFOi+1 ¨ INFO i i ¨ i -
i ¨
f
while i > k
INFOi+1 ¨ INFO i
i ¨
i - 1
INFO i+1 ¨ VALOR
f ¨ f + 1
else “VALOR JÁ ESTÁ NA LISTA”

3.2 LISTAS ENCADEADAS

As listas encadeadas utilizam alocação dinâmica. Este tipo de alocação é ideal quando não pode-se definir previamente o tamanho da lista. Assim, não é possível definir o tamanho de memória necessário para armazenar toda a lista.

Neste caso, são utilizados registros distribuídos aleatoriamente na memória e interligados através de ponteiros para o endereço de memória para o próximo elemento. Uma lista encadeada pode ser representada pela figura abaixo:

lista encadeada pode ser representada pela figura abaixo: Cada elemento da lista é formado por um

Cada elemento da lista é formado por um campo de informação (INFO) e por um ponteiro para o próximo elemento (PROX).

Existem uma rotina para alocar espaço na memória para armazenar cada novo elemento da lista e outra para liberar a memória de um elemento retirado da lista. Nos

8

algoritmos

DESALOQUE, respectivamente.

apresentados

a

seguir

estas

rotinas

3.2.1 Listas Simplesmente Encadeadas

serão

representadas

por:

ALOQUE

e

Simplesmente Encadeadas serão representadas por: ALOQUE e INICIAL: i ¨ ^ 1) Acessar o k-ésimo elemento:

INICIAL:

i ¨

^

1) Acessar o k-ésimo elemento:

if

i = ^ or k < 1

then “Elemento não existe”

else j ¨ i CONTA ¨ 1 while CONTA < k and j ^ j
else
j ¨
i
CONTA ¨ 1
while CONTA < k and j ^
j ¨ PROX j
CONTA ¨ CONTA + 1
if
j = ^
then “NÃO EXISTE ELEMENTO K”
else VALOR ¨ INFO j

2) Inserir Nodo depois do nodo apontado por k:

ALOQUE(t) INFO t ¨ VALOR PROX t ¨ PROX k PROX k ¨ t

3) Inserir Nodo antes do nodo apontado por k:

ALOQUE(t)

INFO t ¨ VALOR PROX t ¨ k

if i = k then i ¨ t else a ¨ i while PROX a
if
i = k
then i ¨ t
else
a ¨
i
while PROX a k
a ¨ PROX a
PROX a ¨ t

9

4) Retirar o nodo apontado por k:

if i = k then i ¨ PROX i else a ¨ i while PROX
if
i = k
then i ¨ PROX i
else
a ¨
i
while PROX a k
a ¨ PROX a
PROX a ¨ PROX k
DESALOQUE(k)

5) Contar o Número de Nodos:

CONTA ¨ 0 ¨ while p ^ CONTA ¨ CONTA + 1 ¨

p

i

p PROX p

6) Pesquisar por um valor:

p ¨

while p ^ and INFO p VALOR

i

p ¨ PROX p

if

p ^ then “ACHEI” else “NÃO ACHEI”

7) Combinar duas listas:

PROX f ¨ p ¨

f

j

p

¨

^

j

¨

^

4 PILHAS

Uma pilha é uma lista linear na qual todas as inserções e remoções são efetuadas no mesmo extremo da lista, no final da lista linear. Ou seja:

A inserção de um elemento torna-o o último da lista linear O elemento retirado na remoção é sempre o último elemento da lista (topo).

na remoção é sempre o último elemento da lista ( topo ). Devido às características das

Devido às características das operações da pilha, o último elemento a ser inserido será o primeiro a ser retirado. Estruturas deste tipo são conhecidas como LIFO (“Last In, First Out”).

Notação:

Topo da Pilha: elemento ao qual se tem acesso imediato

Empilhar: colocar um elemento no topo da pilha

Desempilhar: retirar o elemento do topo

Exemplo: Em Pascal, as pilhas são utilizadas para implementar a recursividade em um programa.

4.1 PILHAS SEQUENCIAIS

para implementar a recursividade em um programa. 4.1 PILHAS SEQUENCIAIS 11 INICIAL: t ¨ ^ (TOPO

11

INICIAL:

t ¨

^

(TOPO DA PILHA)

EMPILHAR (PUSH)

if

t = n

then “OVERFLOW”

else

EMPILHAR (PUSH) if t = n then “OVERFLOW” else t ¨ INFO t ¨ VALOR t

t ¨

INFO t ¨ VALOR

t + 1

4.2 PILHAS ENCADEADAS

else t ¨ INFO t ¨ VALOR t + 1 4.2 PILHAS ENCADEADAS EMPILHAR (PUSH) ALOQUE(p)

EMPILHAR (PUSH)

ALOQUE(p)

INFO p ¨ VALOR PROX p ¨ t

t ¨

p

Obs: A rotina ALOQUE já prevê o OVERFLOW.

12

DESEMPILHAR (POP)

if

t = ^

then “UNDERFLOW”

else

12 DESEMPILHAR (POP) if t = ^ then “UNDERFLOW” else VALOR ¨ INFO t t ¨

VALOR ¨ INFO t

t ¨

t - 1

INICIAL:

t ¨

^

(TOPO DA PILHA)

DESEMPILHAR (POP)

if

t = ^

then “UNDERFLOW”

else

VALOR ¨ INFOt

p ¨

t ¨ PROX t DESALOQUE(p)

t

5 FILAS

Uma fila é uma lista linear onde todas as operações de inserção são efetuadas apenas no final e remoções apenas no início da lista linear. Ou seja:

A inserção de um elemento torna-o o último da lista linear O elemento retirado na remoção é sempre o primeiro

O elemento retirado na remoção é sempre o primeiro Devido às características das operações da fila,

Devido às características das operações da fila, o primeiro elemento a ser inserido será o primeiro a ser retirado. Estruturas deste tipo são conhecidas como FIFO (“First In, First Out”).

Exemplo: Fila de uma impressora, onde as primeiras solicitações que chegam são as primeiras a serem impressas.

5.1 FILAS SEQUENCIAIS

são as primeiras a serem impressas. 5.1 FILAS SEQUENCIAIS INSERÇÃO if SUC(f) = 1 then “OVERFLOW”

INSERÇÃO

if SUC(f) = 1

then “OVERFLOW”

else

INSERÇÃO if SUC(f) = 1 then “OVERFLOW” else f ¨ SUC(f) INFO f ¨ VALOR 13

f ¨ SUC(f) INFO f ¨ VALOR

13

if

REMOÇÃO

i = f

then “UNDERFLOW”

else

else f ¨ SUC(f) INFO f ¨ VALOR 13 if REMOÇÃO i = f then “UNDERFLOW”

i ¨ SUC(i) VALOR ¨ INFO i

SUC(p)

if p = n then else

SUC ¨ 1 SUC ¨ p + 1

ÞÞ

SUC(p) if p = n then else SUC ¨ 1 SUC ¨ p + 1 ÞÞ

Obs : A Rotina SUC implementa a lista sequencial de maneira circular, permitindo que todos os espaços livres da lista possam ser ocupados.

5.2 FILAS ENCADEADAS

5.2.1 Filas Simplesmente Encadeadas

5.2 FILAS ENCADEADAS 5.2.1 Filas Simplesmente Encadeadas INSERÇÃO ALOQUE(p) INFOp ¨ VALOR PROXp ¨ ^ if

INSERÇÃO

ALOQUE(p)

INFOp ¨ VALOR PROXp ¨ ^

if

i = ^

f ¨

then i ¨ p

else

PROXf ¨ p

p

INICIAL:

i

f

¨

¨

^

^

REMOÇÃO

if

i = ^

then “UNDERFLOW”

else p ¨ i i ¨ PROXi VALOR ¨ INFOp DESALOQUE(p)
else
p ¨
i
i ¨ PROXi
VALOR ¨ INFOp
DESALOQUE(p)

5.2.2 Filas Encadeadas com Header

if i = ^ then “UNDERFLOW” else p ¨ i i ¨ PROXi VALOR ¨ INFOp

14

INSERÇÃO

ALOQUE(p) INFOp ¨ VALOR PROXp ¨ ^ PROXf ¨ p ¨

f

p

INICIAL:

ALOQUE(i)

PROX i ¨ ^

f ¨

^

REMOÇÃO

if PROXi

=

^

then “UNDERFLOW”

else

p ¨ PROXi VALOR ¨ INFOp

PROXi ¨ PROXp

if

DESALOQUE(p)

p = f

then f ¨ i

5.2.3 Filas Encadeadas Circulares com Header

f then f ¨ i 5.2.3 Filas Encadeadas Circulares com Header INSERÇÃO ALOQUE(p) INFOp ¨ VALOR

INSERÇÃO

ALOQUE(p) INFOp ¨ VALOR PROXp ¨ i PROXf ¨ p ¨

f

p

INICIAL:

ALOQUE(i)

PROX i ¨ i

f ¨

^

REMOÇÃO

if PROXi = i

then “UNDERFLOW”

else

p ¨ PROXi VALOR ¨ INFOp

PROXi ¨ PROXp

if

DESALOQUE(p)

p = f

then f ¨ i

15

6 DEQUES

Um deque (“Double-Ended QUEue”) é uma lista linear onde as operações de inserção e remoção podem ser efetuadas tanto no início quanto no final da lista linear. Ou seja:

A inserção de um elemento pode torná-lo o primeiro ou o último da lista linear O elemento retirado na remoção é o primeiro ou o último elemento da lista

na remoção é o primeiro ou o último elemento da lista Existem variações de deque, restrições

Existem variações de deque, restrições nos deques faz com que uma das operações só possa ser efetuada em uma das extremidades do deque:

deque de entrada restrita:

onde as inserções só podem ser efetuadas ou no início ou no final da lista linear

ser efetuadas ou no início ou no final da lista linear deque de saída restrita :

deque de saída restrita: onde as remoções só podem ser efetuadas ou no início ou no final da lista linear

ser efetuadas ou no início ou no final da lista linear Podemos perceber que pilhas e

Podemos perceber que pilhas e filas são casos particulares de deque, dependendo das restrições que forem feitas quanto à inserção e retirada.

16

6.1

DEQUES SEQUENCIAIS

6.1 DEQUES SEQUENCIAIS INSERÇÃO - EXT.A REMOÇÃO - EXT.A if PRED(i) = f then “OVERFLOW” if

INSERÇÃO - EXT.A

REMOÇÃO - EXT.A

if PRED(i) = f

then “OVERFLOW”

if

i = f

then “UNDERFLOW”

else

else i ¨ PRED(i) INFOi ¨ VALOR else VALOR ¨ INFOi i ¨ SUC(i)

i ¨ PRED(i) INFOi ¨ VALOR

else

else i ¨ PRED(i) INFOi ¨ VALOR else VALOR ¨ INFOi i ¨ SUC(i)

VALOR ¨ INFOi i ¨ SUC(i)

INSERÇÃO - EXT.B

 

REMOÇÃO - EXT.B

if SUC(f) = 1

 

if

i = f

then “OVERFLOW”

else

if SUC(f) = 1   if i = f then “OVERFLOW” else INFOf ¨ VALOR f

INFOf ¨ VALOR f ¨ SUC(f)

SUC(p)

if p = n then else

SUC ¨ 1 SUC ¨ p + 1

then “UNDERFLOW”

else

else SUC ¨ 1 SUC ¨ p + 1 then “UNDERFLOW” else f ¨ PRED(f) VALOR

f ¨ PRED(f) VALOR ¨ INFOf

PRED(p) if p = 1 then PRED ¨ n else PRED ¨ p - 1

PRED(p) if p = 1 then PRED ¨ n else PRED ¨ p - 1 Obs

Obs : As rotinas SUC(sucessor) e PRED(predecessor) implementam o

sequencial de forma circular, permitindo que todos os espaços livres da lista possam ser ocupados.

deque

17

6.2

DEQUES ENCADEADOS

6.2 DEQUES ENCADEADOS INSERÇÃO - INÍCIO ALOQUE(p) INFO(p) ¨ VALOR PROXp ¨ PROXi ANTp ¨ i

INSERÇÃO - INÍCIO ALOQUE(p) INFO(p) ¨ VALOR PROXp ¨ PROXi ANTp ¨ i

ANT PROXp ¨ p

PROXi ¨ p

INSERÇÃO - FINAL ALOQUE(p) INFO(p) ¨ VALOR f ¨ ANTi PROXp ¨ PROXf ANTp ¨ f ANT PROXp ¨ p PROXf ¨ p

REMOÇÃO - INÍCIO if PROXi = i

then “UNDERFLOW”

else

p ¨ PROXi PROX p ¨ A N T p ¨ PROXp ANT P R O X p ¨ ANTp ANTp ¨ PROXp ANT PROXp ¨ ANTp DESALOQUE(p)

REMOÇÃO - FINAL if PROXi = i

then “UNDERFLOW”

18

else

p ¨ ANTi PROX p ¨ A N T p ¨ PROXp ANT P R O X p ¨ ANTp ANTp ¨ PROXp ANT PROXp ¨ ANTp DESALOQUE(p)