Você está visualizando atualmente Lista encadeada com a linguagem C

Lista encadeada com a linguagem C

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.

Representação visual de uma lista encadeada.
Representação visual de uma lista encadeada.

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.

Deixe um comentário

19 − quinze =

Wagner Gaspar

Capixaba de São Gabriel da Palha, Espírito Santo. Bacharel em Ciência da Computação pela Universidade Federal do Amazonas e mestre em informática pela Universidade Federal do Espírito Santo.