Dando continuidade ao nosso Curso de Programação C e ao estudo da estrutura de dados dinâmica do tipo lista encadeada, vamos aprender nesta aula como construir uma lista duplamente encadeada.
As estruturas dinâmicas que vimos até o momento possuía apenas um ponteiro conectando cada nó, isso não significa que tem que ser sempre assim. Se o problema exigir podem ser criados outros ponteiros. Isso nos leva a outra estrutura de dados dinâmica clássica, a Lista Duplamente Encadeada.
Como o nome sugere, é uma lista encadeada como a que conhecemos nas aulas anteriores. A diferença reside no fato de que cada nó possui dois ponteiros, um apontando para o próximo nó e outro apontando para o nó anterior.
O fato de cada nó possuir dois ponteiros possui ao menos duas implicações importantes que exigem nossa atenção. O primeiro é que agora podemos percorrer a lista nos dois sentidos, não apenas do início para o fim, mas também do fim para o início. O segundo é que em cada operação realizada na lista precisamos atualizar dois ponteiros, o ponteiro que aponta para o próximo nó e o ponteiro que aponta para o nó anterior.
O nó representado na figura acima pode ser criado da seguinte forma:
/* Estrutura nó para a lista duplamente encadeada */ typedef struct no{ int valor; struct no *proximo; struct no *anterior; }No;
Como mencionado, a partir de agora cada alteração na lista precisa atualizar os dois ponteiros de cada nó envolvido na operação. A seguir é apresentado um procedimento para inserir um novo nó no início da lista dupla. Observe que os dois ponteiros são atualizados de acordo com a operação realizada, neste caso uma inserção no início.
/* procedimento para inserir um novo nó no início da lista */ void inserir_no_inicio(No **lista, int num){ No *novo = malloc(sizeof(No)); if(novo){ novo->valor = num; // próximo do novo nó aponta para o início da lista novo->proximo = *lista; // o anterior é nulo pois é o primeiro nó novo->anterior = NULL; // se a lista não estiver vazia, o anterior do primeiro nó aponta para o novo nó if(*lista) (*lista)->anterior = novo; // o novo nó passa a ser o início da lista (o primeiro nó da lista) *lista = novo; } else printf("Erro ao alocar memoria!\n"); }
A seguir temos um código completo de exemplo para manipular uma lista duplamente encadeada com as seguintes operações:
– inserir no início
– inserir no meio
– inserir no fim
– inserir ordenado
– remover
– buscar
– imprimir do início ao fim
– imprimir do fim ao início
Uma dica para ajudar na compreensão de cada uma destas operações é desenhar uma lista no papel e simular as operações. Por exemplo, se for inserir de forma ordenada, desenhe uma lista com alguns elementos e um novo nó a ser inserido de forma ordenada. Agora rastreie o procedimento de inserção ordenada, fazendo no papel o que cada linha de código faz. Isso ajuda a entender a manipulação dos ponteiros.
Código de exemplo completo em C para uma Lista Duplamente Encadeada
/* Aula 257: Lista Duplamente Encadeada Código escrito por Wagner Gaspar Agosto de 2021 */ typedef struct no{ int valor; struct no *proximo; struct no *anterior; }No; // procedimento para inserir no início void inserir_no_inicio(No **lista, int num){ No *novo = malloc(sizeof(No)); if(novo){ novo->valor = num; novo->proximo = *lista; novo->anterior = NULL; if(*lista) (*lista)->anterior = novo; *lista = novo; } else printf("Erro ao alocar memoria!\n"); } // procedimento para inserir no fim void inserir_no_fim(No **lista, int num){ No *aux, *novo = malloc(sizeof(No)); if(novo){ novo->valor = num; novo->proximo = NULL; // é o primeiro? if(*lista == NULL){ *lista = novo; novo->anterior = NULL; } else{ aux = *lista; while(aux->proximo) aux = aux->proximo; aux->proximo = novo; novo->anterior = aux; } } else printf("Erro ao alocar memoria!\n"); } // procedimento para inserir no meio void inserir_no_meio(No **lista, int num, int ant){ No *aux, *novo = malloc(sizeof(No)); if(novo){ novo->valor = num; // é o primeiro? if(*lista == NULL){ novo->proximo = NULL; novo->anterior = NULL; *lista = novo; } else{ aux = *lista; while(aux->valor != ant && aux->proximo) aux = aux->proximo; novo->proximo = aux->proximo; if(aux->proximo) aux->proximo->anterior = novo; novo->anterior = aux; aux->proximo = novo; } } else printf("Erro ao alocar memoria!\n"); } void inserir_ordenado(No **lista, int num){ No *aux, *novo = malloc(sizeof(No)); if(novo){ novo->valor = num; // a lista está vazia? if(*lista == NULL){ novo->proximo = NULL; novo->anterior = NULL; *lista = novo; } // é o menor? else if(novo->valor < (*lista)->valor){ novo->proximo = *lista; (*lista)->anterior = novo; *lista = novo; } else{ aux = *lista; while(aux->proximo && novo->valor > aux->proximo->valor) aux = aux->proximo; novo->proximo = aux->proximo; if(aux->proximo) aux->proximo->anterior = novo; novo->anterior = aux; aux->proximo = novo; } } else printf("Erro ao alocar memoria!\n"); } No* remover(No **lista, int num){ No *aux, *remover = NULL; if(*lista){ if((*lista)->valor == num){ remover = *lista; *lista = remover->proximo; if(*lista) (*lista)->anterior = NULL; } else{ aux = *lista; while(aux->proximo && aux->proximo->valor != num) aux = aux->proximo; if(aux->proximo){ remover = aux->proximo; aux->proximo = remover->proximo; if(aux->proximo) aux->proximo->anterior = aux; } } } return remover; } No* buscar(No **lista, int num){ No *aux, *no = NULL; aux = *lista; while(aux && aux->valor != num) aux = aux->proximo; if(aux) no = aux; return no; } void imprimir(No *no){ printf("\n\tLista: "); while(no){ printf("%d ", no->valor); no = no->proximo; } printf("\n\n"); } // retorna ponteiro para o último nó da lista No* ultimo(No **lista){ No *aux = *lista; while(aux->proximo) aux = aux->proximo; return aux; } // imprime a lista do fim para o início // recebe um ponteiro para o último nó da lista void imprimirContrario(No *no){ printf("\n\tLista: "); while(no){ printf("%d ", no->valor); no = no->anterior; } printf("\n\n"); } int main(){ int opcao, valor, anterior; No *removido, *lista = NULL; do{ printf("\n\t0 - Sair\n\t1 - InserirI\n\t2 - inserirF\n\t3 - InserirM\n\t4 - InserirO\n\t5 - Remover\n\t6 - Imprimir\n\t7 - Buscar\n\t8 - ImprimirC\n"); scanf("%d", &opcao); switch(opcao){ case 1: printf("Digite um valor: "); scanf("%d", &valor); inserir_no_inicio(&lista, valor); break; case 2: printf("Digite um valor: "); scanf("%d", &valor); inserir_no_fim(&lista, valor); break; case 3: printf("Digite um valor e o valor de referencia: "); scanf("%d%d", &valor, &anterior); inserir_no_meio(&lista, valor, anterior); break; case 4: printf("Digite um valor: "); scanf("%d", &valor); inserir_ordenado(&lista, valor); break; case 5: printf("Digite um valor a ser removido: "); scanf("%d", &valor); removido = remover(&lista, valor); if(removido){ printf("Elemento a ser removido: %d\n", removido->valor); free(removido); } else printf("Elemento inexistente!\n"); break; case 6: imprimir(lista); break; case 7: printf("Digite um valor a ser buscado: "); scanf("%d", &valor); removido = buscar(&lista, valor); if(removido) printf("Elemento encontrado: %d\n", removido->valor); else printf("Elemento nao encontrado!\n"); break; case 8: imprimirContrario(ultimo(&lista)); break; default: if(opcao != 0) printf("Opcao invalida!\n"); } }while(opcao != 0); return 0; }
Suas aulas tem me ajudado muito mestre.