7
Aula 1º
RELEMBRANDO LISTAS
Olá,
Bem-vindos(as) à disciplina de Estruturas de Dados
II. Aqui, vamos aprofundar nossos estudos a respeito das
estruturas de dados, analisando e estudando estruturas mais
avançadas e poderosas. Para que isso seja possível, nesta aula,
iremos recapitular as listas, que é uma das estruturas de dados
mais simples que existe.
Leiam atentamente esta aula e façam os exercícios. Se
enfrentarem alguma dúvida, estaremos à sua disposição na área
do aluno.
Bons estudos!
Objetivos de aprendizagem
Ao término desta aula, vocês serão capazes de:
• relembrar o que são listas;
• compreender o que são listas lineares;
• diferenciar listas lineares e listas encadeadas.
8 Estrutura de Dados II 6
lista é o primeiro a ser removido. Com essa regra os dados são
Seções de estudo empilhados formando uma pilha.
Em uma pilha, a operação de inserção, chamada de EMPILHAR, realiza
1. Relembrando o que são listas a inserção no elemento posterior ao último item da lista, naquele
2. Listas Lineares e as suas operações básicas instante. A operação de remoção, denominada de DESEMPILHAR,
3. Listas Encadeadas retorna e remove o último elemento da lista.
A seguir, veMamos o ȵu[o de inserção e remoção em uma pilha
1 - Relembrando o que são listas EMPILHAR(2)
Uma lista pode ser definida como um conjunto de 2
dados dispostos de maneira linear, ou seja, um dado é EMPILHAR(6)
posto na sequência do anterior. Sua disposição de dados 2 6
é como se fosse um vetor, mas com operações bem EMPILHAR(4)
definidas sobre os dados que são armazenados nesse
conjunto. 2 6 4
De modo formal, podemos definir uma lista como DESEMPILHAR()
um conjunto de n >= 0 nós, tais que as suas propriedades 2 6 4
estruturais decorrem unicamente da posição relativa dos nós EMPILHAR(78)
dentro da sequência linear. Possui as seguintes propriedades:
2 6 78
• Existem n elementos na sequência.
• Se n > 0, lista[1] é o primeiro nó. -£ as ȴlas são listas que adotam a disciplina de acesso FIFO (First In,
• Para 1 < k <= n, o nó lista[k] é precedido por l[k-1]. First Out), que prevê que o primeiro item a entrar na lista seja o
• O valor lista[n] é o último nó. primeiro a sair dela. Em ȴlas são previstas a operação E1FILEIRAR,
• Se n = 0, a lista está vazia. que insere um elemento no ȴm da lista - da mesma forma que
• No nosso caso, vamos admitir a faixa de índices para ocorre com a operação de inserção em uma pilha -, e a operação
a lista sendo 1...n. DESE1FILEIRAR, que remove o elemento localizado na primeira
posição preenchida na ȴla.
Geralmente, usamos listas para armazenar registros que
A seguir, mostramos o ȵu[o de inserção e remoção em uma ȴla
podem ser identificados através de um campo denominado
chave. Usualmente, utilizamos a chave para recuperar um
certo registro. Suponha uma lista que armazena os alunos E1FILEIRAR(2)
desse curso. Podemos usar o RGM desses alunos como sendo 2
a chave para identificar cada aluno. Assim, para recuperar o E1FILEIRAR(5)
registro de um determinado aluno, informamos o seu RGM
2 5
para acessarmos os seus dados.
E1FILEIRAR(4)
455.124 455.900 455.567 2 5 4
“Fulano de Tal” “Zé das Couves” “Marcela das Couves” DESE1FILEIRAR()
Em uma lista, você pode definir as regras que disciplinam 5 4
o armazenamento dos dados, como: E1FILEIRAR(1)
Armazenamento de forma ordenada ou desordenada. 5 4 1
Se os elementos podem ser armazenados com chaves E1FILEIRAR(6)
iguais ou não, que são chamadas de listas com chaves únicas
(onde um elemento x considerado de chave não se repete ao 5 4 1 6
longo dos dados salvos) ou chaves não únicas (que podem ser DESE1FILEIRAR()
repetidas). 4 1 6
Disciplina de acesso aos dados, que definem quais serão E1FILEIRAR(9)
as regras que definem a inserção e a remoção dos dados.
9 4 1 6
Listas com disciplinas de acesso
Podemos chamar as listas que implementam regras de inserção e
As listas podem ser programadas das seguintes formas:
remoção de elementos como Listas com disciplinas de acesso. Os
principais tipos de listas que preveem regras de acesso são as pilhas • Por contiguidade de nós (Lista Linear): É
e as ȴlas. criado um vetor de M posições que armazenamos
As pilhas são listas que implementam a regra de acesso, conhecida sequencialmente os dados. A desvantagem é
como LIFO, que prevê que o último elemento a ser inserido na que o número máximo de dados que podem ser
armazenados fica restrito a quantidade M;
7 9
76 23
• Determinar o maior valor armazenado.
Nesta aula, vamos retomar as operações básicas que
“Marcos” “Luciano”
podemos implementar em uma lista: a busca de um elemento,
• Por encadeamento dinâmico dos nós (Lista a inserção e a remoção.
Encadeada): É alocado um novo elemento da
2.1 – Busca de um elemento
lista a cada inserção. Não há um limite previamente
definido de elementos a ser armazenados. Ou seja, Definimos a busca de um elemento como uma função,
podemos armazenar quantos elementos desejarmos, que recebe a lista e um valor de referência x, e retorna
desde que a memória consiga armazenar todos os um índice i que contém como chave o valor x. Se não for
dados. encontrado é retornado o valor -1. O pseudocódigo mostra a
implementação da busca linear em uma lista, dado uma chave
a ser procurada. Vejamos a seguir:
Fonte: FEOFILOFF, 2017.
funcao BUSCA(x : inteiro) : inteiro
var
Nas próximas duas seções, vamos relembrar as operações
i : inteiro
que são realizadas nesses dois tipos de listas, bem como alguns inicio
aspectos particulares dessas listas. i <- 0
enquanto (i <= tamanho) faca
2 - Listas Lineares e as suas operações se lista[i].chave = x entao
básicas retorne i
fimse
Como vimos na seção introdutória, a Lista Linear é uma fimenquanto
lista programada sobre um vetor linear, com um tamanho pré- //se nada for encontrado
definido no momento da programação do sistema. retorne -1
Na ilustração a seguir, temos uma lista linear de quatro fimfuncao
posições. Nesse caso, só podemos armazenar apenas quatro
Se a lista for ordenada, podemos utilizar a busca
elementos de uma vez.
binária para tirar o proveito das vantagens da ordenação. O
12 25
pseudocódigo desta implementação está reproduzido a seguir:
funcao BUSCA_BIN(x : inteiro) : inteiro
“André” “Marcos”
var
esq, meio, dir : inteiro
Usualmente, temos uma lista programada em um
inicio
vetor, onde em cada posição tem um registro que conterá esq <- 0
vários campos. O principal deles se chama chave, e é por dir <- tamanho
meio da chave que podemos recuperar os dados da lista. enquanto (esq < dir) faca
Para auxiliar as operações da lista, usamos duas variáveis de meio <- (esq+dir)/2
controle: tamanho, que vai conter o tamanho máximo do se (lista[meio].chave = x) faca
vetor e; preenchido, que conterá a quantidade n de valores retorne meio //elemento encontrado
preenchidos atualmente no vetor. Para acessarmos a lista, fimse
usamos o vetor global lista. se (lista[meio].chave < x) faca
Podemos declarar em pseudocódigo uma estrutura de esq = meio
uma lista da seguinte forma. Vejamos: senao
dir = meio
tamanho, preenchidos : inteiro fimse
lista : vetor fimenquanto
(...)
tamanho <- 5 //pode ser qualquer outro return -1
preenchidos <- 0 //Não tem nenhum fimfuncao
lista <- novo vetor[1..tamanho] de Dados
2.2 - Inserção de um elemento
Em uma lista podemos fazer diversas operações, como:
• Busca de dados. A inserção de um elemento consiste na ampliação da
• Inserção de dados. lista, por meio da inclusão do item com a chave x. Ele consiste
• Remoção de dados. nos seguintes passos:
• Ordenação de dados. • Verificar se os preenchidos são menores que o valor
• Alteração de dados. de tamanho. Se for, indica que há espaços suficientes
• Combinação de duas ou mais listas em uma. para armazenar o novo dado. Se não tiver, aparece
• Determinar o número de nós preenchidos de uma a mensagem “Overflow” (Transbordou), e recusará a
lista. nova inserção.
• Determinar o menor valor armazenado. • Verificar se a chave já existe na lista. Se não existir,
10 Estrutura de Dados II 8
pode ser feita a inserção. Se existir, a inserção escreva(“Elemento não existe!”)
é recusada, pois a lista é de chaves únicas. (Se a fimse
lista não tiver essa restrição, é desnecessária essa senao
comparação). escreva(“Underflow”)
fimse
• Satisfeitas as duas restrições, o novo valor é
fimprocedimento
inserido no final da lista. O valor de preenchidos é
incrementado. Agora que nós relembramos as principais operações,
A seguir apresentamos o pseudocódigo desta operação vamos mostrar as vantagens e as desvantagens de uma lista
de inserção: linear. A vantagem é a simplicidade de implementação, pois
procedimento INSERE(x : inteiro) construímos a lista com apenas um vetor sequencial.
inicio Aliás, o mesmo motivo que justifica as facilidades é o
se (preenchidos < tamanho) entao mesmo motivo que causa as desvantagens. Pelo fato de
se (BUSCA(X) = -1) entao que um vetor é limitado a um tamanho prédefinido pelo
//insere programador, temos que limitar o número máximo de dados
lista[preenchidos+1].chave = x a serem salvos. Além disso, para as operações de inserção e
preenchidos <- preenchidos + 1 remoção, dependendo do algoritmo que utilizamos, podemos
senao envolver movimentações de dados, o que pode prejudicar o
escreva(“Elemento já existe”) desempenho do programa.
fimse Para resolver esses problemas, usamos as listas
senao
encadeadas, aliando registros a ponteiros. Esse é um assunto
escreva(“Overflow”)
fimse
que veremos na próxima seção.
fimprocedimento
3 - Listas Encadeadas
Por causa da busca, a complexidade do pior caso deste
algoritmo é de O(n), se usarmos a busca linear. Se a lista Ao contrário das listas lineares, onde um elemento é
não tiver a restrição de chaves únicas, podemos dispensar armazenado de forma sequencial, as listas encadeadas usam o
essa busca, e a complexidade cai para O(1), pois o algoritmo recurso de ponteiros para criar uma lista de tamanho variável,
passará apenas a fazer a inserção do dado. onde cada elemento da lista é um registro com vários campos.
Um deles é um ponteiro para o mesmo tipo de registro, que
2.3 - Remoção de um elemento aponta para o próximo elemento. Essa característica é
A remoção consiste em retirar um elemento com chave fundamental, pois permite a ligação dos elementos da lista,
x da lista, liberando uma posição na memória. mesmo que os elementos estejam armazenados em posições
Ele consiste nas seguintes etapas: diferentes de memória.
O código a seguir mostra um registro que pode ser usado
• Verificar se existe algum elemento salvo na lista. Se para armazenar itens de uma lista. O campo prox informa o
não houver elementos, não terá como fazer essa endereço do próximo elemento da lista.
execução. Assim, o programa dispara a mensagem
“Underflow” (estouro negativo) e encerra a execução. tipo
• Se houver elementos, o procedimento pesquisará se registro TLista:
info : inteiro
a chave x existe na lista. Se não tiver, o procedimento
prox : *TLista
é encerrado. fimregistro
• Se o elemento for encontrado, os elementos
posteriores ao elemento a ser removido são À medida que os elementos forem inseridos, teremos
“empurrados” para a esquerda, sobrescrevendo o uma configuração como mostra a figura a seguir: têm-se várias
valor a ser removido. variáveis, e cada uma dessas variáveis aponta para a próxima
variável:
Vamos ver o pseudocódigo dessa operação:
procedimento REMOVE(x : inteiro)
var
Figura 1 - Lista Encadeada. Fonte: FEOFILOFF, 2017.
indice, valor_recuperado : inteiro
inicio Como vimos na matéria anterior, existem três tipos
se (preenchidos <> 0) entao
de listas. A primeira delas é a mais básica que podemos
indice <- BUSCA(x)
se (indice <> -1) entao
implementar, chamada de listas simplesmente encadeadas,
valor_recuperado <- lista[indice] exatamente como descrevemos anteriormente, em que cada
para i de indice ate preenchidos-1 faca variável integrante possui um ponteiro apenas para o próximo
lista[i] <- lista[i+1] elemento.
fimpara Mas podemos incluir um ponteiro para o elemento
preenchidos <- preenchidos-1 anterior nesses registros, permitindo a interação reversa na
senao lista. Nesse caso, chama-se de lista duplamente encadeada.
9 11
A segunda forma é a inserção no final da lista. Para
fazermos isso, vamos supor que tenhamos um nó para
o último elemento da lista, chamado de fim. Dito isso,
para que vocês insiram um nó no fim da lista, devemos
Figura 2 - Lista Duplamente Encadeada. Fonte: Disponível em: <http://
obedecer aos seguintes passos:
tutorialestrutura.blogspot.com.br/2010/05/listas-duplamente-encadeadas.html>. 1. Aloca-se a memória para o ponteiro com o novo
Acesso em: 02 ago. 2018.
nó (NovoNo).
Além disso, podemos fazer com que o último elemento 2. Coloca-se a informação a ser armazenada.
da lista aponte para o primeiro elemento (e não para ninguém - 3. O campo prox recebe o valor NULO, pois é o
NULO, como ocorre em uma lista simplesmente encadeada). último elemento da lista.
Nesse caso, denominamos de lista circular. 4. O ponteiro prox do que era o último elemento da
lista recebe o endereço do novo nó.
5. Em seguida, o ponteiro fim também recebe o
valor do novo nó.
1ovo Registro
Figura 3 - Lista Circular. Fonte: Disponível em: <http://albertocn.sytes.net/2010-2/
HGDXODVȴJXUDVOVWBFLUFSQJ!$FHVVRHPDJR
Agora, relembramos o que consiste as listas encadeadas, Inicio Fim Fim
vamos relembrar os principais algoritmos que usamos para )LJXUDΖQVHU©¥RQRȴPGDOLVWD)RQWH$FHUYR3HVVRDO
manipulá-las.
3.1 - Inserção de dados em uma Por fim, temos a inserção no meio da lista, que é um
lista encadeada pouco mais complicada. Para esse caso, precisamos saber
qual nó será colocado como referência para salvar esse
Antes de ver como funcionam os algoritmos de inserção, próximo elemento. Ao nó, o qual será posicionado o novo
vamos relembrar a estrutura básica de uma lista, que consiste elemento, chamaremos de EndIns (endereço de inserção).
em um ponteiro chamado início que serve para apontar para Com o endereço do elemento em mãos, podemos fazer a
o primeiro elemento da lista; e um ponteiro fim que serve inserção.
para apontar para o último elemento da lista. Como não Os seguintes passos são executados:
temos nenhum elemento, podemos dar o valor nulo a esses 1. Aloca-se a memória para o ponteiro com o novo
ponteiros, por enquanto: nó (NovoNo).
var início, fim : *TLista 2. Coloca-se a informação a ser armazenada.
(...) 3. O campo prox recebe o valor de prox do elemento
inicio <- NULO posterior de EndIns.
fim <- NULO 4. O ponteiro prox de EndIns recebe o endereço do
novo nó.
Em seguida, estamos aptos a inserir elementos na lista.
Nesta aula, vamos relembrar apenas os passos lógicos que são
empregados. Vocês viram que a inserção pode ser feita de três
formas. A primeira delas é a inserção no início da lista, que Endereço de
Inicio Fim
consiste nos seguintes passos: Inserção
Aloca-se a memória para o ponteiro com o novo nó
1ovo Registro
(NovoNo).
1. Coloca-se a informação a ser armazenada.
2. O campo prox recebe o endereço apontado pela Inicio Endereço de
Inserção Fim
variável início, passando a apontar para o que era
Figura 5 - Inserção no meio da lista. Fonte: Acervo Pessoal.
o primeiro elemento da lista - ou nulo, caso seja o
primeiro elemento. Agora que vocês relembraram os códigos de inserção,
3. O ponteiro início recebe o endereço do novo nó, vamos relembrar os códigos de remoção de elementos na
passando a apontar para o nó recém-criado. lista.
4. Se for o primeiro nó, o ponteiro fim também recebe
o valor do novo nó. 3.2 - Removendo elementos na
lista encadeada
1ovo Registro Da mesma forma que a inserção, podemos remover
os dados de três formas diferentes: remover um dado no
início da lista; remover um dado no fim da lista e; remover
Inicio Inicio Fim
um dado no meio da lista.
Figura 4 - Inserção no início da lista. Fonte: Acervo Pessoal.
Os passos que apresentaremos a seguir se aplicam
12 Estrutura de Dados II 10
se vocês souber o endereço do dado a ser removido Caros(as), assim, fechamos a nossa recapitulação
diretamente. Nos algoritmos, o endereço a ser removido sobre listas. Nas próximas aulas, vocês serão apresentados
será armazenado em NoDel. Mas nada impede que vocês a novas estruturas de dados, que são mais poderosas.
utilizem a busca para procurar o nó a ser removido. Se Então, aproveitem esse momento para fazer os exercícios
usarmos essas instruções de maneira pura, a complexidade e tirar as suas dúvidas.
de tempo será O(1), pois os processos que mostraremos
aqui envolvem apenas atribuições.
Lembrem-se: toda memória que não se usa mais Retomando a aula
deveremos desalocar o seu espaço de memória, para que o
computador possa alocar para outra variável.
Dito isso, iremos apresentar os passos lógicos
da remoção. Primeiramente, vamos ver os passos para
remover um dado no início da lista: Chegamos, assim, ao ȴnal da primeira aula, que
1. Verifique se o nó a ser removido é o único da lista. recapitulou as listas. Mas relembrar nunca é demais,
1.1 Se for, atribua aos ponteiros início e fim o certo? Vamos lá.
valor
NULO. 1 - Relembrando o que são listas
1.2 Se não for, coloque o ponteiro início para
apontar Nesta seção, vimos que lista pode ser definida como
para o elemento apontado pelo nó a ser removido. um conjunto de dados, dispostas de maneira linear, ou
2. Desaloque o nó. seja, um dado é posto na sequência do anterior. A sua
disposição de dados é como se fosse um vetor, mas
com operações bem definidas sobre os dados que são
armazenados nesse conjunto. Geralmente, usamos listas
para armazenar registros, que podem ser identificados
Inicio Inicio
através de um campo denominado chave. Relembramos
Fim
Figura 7 - Remoção no início da lista. Fonte: Acervo Pessoal.
também que existem vários tipos de listas, com várias
peculiaridades, como: ordenação, regras de inserção e
Podemos também remover um dado localizado no remoção etc.
fim da lista. Para isso, devemos seguir esses passos:
1. O ponteiro fim passa a receber o endereço de 2 - Listas lineares e as suas operações básicas
memória do elemento anterior ao que será
Nesta aula, vimos que lista linear é uma lista
removido.
programada sobre um vetor linear, com um tamanho
2. O campo prox do elemento anterior passará a
apontar para NULO, quebrando a ligação para o prédefinido no momento da programação do sistema.
nó a ser removido. Nesta lista, podemos programar diversas operações,
3. Desaloque o nó. como a busca, a inserção e a remoção de elementos.
Apesar de ser simples de programar, uma lista linear tem
desvantagens, como, a limitação do número máximo
de dados a ser salvos, e para as operações de inserção e
remoção dependendo do algoritmo que utilizamos, pode
ser que seja necessário movimentar dados, o que pode
Inicio Fim Fim prejudicar o desempenho do programa.
)LJXUD5HPR©¥RQRȴPGDOLVWD)RQWH$FHUYR3HVVRDO
3 - Listas encadeadas
Além disso, podemos remover um elemento no meio
da lista seguindo os seguintes passos: Nesta seção, vimos que, ao contrário das listas lineares
1. O campo prox do nó anterior recebe o valor onde um elemento é armazenado de forma sequencial, as
de prox do elemento a ser removido. Com isso, listas encadeadas usam o recurso de ponteiros para criar
NoDel passa a ser desconectado da lista. uma lista de tamanho variável, onde cada elemento da lista
2. Desaloque o nó. é um registro com vários campos. Um deles é um ponteiro
para o mesmo tipo de registro, que aponta para o próximo
elemento. Essa característica é fundamental, pois permite
a ligação dos elementos da lista, mesmo que os elementos
estejam armazenados em posições diferentes de memória.
Inicio Fim As listas podem ser classificadas como encadeadas,
)LJXUD5HPR©¥RQRȴPGDOLVWD)RQWH$FHUYR3HVVRDO duplamente encadeadas ou listas circulares.
11 13
Vale a pena
Vale a pena ler
CORMEN, Thomas H.; et. al.. Algoritmos: teoria e
prática. Rio de Janeiro: Campus, 2012.
KNUTH, Donald Ervin. The art of computer programming.
3. ed. Amsterdam: Addison-Wesley, 1998.
MAFFEO, Bruno. Tipos Abstratos de Dados - Definição
e Exemplos. PUC, s.d. Disponível em: <http://www.inf.
puc-rio.br/~coordicc/icc/TAD.pdf>. Acesso em: 25 jul.
2018.
SZWARCFITER, Jayme Luiz; MARKENZON,
Lilian. Estruturas de dados e seus algoritmos. 2. ed. Rio de Janeiro:
LTC, 1994.
TENENBAUM, Aaron M.; AUGENSTEIN, Moshe
J.; LANGSAN, Yedidyah. et al. Estruturas de dados usando
C. São Paulo: Pearson Makron Books; São Paulo: Makron
Books do Brasil; São Paulo: McGraw-Hill, 2013.
ZIVIANI, Nivio. Projeto de algoritmos: com
implementação em PASCAL e C. 3. ed. São Paulo: Cengage
Learning, 2011.
Vale a pena acessar
FEOFILOFF, Paulo. Listas encadeadas. São Paulo:
USP, 2017. Disponível em: <https://www.ime.usp.br/~pf/
algoritmos/aulas/lista.html >. Acesso em: 30 jul. 2018.
SILVA, Fernando. Listas, Pilhas e Filas. DCC-FCUP,
s.d. Disponível em: <https://www.dcc.fc.up.pt/~fds/
aulas/EDados/1314/Apontamentos/listas-1x2.pdf>.
Acesso em: 02 ago. 2018.
SONG, Siang Wun. Listas Lineares. USP, 2008.
Disponível em: <https://www.ime.usp.br/~song/
mac5710/slides/02linear.pdf>. Acesso em: 30 jul. 2018.
Minhas anotações