- Introdução
- Como Baixar, Compilar e Executar Código de Repositório via Terminal
- Porque Utilizar TADS?
- Listas Encadeadas Simples
- Listas Encadeadas Duplas
- Listas Encadeadas Circulares Simples
- Listas Encadeadas Circulares Duplas
Uma lista encadeada é uma estrutura de dados que consiste em nós ligados em uma sequência, onde cada nó contém campos: um campo de dados para armazenar informações e para ponteiros.
Antes de prosseguir, certifique-se de que você tenha o seguinte instalado no seu sistema:
- Git: Para baixar o repositório.
- Compilador C: Como o GCC, para compilar o código.
- Terminal: Para executar comandos.
Abra seu terminal e execute o seguinte comando para clonar o repositório do GitHub:
git clone https://github.com/classroom-ufersa/LinkedList.git
Use o comando cd
para navegar para o diretório do repositório que acabou de ser clonado ou simplesmente abra com terminal integrado na pasta.
cd ListaEncadeadaSimples
cd src
Compile-o usando o compilador C. Por exemplo:
gcc -o main main.c
Agora, você pode executar o programa compilado usando o comando ./
no terminal:
./main
-
Reutilização de Código: Uma vez que as operações da lista estão encapsuladas no TAD Lista, elas podem ser reutilizadas em diferentes partes do código ou em projetos diferentes sem a necessidade de reimplementação. Isso economiza tempo e esforço.
-
Encapsulamento: O TAD Lista permite encapsular a implementação da lista. Isso significa que você pode mudar a implementação interna da lista sem afetar o código que a utiliza.
-
Ocultação de Complexidade: A implementação de estruturas de dados como listas encadeadas pode ser complexa, envolvendo alocação de memória, gerenciamento de ponteiros e manipulação de nós. O TAD Lista oculta essa complexidade, tornando mais fácil para outros desenvolvedores usar a lista sem se preocupar com os detalhes complicados.
-
Padronização: O TAD Lista estabelece um conjunto padrão de operações e nomes para trabalhar com listas. Isso torna o código mais consistente e fácil de entender para qualquer desenvolvedor.
-
Facilita a Depuração: Se ocorrer um erro em uma operação da lista, é mais fácil depurar(Achar o erro) quando você tem uma interface clara e bem definida para a lista. Você pode isolar o problema mais facilmente.
-
Documentação: O TAD Lista serve como documentação clara das operações suportadas pela lista. Isso ajuda a vocês (desenvolvedores) a entender como usar a lista corretamente.
-
Uma lista encadeada simples em C é uma estrutura de dados que consiste em nós ligados em uma sequência, onde cada nó contém dois campos: um campo de dados para armazenar informações e um ponteiro que aponta para o próximo nó na lista.
-
Chamos cada elemento da lista de nó, e um conjunto de nós é chamado de lista encadeada.
imagem tirada do site: SauloArisa
struct lista
{
int info;
struct lista *prox;
};
-
Info: Representa o dado armazenado na lista, que, no exemplo acima, é um valor inteiro. No entanto, pode ser utilizado para armazenar qualquer tipo de dado, inclusive estruturas (como a struct aluno, por exemplo).
-
Prox: Armazena o endereço do próximo elemento da lista. Em outras palavras, cada nó mantém um ponteiro para o próximo elemento usando este campo.
-
Alocação Dinâmica de Memória: Permite a alocação dinâmica de memória, facilitando a adição e remoção de elementos conforme necessário.
-
Inserção e Exclusão Eficientes: Inserir ou excluir elementos no início é eficiente, minimizando a necessidade de deslocamento de dados.
-
Redimensionamento Dinâmico: A lista pode crescer ou encolher conforme necessário, adequando-se a tamanhos variáveis de dados.
-
Acesso direto ineficiente: O acesso a um elemento específico requer percorrer a lista, o que é menos eficiente que arrays para acesso direto por índice.
-
Gasto de memória extra por elemento: Cada nó requer espaço extra para o ponteiro, aumentando o consumo de memória em comparação com arrays.
-
Complexidade de implementação: Implementar operações pode ser mais complexo devido à gestão de ponteiros e alocação dinâmica de memória.
- Descrição: Cria uma lista encadeada vazia.
Lista *lst_cria(void)
{
return NULL;
}
- Uso:
Lista *lista = lst_cria();
- Descrição: Insere um novo elemento no início da lista.
Lista *lst_insere(Lista *l, int v)
{
Lista *novo = (Lista *)malloc(sizeof(Lista));
if (novo == NULL)
{
printf("[ERRO] Memória insuficiente!");
exit(1);
}
novo->info = v;
novo->prox = l;
return novo;
}
- Uso:
lista = lst_insere(lista, 42);
- Descrição: Verifica se a lista está vazia.
int lst_vazia(Lista *l)
{
return (l == NULL);
}
- Uso:
if (lst_vazia(lista)) {
printf("A lista está vazia.\n");
}
- Descrição: Imprime os elementos da lista.
void lst_imprime(Lista *l)
{
Lista *p;
for (p = l; p != NULL; p = p->prox)
{
printf(" Info = %d \n", p->info);
}
}
- Uso:
lst_imprime(lista);
- Descrição: Busca por um elemento na lista.
Lista *lst_busca(int elemento, Lista *l)
{
Lista *p;
for (p = l; p != NULL; p = p->prox)
{
if (p->info == elemento)
return p;
}
return NULL;
}
- Uso:
Lista *resultado = lst_busca(42, lista);
- Descrição: Remove um elemento da lista.
Lista *lst_retira(Lista *l, int v)
{
Lista *ant = NULL;
Lista *p = l;
while (p->info != v)
{
if (p == NULL)
return l;
ant = p;
p = p->prox;
}
if (ant == NULL)
l = p->prox;
else
ant->prox = p->prox;
free(p);
return l;
}
- Uso:
lista = lst_retira(lista, 42);
- Descrição: Libera a memória ocupada pela lista.
void lst_libera(Lista *l)
{
Lista *p = l;
Lista *t;
while (p != NULL)
{
t = p->prox;
free(p);
p = t;
}
}
- Uso:
lst_libera(lista);
- Descrição: Insere um elemento de forma ordenada na lista.
Lista *lst_insere_ordenada(Lista *l, int v)
{
Lista *novo;
Lista *ant = NULL;
Lista *p = l;
while (p != NULL && p->info < v)
{
ant = p;
p = p->prox;
}
novo = (Lista *)malloc(sizeof(Lista));
novo->info = v;
if (ant == NULL)
{
novo->prox = l;
l = novo;
}
else
{
novo->prox = ant->prox;
ant->prox = novo;
}
return l;
}
- Uso:
lista = lst_insere_ordenada(lista, 42);
- Descrição: Lê valores de um arquivo e insere na lista.
Lista *lst_ler_arquivo(char *nome_arquivo)
{
FILE *arquivo;
int valor;
Lista *l = lst_cria();
arquivo = fopen(nome_arquivo, "r");
if (arquivo == NULL)
{
printf("Erro ao abrir o arquivo!\n");
exit(1);
}
while (fscanf(arquivo, "%d", &valor) != EOF)
{
l = lst_insere(l, valor);
}
fclose(arquivo);
return l;
}
- Uso:
lista = lst_ler_arquivo("dados.txt");
Uma lista encadeada dupla é uma estrutura de dados que expande a funcionalidade de uma lista encadeada simples, permitindo que cada nó mantenha referências tanto para o próximo quanto para o nó anterior na sequência. Isso significa que, além de apontar para o próximo nó, cada nó também aponta para o nó anterior, criando uma conexão bidirecional entre os elementos.
A principal diferença entre uma lista encadeada simples e uma lista encadeada dupla é que, em uma lista dupla, você pode percorrer a lista em ambas as direções: do início para o fim e do fim para o início.
As listas encadeadas duplas são uma escolha valiosa quando a flexibilidade e a capacidade de travessia bidirecional são necessárias, mas é importante considerar cuidadosamente as vantagens e desvantagens ao escolher essa estrutura de dados para um determinado problema.
imagem tirada do site: SauloArisa
struct lista_dupla
{
int info;
struct lista_dupla *prox;
struct lista_dupla *ant;
};
-
Info: Representa o dado armazenado na lista dupla, que pode ser qualquer tipo de dado, incluindo estruturas ou tipos personalizados.
-
Prox: Armazena o endereço do próximo elemento na lista dupla, permitindo percorrer a lista da esquerda para a direita.
-
Ant: Armazena o endereço do elemento anterior na lista dupla, permitindo percorrer a lista da direita para a esquerda.
-
Travessia Bidirecional: Uma das principais vantagens das listas encadeadas duplas é a capacidade de percorrer a lista em ambas as direções. Isso permite acesso eficiente tanto do início para o fim quanto do fim para o início, o que pode ser útil em muitos cenários, como a necessidade de encontrar elementos próximos ao final da lista.
-
Inserção e Exclusão Eficiente em Qualquer Posição: As listas encadeadas duplas permitem a inserção e exclusão eficiente de elementos em qualquer posição da lista. Isso ocorre porque cada nó mantém referências para os nós anterior e posterior, facilitando a atualização dos ponteiros durante a inserção ou exclusão.
-
Flexibilidade: As listas duplas são altamente flexíveis e podem ser usadas em uma variedade de cenários. Elas são especialmente úteis quando a ordem dos elementos é importante e quando operações de inserção e exclusão frequentes são necessárias.
-
Consumo de Memória: Em comparação com algumas outras estruturas de dados, as listas encadeadas duplas podem consumir mais memória devido à necessidade de manter dois ponteiros (um para o próximo nó e outro para o nó anterior) em cada nó da lista. Isso pode ser uma desvantagem em sistemas com restrições de memória.
-
Complexidade de Implementação: Implementar e manter listas encadeadas duplas pode ser mais complexo do que listas encadeadas simples ou arrays. Isso ocorre porque é necessário gerenciar os ponteiros de forma mais cuidadosa, garantindo que eles sejam atualizados corretamente durante as operações de inserção e exclusão.
-
Acesso Direto Ineficiente: Assim como nas listas encadeadas simples, o acesso direto a um elemento específico em uma lista encadeada dupla requer percorrer a lista, o que pode ser menos eficiente do que o acesso direto por índice em arrays.
- Descrição: Cria uma lista encadeada dupla vazia.
Listad *listd_cria(void)
{
return NULL;
}
- uso:
Listad *lista = listd_cria();
- Descrição: Libera a memória ocupada pela lista encadeada dupla.
void listd_libera(Listad *l)
{
Listad *p = l;
while (p != NULL)
{
Listad *t = p->prox;
free(p);
p = t;
}
}
- uso:
listd_libera(lista);
- Descrição: Adiciona um novo valor na lista encadeada dupla.
Listad *listd_adc(Listad *l, int v)
{
Listad *novo = (Listad *)malloc(sizeof(Listad));
if (novo == NULL)
{
printf("[ERRO] Memória insuficiente!");
exit(1);
}
novo->dado = v;
novo->prox = NULL;
novo->ant = NULL;
if (l == NULL)
{
return novo;
}
Listad *ultimo = l;
while (ultimo->prox != NULL)
{
ultimo = ultimo->prox;
}
ultimo->prox = novo;
novo->ant = ultimo;
return l;
}
- uso:
lista = listd_adc(lista, 42);
- Descrição: Busca por um item na lista encadeada dupla.
Listad *listd_busca(Listad *l, int v)
{
Listad *p;
for (p = l; p != NULL; p = p->prox)
{
if (p->dado == v)
{
return p;
}
}
return NULL; // não achou
}
- uso:
Listad *resultado = listd_busca(42, lista);
- Descrição: Remove um nó da lista encadeada dupla.
Listad *listd_retira(Listad *l, int v)
{
Listad *p = listd_busca(l, v);
if (p == NULL)
{
return l; // não achou
}
// Remove o elemento
if (l == p) // Testa se é o primeiro elemento
{
l = p->prox;
if (p->prox != NULL)
{
p->prox->ant = NULL;
}
}
else
{
p->ant->prox = p->prox; // Remove o do meio
if (p->prox != NULL)
{
p->prox->ant = p->ant;
}
}
free(p);
return l;
}
- uso:
lista = listd_retira(lista, 42);
- Descrição: Verifica se a lista encadeada dupla está vazia.
int listd_vazia(Listad *l)
{
if (l == NULL)
return 1;
else
return 0;
}
- uso:
if (listd_vazia(lista))
{
printf("A lista está vazia.\n");
}
- Descrição: Imprime o valor de todos os elementos da lista encadeada dupla.
void listd_imprime(Listad *l)
{
Listad *p;
for (p = l; p != NULL; p = p->prox)
{
printf("Dado = %d\n", p->dado);
}
}
- uso:
listd_imprime(lista);
Uma lista encadeada circular simples é uma estrutura de dados na qual cada nó da lista aponta para o próximo nó, formando um ciclo no final da lista, onde o último nó aponta de volta para o primeiro nó. Isso cria uma sequência contínua de elementos que podem ser percorridos indefinidamente.
A estrutura de um nó em uma lista encadeada circular simples é igual à de uma lista encadeada simples. Cada nó contém um campo de dado e um ponteiro para o próximo nó na lista.
imagem tirada do site: SauloArisa
struct lista
{
int dado;
struct lista *prox;
};
-
Info: Representa o dado armazenado na lista, que, no exemplo acima, é um valor inteiro. No entanto, pode ser utilizado para armazenar qualquer tipo de dado, inclusive estruturas.
-
Prox: Armazena o endereço do próximo elemento da lista. Em outras palavras, cada nó mantém um ponteiro para o próximo elemento usando este campo.
-
As mesmas vantagens da lista simples: Sim ela tem as mesmas vantagens da lista simples e algumas extras como a descrita a seguir.
-
Loop Infinito: A principal vantagem de uma lista encadeada circular simples é a capacidade de criar um loop infinito de elementos. Isso pode ser útil em algoritmos que exigem que a lista seja percorrida continuamente, sem um ponto final.
-
As mesmas desvantagens da lista simples: Sim ela também tem as mesmas desvantagens da lista simples e algumas extras como as descritas a seguir.
-
Dificuldade na Detecção do Fim: Na lista simples, basta verificar se está apontando para NULL. Na lista circular, uma das soluções para detectar o fim é, antes de começar a percorrer, guardar o nó raiz em um ponteiro auxiliar. A cada passo que você der na lista, compare se aquele nó é igual ao nó raiz. Caso seja igual, significa que você já percorreu toda a lista.
-
Tratamento de casos especiais: A manipulação de casos especiais, como a remoção do último elemento da lista, pode ser mais complexa em uma lista circular do que em uma lista encadeada comum. Você precisa lidar com essas situações de forma cuidadosa para evitar erros.
- Descrição: Inicia uma lista encadeada circular simples vazia.
Lista *listc_cria(void)
{
return NULL;
}
- uso:
Lista *lista = listc_cria();
- Descrição: Libera a memória ocupada pelos nós da lista encadeada circular simples.
void listc_libera(Lista *l)
{
Lista *p = l;
if (p != NULL)
{
do
{
Lista *t = p->prox;
free(p);
p = t;
} while (p != l);
}
}
- uso:
listc_libera(lista);
- Descrição: Insere um novo valor no início da lista encadeada circular simples.
Lista *listc_adc(Lista *l, int i)
{
Lista *novo = (Lista *)malloc(sizeof(Lista));
if (novo == NULL)
{
printf("[ERRO] Memória insuficiente!");
exit(1);
}
novo->dado = i;
if (l == NULL) // Se a lista estiver vazia
{
novo->prox = novo; // O próximo aponta para si mesmo
return novo; // Retorna a nova célula como primeira e última
}
novo->prox = l->prox; // O próximo aponta para a primeira célula
l->prox = novo; // Atualiza o próximo da última célula para apontar para a nova célula
return l; // Retorna a primeira célula da lista
}
- uso:
lista = listc_adc(lista, 42);
- Descrição: Busca por um elemento na lista encadeada circular simples.
Lista *listc_busca(Lista *l, int v)
{
Lista *p = l;
while (p != NULL && p->prox != l && p->dado != v)
{
p = p->prox;
if (p->dado == v)
return p;
}
return NULL;
}
- uso:
Lista *resultado = listc_busca(42, lista);
- Descrição: Retira um elemento da lista encadeada circular simples.
Lista *listc_retira(Lista *l, int v)
{
Lista *ant = NULL;
Lista *p = l;
while (p != NULL && p->dado != v)
{
ant = p;
p = p->prox;
}
if (p == NULL)
{
return l; // Não achou
}
// Retira o elemento
ant->prox = p->prox;
free(p);
return l;
}
- uso:
lista = listc_retira(lista, 42);
- Descrição: Verifica se a lista encadeada circular simples está vazia.
int listc_vazia(Lista *l)
{
if (l == NULL)
return 1;
else
return 0;
}
- uso:
if (listc_vazia(lista))
{
printf("A lista está vazia.\n");
}
- Descrição: Imprime a informação de todos os elementos da lista encadeada circular simples.
void listc_imprime(Lista *l)
{
Lista *p = l;
do
{
printf("Dado = %d\n", p->dado);
p = p->prox;
} while (p != l);
}
- uso:
listc_imprime(lista);
Uma lista encadeada circular dupla é uma estrutura de dados linear que combina as características das listas duplamente encadeadas e das listas circulares simples. Ela consiste em uma sequência de elementos, chamados de nós, onde cada nó armazena um valor (ou dado) e possui ponteiros tanto para o nó anterior quanto para o próximo nó na lista. A característica distintiva dessa estrutura é que o último nó na lista aponta para o primeiro nó, criando um ciclo, enquanto o primeiro nó também aponta para o último.
struct listad
{
int dado;
struct listad *prox;
struct listad *ant;
};
- Dado: Armazena o valor ou dado representado pelo nó.
- Prox: Um ponteiro que aponta para o próximo nó na lista.
- Ant: Um ponteiro que aponta para o nó anterior na lista.
A estrutura da lista em si consiste em um ponteiro para o primeiro nó (cabeça) e, às vezes, um ponteiro para o último nó (cauda), dependendo da implementação.
-
As mesmas vantagens de uma lista dupla: Porém com algumas extras como a descrita a seguir.
-
Inserção e remoção eficientes: As listas encadeadas circulares duplas permitem inserções e remoções rápidas no início, no final e em qualquer posição da lista, pois aproveitam os ponteiros de nó anterior e próximo.
-
As mesmas desvantagens de uma lista dupla: Porém com algumas extras como as descritas a seguir.
-
Manutenção do ciclo: Garantir que a lista permaneça circular exige controle rigoroso dos ponteiros de início e fim. Quando você insere ou remove elementos, é necessário garantir que os ponteiros sejam ajustados corretamente para manter a circularidade.
-
Tratamento de casos especiais: A manipulação de casos especiais, como a remoção do último elemento da lista, pode ser mais complexa em uma lista circular dupla do que em uma lista duplamente encadeada comum. Você precisa lidar com essas situações de forma cuidadosa para evitar erros.
- Descrição: Inicia uma lista encadeada circular dupla vazia.
Listad *listdc_cria(void)
{
return NULL;
}
- uso:
listdc_libera(lista);
- Descrição: Libera a memória ocupada pelos nós da lista encadeada circular dupla.
void listdc_libera(Listad *l)
{
Listad *p = l;
if (p != NULL)
{
do
{
Listad *t = p->prox;
free(p);
p = t;
} while (p != l);
}
}
- uso:
listdc_libera(lista);
- Descrição: Insere um novo valor no início da lista encadeada circular dupla.
Listad *listdc_adc(Listad *l, int v)
{
Listad *novo = (Listad *)malloc(sizeof(Listad));
if (novo == NULL)
{
printf("[ERRO] Memória insuficiente!");
exit(1);
}
novo->dado = v;
if (l == NULL) // Se a lista estiver vazia
{
novo->prox = novo; // O próximo aponta para si mesmo
novo->ant = novo; // O anterior aponta para si mesmo
return novo; // Retorna a nova célula como primeira e última
}
novo->prox = l; // O próximo aponta para a primeira célula
novo->ant = l->ant; // O anterior aponta para a última célula
l->ant->prox = novo; // Atualiza o próximo da última célula para apontar para a nova célula
l->ant = novo; // Atualiza o anterior da primeira célula para apontar para a nova célula
return l; // Retorna a primeira célula da lista
}
- uso:
lista = listdc_adc(lista, 7);
- Descrição: Busca por um elemento na lista encadeada circular dupla.
Listad *listdc_busca(Listad *l, int v)
{
Listad *p = l;
while (p != NULL && p->prox != l && p->dado != v)
{
p = p->prox;
if (p->dado == v)
return p;
}
return NULL;
}
- uso:
Listad *resultado = listdc_busca(7, lista);
- Descrição: Retira um elemento da lista encadeada circular dupla.
Listad *listdc_retira(Listad *l, int v)
{
Listad *p = listdc_busca(l, v);
if (p == NULL)
{
return l; // Não achou
}
// Retira o elemento
if (l == p)
{ // Testa se é o primeiro elemento
if (l->prox == l) // Se for o único elemento na lista
{
free(l);
return NULL; // Retorna lista vazia
}
else
{
l = p->prox; // Atualiza o início da lista
}
}
p->ant->prox = p->prox; // Retira o do meio ou do final
p->prox->ant = p->ant;
if (l == p) // Se o último elemento está sendo retirado
{
l = NULL; // Define a lista como vazia
}
free(p);
return l;
}
- uso:
lista = listdc_retira(lista, 7);
- Descrição: Verifica se a lista encadeada circular dupla está vazia.
int listdc_vazia(Listad *l)
{
if (l == NULL)
return 1;
else
return 0;
}
- uso:
if (listdc_vazia(lista))
{
printf("A lista está vazia.\n");
}
- Descrição: Imprime a informação de todos os elementos da lista encadeada circular dupla.
void listdc_imprime(Listad *l)
{
if (l == NULL)
{
printf("Lista vazia.\n");
return;
}
Listad *p = l;
do
{
printf("Dado = %d\n", p->dado);
p = p->prox;
} while (p != l);
}
- uso:
listdc_imprime(lista);