Implementação de Operações Avançadas em Listas Encadeadas Simples com Linguagem C

A lista encadeada simples é uma estrutura de dados fundamental que permite o armazenamento dinâmico de informações. Diferente dos arrays, os elementos não precisam estar em posições contíguas na memória. Abaixo, exploramos a implementação técnica de operações essenciais, desde a manipulação básica até a inserção e remoção em posições arbitrárias.

Estrutura Básica e Exibição

Primeiramente, definimos a estrutura do nó e uma função para percorrer e imprimir os dados contidos na lista.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int DataType;

typedef struct Node {
    DataType data;
    struct Node* next;
} Node;

void printLinkedList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

Inserção e Remoção nas Extremidades

Para modificar a cabeça da lista dentro de uma função, passamos o endereço do ponteiro (ponteiro duplo). Isso permite que a alteração persista fora do escopo da função.

// Inserção no final (Tail)
void pushBack(Node** headRef, DataType val) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = val;
    newNode->next = NULL;

    if (*headRef == NULL) {
        *headRef = newNode;
    } else {
        Node* temp = *headRef;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
    }
}

// Inserção no início (Head)
void pushFront(Node** headRef, DataType val) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = val;
    newNode->next = *headRef;
    *headRef = newNode;
}

// Remoção no início
void popFront(Node** headRef) {
    assert(*headRef);
    Node* oldHead = *headRef;
    *headRef = (*headRef)->next;
    free(oldHead);
}

// Remoção no final
void popBack(Node** headRef) {
    assert(*headRef);
    if ((*headRef)->next == NULL) {
        free(*headRef);
        *headRef = NULL;
    } else {
        Node* prev = NULL;
        Node* tail = *headRef;
        while (tail->next != NULL) {
            prev = tail;
            tail = tail->next;
        }
        free(tail);
        prev->next = NULL;
    }
}

Busca e Manipulação por Posição

Operações em locais específicos exigem a localização prévia de um nó ou o gerenciamento de ponteiros adjacentes.

// Localizar um nó pelo valor
Node* findNode(Node* head, DataType target) {
    Node* curr = head;
    while (curr) {
        if (curr->data == target) return curr;
        curr = curr->next;
    }
    return NULL;
}

// Criar um novo nó isolado
Node* createNewNode(DataType val) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) return NULL;
    newNode->data = val;
    newNode->next = NULL;
    return newNode;
}

// Inserir ANTES de um nó específico
void insertBefore(Node** headRef, Node* pos, DataType val) {
    assert(pos);
    if (pos == *headRef) {
        pushFront(headRef, val);
    } else {
        Node* prev = *headRef;
        while (prev->next != pos) {
            prev = prev->next;
        }
        Node* newNode = createNewNode(val);
        newNode->next = pos;
        prev->next = newNode;
    }
}

// Inserir DEPOIS de um nó específico
void insertAfter(Node* pos, DataType val) {
    assert(pos);
    Node* newNode = createNewNode(val);
    newNode->next = pos->next;
    pos->next = newNode;
}

// Remover um nó específico
void eraseNode(Node** headRef, Node* pos) {
    assert(pos && *headRef);
    if (pos == *headRef) {
        popFront(headRef);
    } else {
        Node* prev = *headRef;
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = pos->next;
        free(pos);
    }
}

// Remover o nó subsequente
void eraseNextNode(Node* pos) {
    assert(pos && pos->next);
    Node* target = pos->next;
    pos->next = target->next;
    free(target);
}

Exemplo de Execução Completa

Abaixo, um fluxo de teste demonstrando a integração de todas as funções implementadas.

int main() {
    Node* myList = NULL;

    // Construindo a lista: 10, 20, 30
    pushBack(&myList, 10);
    pushBack(&myList, 20);
    pushBack(&myList, 30);
    
    // Adicionando 5 na frente: 5, 10, 20, 30
    pushFront(&myList, 5);
    
    // Buscando o nó com valor 20 e inserindo 25 após ele
    Node* node20 = findNode(myList, 20);
    if (node20) {
        insertAfter(node20, 25);
    }
    
    // Lista resultante: 5 -> 10 -> 20 -> 25 -> 30
    printLinkedList(myList);
    
    // Removendo o primeiro e o último
    popFront(&myList);
    popBack(&myList);
    
    // Lista final: 10 -> 20 -> 25
    printLinkedList(myList);

    // Limpeza de memória (opcional para este exemplo simples)
    while (myList) {
        popFront(&myList);
    }

    return 0;
}

O gerenciamento manual de memória via malloc e free é crucial. Ao manipular listas ligadas, o cuidado com ponteiros nulos e a ordem das atribuições evita vazamentos de memória e erros de segmentação.

Tags: C DataStructures pointers MemoryManagement algorithms

Publicado em 7-4 01:42