#define MALLOC(a) (a *) malloc ( sizeof(a) )
struct no
{
int info;
struct no *prox;
struct no *ant;
};
// não vou usar typedef - complica a vida de quem está começando
struct no *inicio;
struct no *fim;
Bom, definida nossa estrutura, nossos ponteiros globais e nossa simplificação da malloc(), vamos começar a pensar em quais seriam os possíveis casos que uma função de inserção teria que analisar:
- O novo nó passará a ser o novo primeiro nó (inicio).
- O novo nó ocupará uma posição entre dois outros nós.
- O novo nó passará a ser o novo último nó (fim).
Considerações:
- As setas pretas GRANDES representam o ponteiro "inicio".
- As setas azuis GRANDES representam o ponteiro "fim".
- As demais "setinhas" representam as ligações dos ponteiros "prox" e "ant".
Bom, mostrada a teoria de como inserir um dado, podemos construir nossa função para inserir.
void inserir ( int info )
{
struct no *novo = MALLOC ( struct no );
struct no *atual;
if ( !novo )
{
perror ( "Malloc: " );
return ;
}
// atribuição do novo valor...
novo->info = info;
// MARCAÇÃO 1 - NÃO HÁ ELEMENTOS NA LISTA.
if ( !inicio )
{
novo->prox = NULL;
novo->ant = NULL;
inicio = novo;
fim = novo;
return ;
}
// se não for o primeiro elemento da lista...
// MARCAÇÃO 2 - CASOS DE INSERÇÃO
atual = inicio;
while ( atual )
{
if ( atual->info < info )
atual = atual->prox;
else
{
// MARCAÇÃO 3 - caso 2
if ( atual->ant )
{
novo->prox = atual;
novo->ant = atual->ant;
atual->ant->prox = novo;
atual->ant = novo;
return ;
}
// MARCAÇÃO 4 caso 1
novo->prox = atual;
novo->ant = NULL;
atual->ant = novo;
inicio = novo;
return ;
}
}
// MARCAÇÃO 5 - caso 3
fim->prox = novo;
novo->prox = NULL;
novo->ant = fim;
fim = novo;
return ;
}
MARCAÇÃO 1:
A função inserir() primeiro certifica-se de que há uma lista para poder entrar em um loop de procura. Se não houver uma lista ( !inicio), elá criará uma.
if ( !inicio)
Se não há uma lista...
novo->prox = NULL;
novo->ant = NULL;
O ponteiro para o próximo nó do novo nó será nulo, assim como o ponteiro para o nó anterior, pois este será o único nó da lista atualmente.
inicio = novo;
fim = novo;
Isso "cria" a lista: toda vez que a função inserir() for chamada, ela fara a comparação "if(!inicio)", e como agora o ponteiro inicio terá um valor, esta comparação será falsa e a função executará a próxima parte. O ponteiro fim tem que ser iniciado também, pois se por algum motivo for usado sem nenhum valor por qualquer outra função, provavelmente causará quebra do programa.
MARCAÇÃO 2:
atual = inicio;
Esta atribuição garante que possamos varrer a lista conservando o endereço original do ponteiro inicio. "atual" é um ponteiro auxiliar de varredura. Para todas as função que varram a lista você precisará de um, a não ser que reconstrua a lista depois de todas as vezes que usar o ponteiro "inicio" ou "fim".
while (atual)
É auto-explicativo, mas vamos lá. Toda vez que o loop voltar ao inicio, ele fará uma comparação para checar se não chegou ao final da lista (ou ao inicio, dependendo da direção de sua varredura). Se atual for NULL (ou seja, fim da lista), o loop é cancelado.
if ( atual->info < info)
Este é o responsável por toda a função de inserção (Tá, exagerei um pouco...). Mas o que isso faz é comparar se a nova informação é maior do que a informação do nó atualmente sendo testado (note que ordem crescente foi a ordem escolhida para ordenação - certifique-se sempre de usar sinais/funções certas de comparação para a ordenação escolhida, errar aqui quase sempre significa quebra do programa).
atual = atual->prox;
Caso o if acima seja verdadeiro, ou seja, a nova informação é maior do que a informação atualmente sendo testada, esta linha faz com que 'atual' passe a ser o próximo item (Atribuições de endereço...).
Obs.: Note também que podemos nos referenciar ao elemento 'atual' da figura de 'atual->prox->ant'.
else {
Bom, isto é o que vai acontecer se a comparação der falsa (A nova informação é menor que o item atualmente sendo comparado - o que significa: inserção).
MARCAÇÃO 3:
if ( atual->ant )
Esta linha certifica-se de que o nó "atual" não é o primeiro nó da lista. Sim, qual o único nó da lista que possui um ponteiro "ant" com valor NULL? Sim, o primeiro nó. Então, se esta comparação for VERDADEIRA, significa que o valor de "atual->ant" NÃO É NULL, logo, não é o primeiro nó.
novo->prox = atual;
novo->ant = atual->ant;
atual->ant->prox = novo;
atual->ant = novo;
return ;
Bom, não tem como explicar isso melhor do que uma figura explicaria, então é só olhar como é feita a inserção na figura no início da página - CASO 2. Na figura, o ponteiro "atual" seria o quadrado INFO da direita. Soltem a imaginação!!!
MARCAÇÃO 4:
Estas próximas linhas poderiam estar dentro de um "else" do "if" da marcação 3... Sim, pois isto aqui só acontecerá se o "atual->ant" for NULL, ou seja, se o nó atual for o primeiro nó! Então vá lá ver na figura o caso 1 e novamente solte a imaginação...
MARCAÇÃO 5:
Se o programa sair do loop while(), significa que "atual" chegou ao último elemento e ainda assim, a nova informação era maior do que a informação atualmente sendo testada. Então é só inserir o elemento no final da lista. Bora pra figura ver o caso 3... e de novo, outra vez, novamente: solte a imaginação. O bom entendimento de listas encadeadas, árvores e afins está na visualização mental do negócio todo, quando conseguir ver as setinhas movimentando de uma lado para o outro para remover elementos ou inserí-los, você fará as funções de olhos fechados.
É, agora que inserimos os elementos não queremos eles lá mais não... vamos procurar e excluir esses @#$*%!