Aulas 01 e 02 - O tipo Lista
O tipo Array
que discutimos na apresentação inicial representa a maneira mais básica de se armazenar dados em linguagens de programação. Ele é, basicamente, uma região de memória em que colocamos um objeto após o outro, em sequência. Como o computador compartilha a memória com diversos programas, precisamos dizer com antecedência o quanto de memória usaremos. Por isso, o Array
tem tamanho fixo.
Gerenciar esse tamanho fixo do array dá trabalho e várias linguagens oferecem implementações do tipo List
, que permite inserir e remover elementos. Veja alguns exemplos disso abaixo.
list
de PythonArrayList
de Javastd::vector
de C++
Nesta aula iremos trabalhar os seguintes objetivos:
- definir quais operações o tipo
List
suporta - utilizar um Tipo de Dados mais simples (
Array
) para implementar outro tipo mais complexo (Lista
) - implementar uma Estrutura de Dados completa que implemente
List
Dica 1
- Um Tipo de Dados somente define quais operações existem e quais são suas entradas e saídas. Ou seja, aqui definimos o quê deve ser feito.
- Uma Estrutura de Dados (ED) descreve também algoritimos para cada uma das operações suportadas. Ou seja, aqui temos também como cada operação deve ser realizada.
Dessa maneira, podemos pensar na ED como uma descrição detalhada (em termos de algoritmos) de como uma ADT poderia ser implementada.
Important
Existem várias EDs diferentes que podem implementar a mesma ADT e uma mesma ED pode implementar várias ADTs diferentes.
Definição do tipo de dados List
O Tipo de dados List
é uma coleção de valores indexados por um inteiro não negativo (maior ou igual a zero) sem espaços vazios. Essa coleção pode aumentar ou diminuir de tamanho e suporta as seguintes operações:
NOVA_LISTA()
- cria nova lista vaziaTAMANHO(L)
- devolve o número de elementos da listaL[i]
- devolve o elemento de índicei
. Sei<0
oui>TAMANHO(A)
dá erroINSERE(L, V, i)
- insere o valorV
na listaL
na posiçãoi
. Os elementos a partir da posiçãoi
são "empurrados" para a direita. Essa operação incrementa o tamanho da lista.REMOVE(L, V)
- remove a primeira ocorrência deV
da lista. SeV
não estiver presente não faz nada. Os elementos a partir do índice da primeira ocorrência deV
são "puxados" para a esquerda. Essa operação decrementa o tamanho da lista.
A estrutura de dados Vetor dinâmico
O vetor dinâmico é uma ED que permite implementar as operações do tipo List
usando um Array
. Vamos iniciar relembrando os conceitos que acabamos de discutir.
Important
Como o vetor dinâmico implementa toda a ADT List
, nesse guia iremos usar a letra L
quando nos referirmos a um vetor dinâmico.
Exercício 1
Resposta
Nossa ED vetor dinâmico tem três atributos.
Array
A para guardar os dados- capacidade: número máximo de elementos que podem ser guardados A. Sempre igual a
TAMANHO(A)
- tamanho: número de elementos efetivamente guardados em A. Sempre igual a
TAMANHO(L)
.
As operações mais interessantes do vetor são INSERE
e REMOVE
. Vamos trabalhar nelas agora.
Important
Dado que usaremos um Array
para implementar List
, teremos sempre um tamanho máximo fixo para nossos dados. Nessas próximas seções vamos sempre supor que existem espaços vazios disponíveis para inserir mais dados.
Inserção
Vamos primeiro rever a descrição de INSERE
:
Definição
insere o valor V
na lista L
na posição i
. Os elementos a partir da posição i
(inclusive) são "empurrados" para a direita. Essa operação incrementa o tamanho da lista.
O primeiro passo do algoritmo é "empurrar" todos elementos para a direita. Vamos inicialmente supor que a lista L
tem espaço para colocar o elemento V
na posição i
. Ou seja, é verdade que TAMANHO(L) + 1 < capacidade = TAMANHO(A)
.
Exercício 2
Resposta
Teste seu algoritmo com as seguintes entradas. Estamos supondo que L
já tem um elemento vazio disponível no fim.
O algoritmo é bem simples: começamos no elemento N
e descemos até a posição i+1
copiando o valor do índice anterior. Note que atribuímos a L[N]
, o que significa que TAMANHO(L)=N+1
.
Com essa rotina podemos implementar facilmente a inserção.
Exercício 3
Note que a etapa de tradução do algoritmo acima para Java não é exatamente trivial e depende de um monte de outras definições. Trabalharemos isso no PrairieLearn deste módulo.
Remoção
O algoritmo de remoção é muito parecido com a inserção, mas agora faz o deslocamento à esquerda. Note que novamente não vamos mexer na capacidade
de A
, somente no tamanho
de L
.
Exercício 4
Resposta
Esse será feito na lousa. Aproveite para anotar o resultado.
Exercício 5
Resposta
REMOVE(L, V)
N = TAMANHO(L)
PARA i = 0 ATÉ N FAÇA
SE L[i] = V ENTÃO
DESLOCA_ESQUERDA(L, N, i)
Decrementa TAMANHO(L)
RETORNA
FIM
FIM
Esse algoritmo tem duas partes importantes:
- encontrar um índice
i
tal queL[i] = V
- usar o
DESLOCA_ESQUERDA
para sobrescrever o elemento na posiçãoj
e diminuir o tamanho da lista.
Redimensionamento
Vamos agora analizar a situação das variáveis capacidade
e tamanho
quando realizamos inserções e remoções.
Exercício 6
Resposta
Dado que a capacidade é o número máximo de elementos que podem ser guardados no Array
, a afirmação é falsa. O contrário é que é verdade: tamanho <= capacidade
.
Pense no tamanho como o número de elementos não vazios do Array
, enquanto a capacidade é TAMANHO(Array)
.
Exercício 7
Resposta
Como ainda tem espaço em nosso Array
, somente o tamanho aumenta.
Exercício 8
Resposta
Neste caso, como o Array
que usamos em nosso vetor está quase cheio (6/8 posições ocupadas) a capacidade se mantém. Assim como na inserção, a remoção sempre mexe no tamanho do vetor.
Agora que já relembramos os conceitos mais importantes, vamos modificar o algoritmo INSERE
para permitir que a lista cresça de tamanho. Nossa estratégia será executar o seguintes passos logo no início de INSERE
.
- Entrada:
Lista L
- Saída:
REDIMENSIONA_SE_PRECISAR(L)
A = Array com dados de L
N = TAMANHO(L)
C = TAMANHO(A)
SE C == N ENTÃO
A_NOVO = NOVO_ARRAY(2 * N)
PARA CADA i=0 ATÉ N FAÇA
A_NOVO[i] = A[i]
FIM
Array de dados de L = A_NOVO
FIM
Exercício 9
Resposta
O Array
está cheio, então precisamos aumentá-lo. Nosso algoritmo acima dobra a capacidade do array de L
. Logo, a nova capacidade de L
é 16 enquanto o tamanho de L
só aumenta 1 (que é o novo elemento inserido).
Exercício 10
Resposta
A checagem de lista cheia ocorre no início de INSERE
, mas o Array
de dados de Lista
só fica cheio ao fim de INSERE
. Logo, não há redimensionamento.
A mesma estratégia pode ser usada na remoção, desta vez para diminuir a capacidade pela metade se o tamanho for menor que ¼ da capacidade.
Prática
Agora que temos todos os detalhes da nossa implementação da ED Vetor Dinâmico basta traduzir isso para Java. Visite o PrairieLearn e implemente os algoritmos discutidos neste handout.