Na computação, constantemente se faz necessário armazenar uma grande quantidade de dados em estruturas a fim de realizar algum processamento. Em muitas situações, estruturas mais simples como vetor e matriz não são suficientes ou eficientes a ponto de serem uma boa escolha. Assim, precisamos lançar mão de estruturas mais avançadas.
Neste grupo, entram estruturas como pilhas, filas, listas, árvores, entre outras. Hoje apresentaremos como trabalhar com as listas encadeadas. Seu principal benefício é seu crescimento dinâmico, iniciando vazias e crescendo conforme novos elementos são inseridos.
Uma lista encadeada pode ser representada graficamente como na figura a seguir, uma sequencia de elementos onde cada um aponta para o elemento seguinte, até o final, que não aponta para ninguém.

A primeira coisa que precisamos fazer para trabalhar com uma lista encadeada é criar as estruturas necessárias, neste caso, duas estruturas, uma para a lista propriamente dita e outra para os nós da lista.
A estrutura nó é bem simples, possuindo um campo do tipo inteiro chamado valor (poderia ser qualquer tipo, inclusive outro tipo mais complexo) e um ponteiro para um próximo nó.
typedef struct No {
int valor;
struct No *proximo;
} No;
A estrutura lista possui um ponteiro para um nó chamado início, o primeiro nó de nossa lista, e um segundo campo inteiro para guardar o tamanho da lista. Aqui acrescentamos um segundo ponteiro para o fim da lista com a finalidade de facilitar uma inserção de novos elementos no final da lista. Ressaltamos que este segundo ponteiro é opcional e sua inclusão ou não vai depender da finalidade da lista que está sendo construída.
typedef struct {
No *inicio, *fim;
int tam;
} Lista;
Na sequência, vamos agora trabalhar na inserção de elementos em nossa lista encadeada. A seguir apresentamos um procedimento para inserir um novo elemento no início de nossa lista.
// inserção no início da lista
void inserirInicio(Lista *lista, int valor) {
No *novo = (No*)malloc(sizeof(No)); // cria um novo nó
novo->valor = valor;// (*novo).valor = valor
if(lista->inicio == NULL) { // a lista está vazia
novo->proximo = NULL;
lista->inicio = novo;
lista->fim = novo;
} else { // a lista não está vazia
novo->proximo = lista->inicio;
lista->inicio = novo;
}
lista->tam++;
}
Vamos agora escrever outro procedimento, desta vez para inserir um novo elemento no final da lista.
// inserir no final da lista
void inserirFim(Lista *lista, int valor) {
No *novo = (No*)malloc(sizeof(No)); // cria um novo nó
novo->valor = valor;
novo->proximo = NULL;
if(lista->inicio == NULL) { // lista vazia
lista->inicio = novo;
lista->fim = novo;
} else { // lista não vazia
lista->fim->proximo = novo;
lista->fim = novo;
}
lista->tam++;
}
Para saber se tudo isso está funcionando realmente, precisamos também imprimir nossa lista na tela.
// imprimir a lista
void imprimir(Lista *lista) {
No *inicio = lista->inicio;
printf("Tamanho da lista: %d\n", lista->tam);
while(inicio != NULL) {
printf("%d ", inicio->valor);
inicio = inicio->proximo;
}
printf("\n\n");
}
Veja na videoaula a seguir a construção de uma lista encadeada
Podendo inserir elementos no início e no fim da lista, vamos agora remover um determinado elemento.
// remover um elemento da lista
void remover(Lista *lista, int valor) {
No *inicio = lista->inicio; // ponteiro para o início da lista
No * noARemover = NULL; // ponteiro para o nó a ser removido
if(inicio != NULL && lista->inicio->valor == valor) { // remover 1º elemento
noARemover = lista->inicio;
lista->inicio = noARemover->proximo;
if(lista->inicio == NULL)
lista->fim = NULL;
} else { // remoção no meio ou no final
while(inicio != NULL && inicio->proximo != NULL && inicio->proximo->valor != valor) {
inicio = inicio->proximo;
}
if(inicio != NULL && inicio->proximo != NULL) {
noARemover = inicio->proximo;
inicio->proximo = noARemover->proximo;
if(inicio->proximo == NULL) // se o último elemento for removido
lista->fim = inicio;
}
}
if(noARemover) {
free(noARemover); // libera a memória do nó
lista->tam--; // decrementa o tamanho da lista
}
}
Veja na videoaula a seguir a remoção de um nó da lista
Uma dúvida postada em um dos vídeos queria sabe como dividir uma lista em duas, inserindo os elementos das posições pares em uma lista e os elementos das posições ímpares em outra lista. Para isso, implementamos uma função que remove e retorna o primeiro nó da lista:
// função que remove e retorna o primeiro nó
No* removerPrimeiroNO(Lista *lista) {
if(lista->inicio != NULL) {
No *no = lista->inicio;
lista->inicio = no->proximo;
lista->tam--;
if(lista->inicio == NULL)
lista->fim = NULL;
return no;
} else
return NULL;
}
E um procedimento para fazer a divisão propriamente dita da lista, alternando a inserção em duas listas, uma para os elementos de posição ímpar e outra para os elementos de posição par.
// procedimento que divide uma lista em duas
void dividirLista(Lista *lista, Lista *listaI, Lista *listaP) {
No *removido;
while(lista->inicio != NULL) {
removido = removerPrimeiroNO(lista);
inserirFim(listaI, removido->valor);
free(removido);
removido = removerPrimeiroNO(lista);
if(removido != NULL) {
inserirFim(listaP, removido->valor);
free(removido);
}
}
}
Na videoaula a seguir apresento como dividir uma lista encadeada em duas de acordo com a posição do elemento
Agora, vamos então criar um menu em nosso programa principal com opções de inserção, remoção, divisão e impressão da lista.
int main() {
Lista lista, listaI, listaP; // criação de 3 listas
int opcao, valor;
// inicialização das listas
lista.inicio = NULL;
lista.fim = NULL;
lista.tam = 0;
listaI.inicio = NULL;
listaI.fim = NULL;
listaI.tam = 0;
listaP.inicio = NULL;
listaP.fim = NULL;
listaP.tam = 0;
do { // menu de opções
printf("1 - Inserir no inicio\n2 - Imprimir\n3 - Inserir no fim\n4 - Remover\n6 - Dividir lista\n5 - Sair\n");
scanf("%d", &opcao);
switch(opcao) {
case 1:
printf("Digite um valor a ser inserido: ");
scanf("%d", &valor);
inserirInicio(&lista, valor);
break;
case 2:
printf("\nLista original:\n");
imprimir(&lista);
printf("\nLista impar:\n");
imprimir(&listaI);
printf("\nLista par:\n");
imprimir(&listaP);
break;
case 3:
printf("Digite um valor a ser inserido: ");
scanf("%d", &valor);
inserirFim(&lista, valor);
break;
case 4:
printf("Digite um valor a ser removido: ");
scanf("%d", &valor);
remover(&lista, valor);
break;
case 5:
printf("Finalizando...\n");
break;
case 6:
dividirLista(&lista, &listaI, &listaP);
break;
default:
printf("Opcao invalida!\n");
}
} while(opcao != 5);
return 0;
}
Se ficou alguma dúvida faça bom uso dos comentários abaixo, farei o possível para ajudar. Um grande abraço e até a próxima. Bons estudos.
