Правильний спосіб видалення елемента із пов’язаного списку


10

У цьому інтерв'ю Slashdot цитується Лінус Торвальдс:

Я бачив занадто багато людей, які видаляють запис, пов'язаний окремо у списку, відслідковуючи запис "prev", а потім видаляючи його, роблячи щось подібне

if (prev)
  prev-> next = entry-> next;
else
  list_head = entry-> наступний;

і коли я бачу такий код, я просто переходжу "Ця людина не розуміє покажчиків". І це, на жаль, досить часто.

Люди, які розуміють покажчики, просто використовують "вказівник на вхідний вказівник" та ініціалізують його з адресою list_head. А потім, переходячи список, вони можуть видалити запис, не використовуючи жодних умовних умов, просто виконавши "* pp = entry-> next".

Як розробник PHP, я не торкався покажчиків після вступу до С в університеті десятиліття тому. Однак я відчуваю, що це такий тип ситуацій, з яким я, принаймні, повинен бути знайомий. Про що говорить Лінус? Якщо чесно, якщо мене попросили застосувати пов'язаний список і видалити елемент, вищезгаданий «неправильний» спосіб - це шлях, який я б пройшов. Що мені потрібно знати, щоб кодувати, як найкраще каже Лінус?

Я запитую тут, а не про Stack Overflow, оскільки у мене фактично не виникає проблем із цим у виробничому коді.


1
Що він говорить, це те, що коли вам потрібно зберігати місце розташування prev, а не зберігати весь вузол, ви можете просто зберігати розташування prev.next, тому що це єдине, що вас цікавить. Вказівник на покажчик. А якщо ви це зробите, то уникаєте дурних if, оскільки зараз у вас немає незручного випадку list_headбути вказівником ззовні вузла. Потім вказівник на голову списку семантично такий же, як вказівник на наступний вузол.
Звичайний

@Ordous: Бачу, дякую. Чому коментар? Це лаконічна, чітка та освітлююча відповідь.
dotancohen

@Ordous Все, що бере участь у цьому фрагменті коду, - це вказівник, тому його точка не може мати нічого спільного зі збереженням всього вузла проти збереження вказівника на нього.
Doval

Відповіді:


9

Використання моїх навичок L331 MS Paint:

введіть тут опис зображення

Оригінальним рішенням є вказівка ​​на Nodes via curr. У такому випадку ви перевіряєте, чи має наступний вузол після currцього значення видалення, і якщо так, скидаєте покажчик currвузла next. Проблема полягає в тому, що не існує вузла, який би вказував на голову списку. Це означає, що для перевірки це має бути окремий випадок.

Лінус (ймовірно) пропонує замість цього не зберегти вказівник на поточний досліджуваний вузол, а скоріше вказівник на вказівник на поточний вузол (позначений міткою pp). Операція така ж - якщо ppвказівник вказує на вузол з правильним значенням, ви скинете ppвказівник.

Різниця полягає в самому початку списку. Поки немає Вузла, який би вказував на голову списку, насправді є вказівник на голову списку. І це точно такий же покажчик на вузол, як і інший nextвказівник на вузли . Отже, для початку списку не потрібне спеціальне застереження.


Ах, я бачу .... ви щодня дізнаєтесь щось нове.
Лоуренс Айелло

1
Я думаю, ви правильно описуєте речі, але я б припустив, що правильним рішенням є list_headвказівка ​​на щось із nextвузлом, який вказує на перший реальний елемент даних (і prevініціалізований на той самий фіктивний об’єкт). Мені не подобається ідея prevвказувати на щось різного типу, оскільки такі хитрощі можуть ввести Undefined Behavior за допомогою псевдоніму і зробити код непотрібним для розташування структури.
supercat

@supercat Саме в цьому справа. Замість того, щоб prevвказувати на Вузли, він вказує на вказівники. Це завжди вказує на щось одного типу, а саме на вказівник на Вузол. Ваша пропозиція по суті однакова - prevвкажіть на щось "з nextвузлом". Якщо ви відкинете оболонку, ви отримаєте початковий list_headвказівник. Або іншими словами - те, що визначається лише наявністю вказівника на наступний вузол, семантично еквівалентне вказівнику на вузол.
Звичайний

@Ordous: Це має сенс, хоча він припускає , що list_headі nextбуде тримати такий же «вид» покажчика. Можливо, це не проблема C, але, можливо, проблема C ++.
supercat

@supercat Я завжди вважав, що це "канонічне" представлення пов'язаного списку, мовно-агностичного. Але я недостатньо досвідчений, щоб судити про те, чи справді це різниця між C і C ++, і які там стандартні реалізації.
Звичайний

11

введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення

Приклад коду

// ------------------------------------------------------------------
// 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;

Якщо нам потрібна певна логіка, щоб знищити вузол, то ми можемо просто додати рядок коду в кінці:

// Deallocate the node which is now stranded from the list.
free(to_remove);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.