วิธีที่เหมาะสมในการลบรายการออกจากรายการที่เชื่อมโยง


10

ในการสัมภาษณ์ Slashdot Linus Torvalds อ้างว่า:

ฉันเห็นคนจำนวนมากเกินไปที่ลบรายการที่ลิงก์แบบเอกเทศโดยติดตามรายการ "ก่อนหน้า" จากนั้นลบรายการทำสิ่งที่ชอบ

ถ้า (prev)
  prev-> next = entry-> next;
else
  list_head = entry-> ถัดไป;

และเมื่อใดก็ตามที่ฉันเห็นรหัสเช่นนั้นฉันเพิ่งไป "คนนี้ไม่เข้าใจตัวชี้" และเป็นเรื่องธรรมดาที่ค่อนข้างเศร้า

ผู้ที่เข้าใจพอยน์เตอร์เพียงใช้ "ตัวชี้ไปยังตัวชี้รายการ" และเริ่มต้นด้วยที่อยู่ของ list_head และเมื่อพวกเขาเข้าไปในรายการพวกเขาสามารถลบรายการโดยไม่ต้องใช้เงื่อนไขใด ๆ เพียงแค่ทำ "* pp = entry-> next"

ในฐานะนักพัฒนา PHP ฉันไม่ได้สัมผัสพอยน์เตอร์ตั้งแต่รู้เบื้องต้นเกี่ยวกับ Cในมหาวิทยาลัยเมื่อสิบปีก่อน อย่างไรก็ตามฉันรู้สึกว่านี่เป็นสถานการณ์ประเภทที่ฉันควรทำความคุ้นเคยเป็นอย่างน้อย Linus กำลังพูดถึงอะไร ความจริงแล้วถ้าฉันถูกขอให้นำลิสต์ที่เชื่อมโยงและนำไอเท็มออกวิธีที่ 'ผิด' ข้างต้นเป็นวิธีที่ฉันจะทำ ฉันต้องรู้อะไรบ้างในการเขียนโปรแกรมเนื่องจาก Linus บอกว่าดีที่สุด

ฉันถามที่นี่มากกว่า Stack Overflow เพราะฉันไม่ได้มีปัญหากับรหัสการผลิต


1
สิ่งที่เขาพูดคือเมื่อคุณต้องการจัดเก็บตำแหน่งของprevแทนที่จะเก็บโหนดทั้งหมดคุณสามารถจัดเก็บตำแหน่งได้prev.nextเนื่องจากเป็นเพียงสิ่งเดียวที่คุณสนใจตัวชี้ไปยังตัวชี้ และถ้าคุณทำเช่นนั้นคุณจะหลีกเลี่ยงความโง่เขลาifตั้งแต่ตอนนี้คุณไม่มีกรณีที่น่าอึดอัดใจในlist_headการเป็นตัวชี้จากภายนอกโหนด ตัวชี้ไปยังส่วนหัวของรายการนั้นจะมีความหมายเหมือนกับตัวชี้ไปยังโหนดถัดไป
58

@Ordous: ฉันเห็นขอบคุณ ทำไมถึงแสดงความคิดเห็น? นั่นคือคำตอบที่กระชับชัดเจนและให้แสงสว่าง
dotancohen

@Ordous ทุกอย่างที่เกี่ยวข้องในโค้ดขนาดสั้นนั้นเป็นตัวชี้ดังนั้นประเด็นของเขาจึงไม่มีอะไรเกี่ยวข้องกับการจัดเก็บโหนดทั้งหมดเทียบกับการเก็บตัวชี้ไว้
Doval

คำตอบ:


9

การใช้ทักษะ L331 MS Paint ของฉัน:

ป้อนคำอธิบายรูปภาพที่นี่

currวิธีการแก้ปัญหาเดิมคือการชี้ไปที่โหนดผ่าน ในกรณีดังกล่าวคุณตรวจสอบว่าโหนดถัดไปหลังจากนั้นcurrมีค่าลบหรือไม่และให้รีเซ็ตตัวชี้currโหนด nextปัญหาคือไม่มีโหนดที่ชี้ไปที่ส่วนหัวของรายการ นั่นหมายความว่าจะต้องมีกรณีพิเศษเพื่อตรวจสอบ

สิ่งที่ Linus (น่าจะเป็น) เสนอแทนที่จะไม่บันทึกตัวชี้ไปยังโหนดที่ตรวจสอบปัจจุบัน แต่ให้ตัวชี้ไปยังตัวชี้ไปยังโหนดปัจจุบัน (ติดป้ายpp) การดำเนินการเหมือนกัน - หากppตัวชี้ชี้ไปที่โหนดที่มีค่าเหมาะสมคุณจะรีเซ็ตppตัวชี้

ความแตกต่างมาจากจุดเริ่มต้นของรายการ ในขณะที่ไม่มีโหนดที่ชี้ไปที่ส่วนหัวของรายการในความเป็นจริงมีตัวชี้ไปยังส่วนหัวของรายการ และมันก็เป็นเพียงตัวชี้เดียวกับโหนดเช่นเดียวกับnextตัวชี้โหนดอื่น ดังนั้นจึงไม่จำเป็นต้องมีประโยคพิเศษสำหรับการเริ่มต้นของรายการ


ฉันรู้แล้วตอนนี้ .... คุณเรียนรู้สิ่งใหม่ทุกวัน
ลอเรนซ์ Aiello

1
ฉันคิดว่าคุณอธิบายสิ่งต่าง ๆ ได้อย่างถูกต้อง แต่ฉันขอแนะนำว่าวิธีแก้ปัญหาที่เหมาะสมคือให้list_headชี้ไปที่สิ่งที่มีnextโหนดที่ชี้ไปที่รายการข้อมูลจริงแรก (และprevเริ่มต้นกับวัตถุจำลองเดียวกัน) ฉันไม่ชอบความคิดในการprevชี้ไปที่ประเภทที่แตกต่างกันเนื่องจากเทคนิคดังกล่าวอาจนำเสนอพฤติกรรมที่ไม่ได้กำหนดผ่านการใช้นามแฝงและทำให้โค้ดมีความอ่อนไหวโดยไม่จำเป็นกับโครงร่างโครงสร้าง
supercat

@supercat นั่นคือจุดที่แน่นอน แทนที่จะprevชี้ไปที่โหนดจะชี้ไปที่พอยน์เตอร์ มันมักจะชี้ไปที่สิ่งที่มีประเภทเดียวกันคือตัวชี้ไปยังโหนด ข้อเสนอแนะของคุณนั้นเหมือนกัน - prevชี้ไปที่บางสิ่ง "กับnextโหนด" หากคุณทิ้งเปลือกที่คุณเพิ่งได้รับการเริ่มต้นlist_headชี้ หรือกล่าวอีกนัยหนึ่ง - สิ่งที่ถูกกำหนดโดยการมีตัวชี้ไปยังโหนดถัดไปเท่านั้นจะเทียบเท่ากับตัวชี้ไปยังโหนด
Ordous

@Ordous: นั่นทำให้รู้สึกถึงแม้ว่ามันจะคาดการณ์ว่าlist_headและnextจะถือตัวชี้ "ชนิด" เดียวกัน ไม่ใช่ปัญหาใน C บางที แต่อาจมีปัญหาใน C ++
supercat

@supercat ฉันมักจะสันนิษฐานว่าเป็นตัวแทน "บัญญัติ" ของรายการที่เชื่อมโยงผู้ไม่เชื่อเรื่องภาษา แต่ฉันไม่ชำนาญพอที่จะตัดสินว่ามันสร้างความแตกต่างระหว่าง C และ C ++ จริง ๆ หรือไม่และการใช้งานมาตรฐานนั้นมีอะไรบ้าง
58

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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.