A maneira correta de remover um item de uma lista vinculada


9

Nesta entrevista ao Slashdot, Linus Torvalds é citado como tendo dito:

Eu já vi muitas pessoas que excluem uma entrada de lista vinculada individualmente acompanhando a entrada "anterior" e, em seguida, para excluir a entrada, fazendo algo como

se (anterior) anterior-
  > próxima = entrada-> próxima;
else
  list_head = entry-> next;

e sempre que vejo código assim, simplesmente digo "Essa pessoa não entende ponteiros". E, infelizmente, é bastante comum.

As pessoas que entendem ponteiros simplesmente usam um "ponteiro para o ponteiro de entrada" e inicializam isso com o endereço do list_head. E então, ao percorrer a lista, eles podem remover a entrada sem usar condicionais, apenas executando "* pp = entry-> next".

Como desenvolvedor de PHP, não toquei em indicadores desde a introdução ao C na universidade há uma década. No entanto, sinto que este é um tipo de situação com a qual devo pelo menos estar familiarizado. Sobre o que Linus está falando? Para ser sincero, se me pedissem para implementar uma lista vinculada e remover um item, a maneira 'errada' acima é a maneira que eu faria. O que preciso saber para codificar como Linus diz melhor?

Estou perguntando aqui, e não no Stack Overflow, pois não estou tendo um problema com isso no código de produção.


11
O que ele está dizendo é que, quando você precisa armazenar a localização do prev, em vez de armazenar o nó inteiro, pode simplesmente armazenar a localização prev.next, já que essa é a única coisa na qual você está interessado. Um ponteiro para um ponteiro. E se você fizer isso, evite o tolo if, já que agora não tem o caso estranho de list_headser um ponteiro de fora de um nó. O ponteiro para o início da lista é semanticamente o mesmo que o ponteiro para o próximo nó.
Ordous

@ Ordous: Entendo, obrigado. Por que um comentário? Essa é uma resposta concisa, clara e esclarecedora.
dotancohen

@Ordous Tudo o que está envolvido nesse trecho de código é um ponteiro; portanto, seu argumento não pode ter nada a ver com o armazenamento de todo o nó contra o armazenamento de um ponteiro nele.
Doval

Respostas:


9

Usando minhas habilidades no L331 MS Paint:

insira a descrição da imagem aqui

A solução original é apontar para Nós via curr. Nesse caso, você verifica se o próximo nó depois currtem o valor de exclusão e, se sim, redefine o ponteiro do currnext. O problema é que não há um nó que aponte para o início da lista. Isso significa que deve haver um caso especial para verificar isso.

O que Linus (provavelmente) propõe, em vez disso, não é salvar o ponteiro no nó examinado atual, mas o ponteiro no ponteiro no nó atual (rotulado pp). A operação é a mesma - se o ppponteiro apontar para um nó com o valor correto, você redefine o ppponteiro.

A diferença vem no começo da lista. Embora não haja Nó que aponte para o início da lista, na verdade, existe um ponteiro para o início da lista. E é da mesma forma um ponteiro para um nó, assim como outro nextponteiro de nós . Portanto, não há necessidade de uma cláusula especial para o início da lista.


Ah eu vejo agora .... você aprende algo novo todos os dias.
Lawrence Aiello

11
Eu acho que você descreve as coisas corretamente, mas eu sugeriria que a solução adequada é list_headapontar para algo com um nextnó que aponte para o primeiro item de dados reais (e tenha sido previnicializado para o mesmo objeto fictício). Não gosto da ideia de prevapontar para algo de tipo diferente, pois esses truques podem introduzir o comportamento indefinido por meio de alias e tornar o código desnecessariamente sensível ao layout da estrutura.
Supercat

@ supercat Esse é exatamente o ponto. Em vez de prevapontar para nós, ele aponta para ponteiros. Sempre aponta para algo do mesmo tipo, ou seja, um ponteiro para um Nó. Sua sugestão é essencialmente a mesma - prevaponte para algo "com um nextnó". Se você descartar o shell, basta obter o list_headponteiro inicial . Ou, em outras palavras - algo que é definido apenas com um ponteiro para o próximo nó, é semanticamente equivalente a um ponteiro para um nó.
Ordous

@ Ordous: Isso faz sentido, apesar de pressupor isso list_heade nextmanterá o mesmo "tipo" de ponteiro. Não é um problema em C, talvez, mas talvez seja problemático em C ++.
Supercat

@ supercat Eu sempre assumi que essa é a representação "canônica" de uma lista vinculada, independente da linguagem. Mas não sou proficiente o suficiente para julgar se realmente faz alguma diferença entre C e C ++ e quais são as implementações padrão.
Ordous

11

insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

Exemplo de código

// ------------------------------------------------------------------
// Start by pointing to the head pointer.
// ------------------------------------------------------------------
//    (next_ptr)
//         |
//         v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[....]
Node** next_ptr = &list->head;

// ------------------------------------------------------------------
// Search the list for the matching entry.
// After searching:
// ------------------------------------------------------------------
//                                  (next_ptr)
//                                       |
//                                       v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[next]
while (*next_ptr != to_remove) // or (*next_ptr)->val != to_remove->val
{
    Node* next_node = *next_ptr
    next_ptr = &next_node->next;
}

// ------------------------------------------------------------------
// Dereference the next pointer and set it to the next node's next
// pointer.
// ------------------------------------------------------------------
//                                           (next_ptr)
//                                                |
//                                                v
// [head]----->[..]----->[..]----->[..]---------------------->[next]
*next_ptr = to_remove->next;

Se precisarmos de alguma lógica para destruir o nó, basta adicionar uma linha de código no final:

// Deallocate the node which is now stranded from the list.
free(to_remove);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.