Cách đúng để xóa một mục khỏi danh sách được liên kết


9

Trong cuộc phỏng vấn Slashdot này, Linus Torvalds được trích dẫn:

Tôi đã thấy quá nhiều người xóa mục nhập danh sách liên kết đơn bằng cách theo dõi mục "trước" và sau đó xóa mục đó, làm một cái gì đó như

if (trước)
  trước-> next = entry-> next;
khác
  list_head = entry-> tiếp theo;

và bất cứ khi nào tôi thấy mã như vậy, tôi chỉ đi "Người này không hiểu con trỏ". Và thật đáng buồn là nó khá phổ biến.

Những người hiểu con trỏ chỉ cần sử dụng "con trỏ tới con trỏ mục nhập" và khởi tạo nó bằng địa chỉ của list_head. Và sau đó khi họ duyệt qua danh sách, họ có thể xóa mục nhập mà không cần sử dụng bất kỳ điều kiện nào, chỉ bằng cách thực hiện "* pp = entry-> next".

Là một nhà phát triển PHP, tôi đã không chạm vào con trỏ kể từ Giới thiệu về C ở trường đại học một thập kỷ trước. Tuy nhiên, tôi cảm thấy rằng đây là một loại tình huống mà ít nhất tôi nên làm quen. Linus đang nói về cái gì vậy? Thành thật mà nói, nếu tôi được yêu cầu thực hiện một danh sách được liên kết và xóa một mục, cách 'sai' ở trên là cách mà tôi sẽ đi về nó. Tôi cần biết gì để viết mã như Linus nói tốt nhất?

Tôi đang hỏi ở đây chứ không phải trên Stack Overflow vì tôi thực sự không gặp vấn đề gì với mã sản xuất này.


1
Điều anh ấy nói là khi bạn cần lưu trữ vị trí của prev, thay vì lưu trữ toàn bộ nút, bạn chỉ có thể lưu trữ vị trí của prev.next, vì đó là điều duy nhất bạn quan tâm. Một con trỏ tới một con trỏ. Và nếu bạn làm điều đó, bạn sẽ tránh được sự ngớ ngẩn if, vì bây giờ bạn không có trường hợp khó xử list_headlà một con trỏ từ bên ngoài một nút. Con trỏ tới đầu danh sách sau đó giống như con trỏ tới nút tiếp theo.
Ordous

@Ordous: Tôi hiểu rồi, cảm ơn. Tại sao một bình luận? Đó là một câu trả lời súc tích, rõ ràng và sáng sủa.
dotancohen

@Ordous Mọi thứ liên quan đến đoạn mã đó là một con trỏ, vì vậy điểm của anh ta không liên quan gì đến việc lưu trữ toàn bộ nút so với lưu trữ một con trỏ vào nó.
Doval 18/2/2015

Câu trả lời:


9

Sử dụng các kỹ năng L31 MS Paint của tôi:

nhập mô tả hình ảnh ở đây

Giải pháp ban đầu là trỏ đến Nút thông qua curr. Trong trường hợp đó, bạn kiểm tra xem nút tiếp theo sau currcó giá trị xóa hay không và nếu có thì đặt lại con trỏ currnút next. Vấn đề là không có nút nào trỏ đến đầu danh sách. Điều đó có nghĩa là phải có một trường hợp đặc biệt để kiểm tra nó.

Thay vào đó, Linus (có khả năng) đề xuất thay vào đó không phải là lưu con trỏ vào nút được kiểm tra hiện tại, mà là con trỏ tới con trỏ tới nút hiện tại (được gắn nhãn pp). Hoạt động là như nhau - nếu ppcon trỏ trỏ đến một nút có giá trị đúng, bạn đặt lại ppcon trỏ.

Sự khác biệt đến ngay từ đầu danh sách. Mặc dù không có Node nào trỏ đến phần đầu của danh sách, nhưng thực tế, có một con trỏ đến phần đầu của danh sách. Và nó cũng giống như một con trỏ tới một nút, giống như một nextcon trỏ nút khác . Do đó không cần một điều khoản đặc biệt cho phần đầu của danh sách.


Ah tôi hiểu rồi .... bạn học được điều gì đó mới mỗi ngày.
Lawrence Aiello

1
Tôi nghĩ rằng bạn mô tả mọi thứ một cách chính xác, nhưng tôi sẽ đề xuất rằng giải pháp thích hợp là list_headchỉ đến một cái gì đó có nextnút trỏ đến mục dữ liệu thực đầu tiên (và đã prevkhởi tạo cho cùng một đối tượng giả). Tôi không thích ý tưởng về việc prevchỉ ra một thứ gì đó thuộc loại khác, vì các thủ thuật như vậy có thể đưa ra Hành vi không xác định thông qua bí danh và làm cho mã không nhạy cảm với bố cục cấu trúc.
supercat

@supercat Đó chính xác là vấn đề. Thay vì prevchỉ vào Nút, nó trỏ đến con trỏ. Nó luôn trỏ đến một cái gì đó cùng loại, cụ thể là một con trỏ tới một Nút. Đề xuất của bạn về cơ bản là giống nhau - hãy prevchỉ ra một cái gì đó "bằng một nextnút". Nếu bạn loại bỏ vỏ, bạn chỉ cần lấy list_headcon trỏ ban đầu . Hay nói cách khác - một cái gì đó chỉ được xác định bằng cách có một con trỏ đến nút tiếp theo, tương đương về mặt ngữ nghĩa với một con trỏ tới một nút.
Thông thường

@Ordous: Điều đó có ý nghĩa, mặc dù nó giả định rằng list_headnextsẽ giữ cùng một "loại" con trỏ. Không phải là một vấn đề trong C, có lẽ, nhưng có lẽ có vấn đề trong C ++.
supercat

@supercat Tôi luôn cho rằng đó là đại diện "chính tắc" của một danh sách được liên kết, không liên quan đến ngôn ngữ. Nhưng tôi không đủ thành thạo để đánh giá liệu nó có tạo ra sự khác biệt giữa C và C ++ hay không, và các triển khai tiêu chuẩn ở đó là gì.
Thông thường

11

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Mã ví dụ

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

Nếu chúng ta cần một số logic để phá hủy nút, thì chúng ta chỉ cần thêm một dòng mã ở cuối:

// Deallocate the node which is now stranded from the list.
free(to_remove);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.