Cara yang tepat untuk menghapus item dari daftar tertaut

10

Dalam wawancara Slashdot ini, Linus Torvalds dikutip mengatakan:

Saya telah melihat terlalu banyak orang yang menghapus entri daftar yang ditautkan tunggal dengan melacak entri "sebelumnya", dan kemudian menghapus entri, melakukan sesuatu seperti

if (prev)
  prev-> next = entry-> next;
lain
  list_head = entri-> selanjutnya;

dan setiap kali saya melihat kode seperti itu, saya hanya pergi "Orang ini tidak mengerti petunjuk". Dan itu sangat umum.

Orang-orang yang mengerti pointer hanya menggunakan "pointer ke entry pointer", dan menginisialisasi dengan alamat dari list_head. Dan ketika mereka melintasi daftar, mereka dapat menghapus entri tanpa menggunakan kondisional apa pun, dengan hanya melakukan "* pp = entri-> selanjutnya".

Sebagai pengembang PHP saya belum menyentuh pointer sejak Pengantar C di universitas satu dekade lalu. Namun, saya merasa ini adalah jenis situasi yang setidaknya harus saya kenal. Apa yang Linus bicarakan? Sejujurnya, jika saya diminta untuk mengimplementasikan daftar tertaut dan menghapus item, cara 'salah' di atas adalah cara saya melakukannya. Apa yang perlu saya ketahui kode sebagai Linus mengatakan yang terbaik?

Saya bertanya di sini daripada di Stack Overflow karena saya sebenarnya tidak memiliki masalah dengan ini dalam kode produksi.

dotancohen
sumber
1
Apa yang dia katakan adalah bahwa ketika Anda perlu menyimpan lokasi prev, alih-alih menyimpan seluruh node, Anda hanya dapat menyimpan lokasi prev.next, karena itulah satu-satunya hal yang Anda minati. Penunjuk ke penunjuk. Dan jika Anda melakukan itu, Anda menghindari yang konyol if, karena sekarang Anda tidak memiliki kasus canggung list_headmenjadi penunjuk dari luar node. Pointer ke kepala daftar kemudian secara semantik sama dengan pointer ke node berikutnya.
Ordous
@Ordous: Begitu, terima kasih. Kenapa berkomentar? Itu adalah jawaban yang ringkas, jelas, dan mencerahkan.
dotancohen
@Ordous Segala sesuatu yang terlibat dalam cuplikan kode tersebut adalah pointer, jadi maksudnya tidak ada hubungannya dengan menyimpan seluruh node vs menyimpan pointer ke sana.
Doval

Jawaban:

9

Menggunakan keterampilan L331 MS Paint saya:

masukkan deskripsi gambar di sini

Solusi asli adalah dengan menunjuk ke Nodes via curr. Dalam hal ini Anda memeriksa apakah simpul berikutnya setelah currmemiliki nilai hapus, dan jika demikian reset pointer currsimpul next. Masalahnya adalah bahwa tidak ada simpul yang menunjuk ke kepala daftar. Itu berarti harus ada kasus khusus untuk memeriksanya.

Apa yang Linus (kemungkinan) usulkan bukan untuk menyimpan pointer ke node yang diperiksa saat ini, melainkan pointer ke pointer ke node saat ini (berlabel pp). Operasi ini sama - jika pppointer menunjuk ke sebuah simpul dengan nilai yang tepat, Anda mereset pppointer tersebut.

Perbedaannya ada di bagian paling awal dari daftar. Meskipun tidak ada Node yang menunjuk ke kepala daftar, sebenarnya ada sebuah penunjuk ke kepala daftar. Dan itu adalah pointer sama ke node, sama seperti nextpointer node lain . Karenanya tidak perlu klausa khusus untuk awal daftar.

Biasa saja
sumber
Ah saya mengerti sekarang .... Anda belajar sesuatu yang baru setiap hari.
Lawrence Aiello
1
Saya pikir Anda menggambarkan hal-hal dengan benar, tetapi saya akan menyarankan bahwa solusi yang tepat adalah list_headmenunjuk ke sesuatu dengan nextsimpul yang menunjuk ke item data nyata pertama (dan telah prevdiinisialisasi ke objek boneka yang sama). Saya tidak suka ide untuk prevmenunjuk sesuatu yang berbeda, karena trik seperti itu dapat memperkenalkan Perilaku Tidak Terdefinisi melalui aliasing, dan membuat kode sensitif terhadap tata letak struktur.
supercat
@ supercat Itulah intinya. Alih-alih prevmenunjuk ke Nodes, itu menunjuk ke pointer. Selalu menunjuk ke sesuatu dari jenis yang sama, yaitu pointer ke Node. Saran Anda pada dasarnya sama - prevarahkan ke sesuatu "dengan nextsimpul". Jika Anda membuang shell Anda hanya mendapatkan list_headpointer awal . Atau dengan kata lain - sesuatu yang didefinisikan hanya dengan memiliki pointer ke node berikutnya, secara semantik setara dengan pointer ke sebuah node.
Ordous
@Ordous: Itu masuk akal, meskipun mengandaikan itu list_headdan nextakan memegang "jenis" pointer yang sama. Bukan masalah di C, mungkin, tapi mungkin bermasalah di C ++.
supercat
@supercat Saya selalu berasumsi bahwa itu adalah representasi "kanonik" dari daftar tertaut, bahasa-agnostik. Tapi saya tidak cukup mahir untuk menilai apakah itu membuat perbedaan antara C dan C ++ benar-benar, dan apa implementasi standar di sana.
Ordous
11

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Contoh Kode

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

Jika kita membutuhkan logika untuk menghancurkan node, maka kita bisa menambahkan satu baris kode di akhir:

// Deallocate the node which is now stranded from the list.
free(to_remove);

sumber