Estruturas de Dados: Implementações de Listas Lineares

Conceitos Fundamentais de Listas Lineares

Uma lista linear é uma estrutura de dados que organiza elementos em uma sequência ordenada, onde cada item posui um predecessor e um sucessor, com exceção do primeiro e último elementos. Esta estrutura pode ser implementada de duas maneiras principais: listas sequenciais (baseadas em arrays) e listas encadeadas (baseadas em ponteiros).

Lista Sequencial

A lista sequencial, também chamada de lista estática, armazena elementos em posições de memória contíguas. Essa característica permite acesso aleatório em tempo constante O(1), dado o endereço inicial e o índice do elemento desejado. Em linguagens como C, isso é frequentemente implementado com arrays de tamanho fixo ou dinâmico. O código abaixo ilustra uma implemenatção estática com capacidade predefinida.

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

#define CAPACIDADE 100  // Capacidade máxima da lista

typedef struct {
    int itens[CAPACIDADE]; // Array que armazena os dados
    int quant;             // Número atual de elementos
} ListaArray;

void InicializarLista(ListaArray *arr) {
    arr->quant = 0;
}

int InserirNaLista(ListaArray *arr, int pos, int elem) {
    if (pos < 1 || pos > arr->quant + 1) {
        fprintf(stderr, "Posição de inserção inválida!\n");
        return 0;
    }
    if (arr->quant >= CAPACIDADE) {
        fprintf(stderr, "Lista cheia, não é possível inserir!\n");
        return 0;
    }
    for (int idx = arr->quant; idx >= pos; idx--) {
        arr->itens[idx] = arr->itens[idx - 1];
    }
    arr->itens[pos - 1] = elem;
    arr->quant++;
    return 1;
}

int RemoverDaLista(ListaArray *arr, int pos, int *removido) {
    if (pos < 1 || pos > arr->quant) {
        fprintf(stderr, "Posição de remoção inválida!\n");
        return 0;
    }
    *removido = arr->itens[pos - 1];
    for (int idx = pos; idx < arr->quant; idx++) {
        arr->itens[idx - 1] = arr->itens[idx];
    }
    arr->quant--;
    return 1;
}

int LocalizarElemento(ListaArray arr, int valor) {
    for (int i = 0; i < arr.quant; i++) {
        if (arr.itens[i] == valor) {
            return i + 1; // Retorna posição (base 1)
        }
    }
    return 0; // Elemento não encontrado
}

int RecuperarElemento(ListaArray arr, int pos, int *valor) {
    if (pos < 1 || pos > arr.quant) {
        fprintf(stderr, "Posição inválida!\n");
        return 0;
    }
    *valor = arr.itens[pos - 1];
    return 1;
}

void MostrarLista(ListaArray arr) {
    printf("Conteúdo da lista: ");
    for (int i = 0; i < arr.quant; i++) {
        printf("%d ", arr.itens[i]);
    }
    printf("\n");
}

int main() {
    ListaArray minhaLista;
    InicializarLista(&minhaLista);

    InserirNaLista(&minhaLista, 1, 5);
    InserirNaLista(&minhaLista, 2, 15);
    InserirNaLista(&minhaLista, 3, 25);
    MostrarLista(minhaLista);

    int pos = LocalizarElemento(minhaLista, 15);
    if (pos) {
        printf("O elemento 15 está na posição: %d\n", pos);
    }

    int rem;
    if (RemoverDaLista(&minhaLista, 2, &rem)) {
        printf("Elemento removido: %d\n", rem);
    }
    MostrarLista(minhaLista);

    int val;
    if (RecuperarElemento(minhaLista, 1, &val)) {
        printf("Primeiro elemento: %d\n", val);
    }

    return 0;
}

Lista Encadeada

Em uma lista encadeada, os elementos são armazenados em nós distribuídos em diferentes posições de memória. Cada nó contém um campo de dados e um ou mais ponteiros que apontam para outros nós, estabelecendo a ordem lógica. As variantes incluem listas simplesmente encadeadas, circulares e duplamente encadeadas.

Lista Simplesmente Encadeada

Cada nó possui um único ponteiro que aponta para o próximo nó na sequência. O primeiro nó é frequentemente referenciado por uma cabeça de lista, e um nó cabeça pode ser usado para simplificar operações.

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

typedef struct No {
    int dado;
    struct No *seguinte;
} No;

No* CriarNo(int valor) {
    No *novo = (No *)malloc(sizeof(No));
    if (!novo) {
        perror("Erro na alocação de memória");
        exit(EXIT_FAILURE);
    }
    novo->dado = valor;
    novo->seguinte = NULL;
    return novo;
}

void AdicionarNoInicio(No **cabeca, int valor) {
    No *novo = CriarNo(valor);
    novo->seguinte = *cabeca;
    *cabeca = novo;
}

void AdicionarNoFim(No **cabeca, int valor) {
    No *novo = CriarNo(valor);
    if (*cabeca == NULL) {
        *cabeca = novo;
        return;
    }
    No *atual = *cabeca;
    while (atual->seguinte != NULL) {
        atual = atual->seguinte;
    }
    atual->seguinte = novo;
}

void ExcluirNo(No **cabeca, int valor) {
    No *atual = *cabeca;
    No *anterior = NULL;

    while (atual != NULL && atual->dado != valor) {
        anterior = atual;
        atual = atual->seguinte;
    }

    if (atual == NULL) return; // Valor não encontrado

    if (anterior == NULL) {
        *cabeca = atual->seguinte;
    } else {
        anterior->seguinte = atual->seguinte;
    }
    free(atual);
}

void ImprimirLista(No *cabeca) {
    No *temp = cabeca;
    while (temp != NULL) {
        printf("%d -> ", temp->dado);
        temp = temp->seguinte;
    }
    printf("NULL\n");
}

void LiberarLista(No *cabeca) {
    No *temp;
    while (cabeca != NULL) {
        temp = cabeca;
        cabeca = cabeca->seguinte;
        free(temp);
    }
}

int main() {
    No *lista = NULL;

    AdicionarNoInicio(&lista, 30);
    AdicionarNoInicio(&lista, 20);
    AdicionarNoInicio(&lista, 10);
    AdicionarNoFim(&lista, 40);
    AdicionarNoFim(&lista, 50);

    printf("Lista inicial: ");
    ImprimirLista(lista);

    ExcluirNo(&lista, 30);
    printf("Após remover o 30: ");
    ImprimirLista(lista);

    LiberarLista(lista);
    return 0;
}

Lista Circular

Em uma lista circular, o último nó aponta de volta para o primeiro nó, formando um anel. Isso permite traversar a lista continuamente a partir de qualquer nó.

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

typedef struct NoCirc {
    int valor;
    struct NoCirc *prox;
} NoCirc;

typedef NoCirc* ListaCirc;

void CriarListaCirc(ListaCirc *lista) {
    *lista = (ListaCirc)malloc(sizeof(NoCirc));
    (*lista)->prox = *lista; // Aponta para si mesmo, lista vazia
}

int InserirEmCirc(ListaCirc lista, int pos, int elem) {
    NoCirc *p = lista;
    int cont = 0;

    while (p->prox != lista && cont < pos - 1) {
        p = p->prox;
        cont++;
    }

    if (cont != pos - 1) {
        fprintf(stderr, "Posição inválida!\n");
        return 0;
    }

    NoCirc *novo = (NoCirc *)malloc(sizeof(NoCirc));
    novo->valor = elem;
    novo->prox = p->prox;
    p->prox = novo;
    return 1;
}

int RemoverDeCirc(ListaCirc lista, int pos, int *removido) {
    NoCirc *p = lista;
    int cont = 0;

    while (p->prox != lista && cont < pos - 1) {
        p = p->prox;
        cont++;
    }

    if (p->prox == lista || cont != pos - 1) {
        fprintf(stderr, "Posição inválida!\n");
        return 0;
    }

    NoCirc *alvo = p->prox;
    *removido = alvo->valor;
    p->prox = alvo->prox;
    free(alvo);
    return 1;
}

void ExibirListaCirc(ListaCirc lista) {
    NoCirc *p = lista->prox;
    printf("Lista circular: ");
    while (p != lista) {
        printf("%d ", p->valor);
        p = p->prox;
    }
    printf("\n");
}

int main() {
    ListaCirc lista;
    CriarListaCirc(&lista);

    InserirEmCirc(lista, 1, 100);
    InserirEmCirc(lista, 2, 200);
    InserirEmCirc(lista, 3, 300);
    ExibirListaCirc(lista);

    int rem;
    if (RemoverDeCirc(lista, 2, &rem)) {
        printf("Elemento removido: %d\n", rem);
    }
    ExibirListaCirc(lista);

    free(lista);
    return 0;
}

Lista Duplamente Encadeada

Cada nó contém ponteiros tanto para o nó anterior quanto para o próximo, permitindo navegação bidirecional. Quando conectada em um ciclo, torna-se uma lista duplamente encadeada circular.

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

typedef struct NoDuplo {
    int chave;
    struct NoDuplo *ant;
    struct NoDuplo *prox;
} NoDuplo;

typedef NoDuplo* ListaDupla;

void InicializarDupla(ListaDupla *lista) {
    *lista = (ListaDupla)malloc(sizeof(NoDuplo));
    (*lista)->ant = *lista;
    (*lista)->prox = *lista;
}

int InserirDupla(ListaDupla lista, int pos, int elem) {
    NoDuplo *p = lista;
    int cont = 0;

    while (p->prox != lista && cont < pos - 1) {
        p = p->prox;
        cont++;
    }

    if (cont != pos - 1) {
        fprintf(stderr, "Posição inválida!\n");
        return 0;
    }

    NoDuplo *novo = (NoDuplo *)malloc(sizeof(NoDuplo));
    novo->chave = elem;
    novo->prox = p->prox;
    novo->ant = p;
    p->prox->ant = novo;
    p->prox = novo;
    return 1;
}

int RemoverDupla(ListaDupla lista, int pos, int *removido) {
    NoDuplo *p = lista->prox;
    int cont = 1;

    while (p != lista && cont < pos) {
        p = p->prox;
        cont++;
    }

    if (p == lista || cont > pos) {
        fprintf(stderr, "Posição inválida!\n");
        return 0;
    }

    *removido = p->chave;
    p->ant->prox = p->prox;
    p->prox->ant = p->ant;
    free(p);
    return 1;
}

void ImprimirDupla(ListaDupla lista) {
    NoDuplo *p = lista->prox;
    printf("Lista duplamente encadeada: ");
    while (p != lista) {
        printf("%d ", p->chave);
        p = p->prox;
    }
    printf("\n");
}

int main() {
    ListaDupla lista;
    InicializarDupla(&lista);

    InserirDupla(lista, 1, 500);
    InserirDupla(lista, 2, 600);
    InserirDupla(lista, 3, 700);
    ImprimirDupla(lista);

    int rem;
    if (RemoverDupla(lista, 2, &rem)) {
        printf("Elemento removido: %d\n", rem);
    }
    ImprimirDupla(lista);

    free(lista);
    return 0;
}

Tags: lista linear lista sequencial lista encadeada lista circular Lista Duplamente Encadeada

Publicado em 6-23 16:10