연결된 목록에서 항목을 제거하는 올바른 방법


10

이 Slashdot 인터뷰에서 Linus Torvalds는 다음과 같이 인용됩니다.

"이전"항목을 추적하여 단일 링크 목록 항목을 삭제 한 다음 항목을 삭제하여 다음과 같은 작업을 수행하는 사람이 너무 많습니다.

if (prev)
  prev-> next = entry-> next;
else
  list_head = 항목-> 다음;

그런 코드를 볼 때마다 "이 사람은 포인터를 이해하지 못합니다"로 이동합니다. 슬프게도 매우 흔합니다.

포인터를 이해하는 사람들은 "항목 포인터에 대한 포인터"를 사용하고 list_head의 주소로 초기화합니다. 그런 다음 목록을 탐색 할 때 "* pp = entry-> next"를 수행하여 조건을 사용하지 않고 항목을 제거 할 수 있습니다.

PHP 개발자로서 저는 10 년 전 대학 소개 C 이후 포인터를 다루지 않았습니다 . 그러나 나는 이것이 내가 익숙해야 할 상황 유형이라고 생각합니다. 리누스는 무엇에 대해 이야기하고 있습니까? 솔직히 말하면, 링크 된 목록을 구현하고 항목을 제거하라는 요청을 받으면 위의 '잘못된'방법은 내가 갈 길입니다. Linus가 가장 잘 말한 것처럼 코딩하려면 무엇을 알아야합니까?

실제로 프로덕션 코드 에서이 문제가 없으므로 Stack Overflow 대신 여기에 묻습니다.


1
그가 말한 것은 prev전체 노드를 저장하는 대신의 위치를 저장해야 할 때의 위치 만 저장할 prev.next수 있다는 것입니다. 관심있는 유일한 항목이므로 포인터에 대한 포인터입니다. 그리고 그렇게하면 어리석은 것을 피할 수 있습니다. if이제 list_head노드 외부에서 포인터가 어색한 경우가 없기 때문에 . 리스트의 헤드에 대한 포인터는 의미 적으로 다음 노드에 대한 포인터와 동일합니다.
— Ordous

@Ordous : 감사합니다. 왜 코멘트? 그것은 간결하고 명확하며 조명적인 답변입니다.
— dotancohen

@Ordous 해당 코드 스 니펫과 관련된 모든 것은 포인터이므로 포인터를 저장하는 것과 전체 노드를 저장하는 것과 관련이 없습니다.
— Doval

답변:


9

내 L331 MS Paint 기술 사용 :

여기에 이미지 설명을 입력하십시오

원래 솔루션은을 통해 노드를 가리키는 것 curr입니다. 이 경우 다음 노드 다음 curr에 삭제 값이 있는지 확인 하고 curr노드 next포인터를 재설정하십시오 . 문제는 목록의 헤드를 가리키는 노드가 없다는 것입니다. 그것은 그것을 확인하기 위해 특별한 경우가 있어야 함을 의미합니다.

Linus가 제안하는 것은 현재 검사 된 노드에 대한 포인터를 저장하는 것이 아니라 현재 노드에 대한 포인터를 가리키는 것입니다 (이라고 표시 pp). 작업이 동일합니다. pp포인터가 올바른 값을 가진 노드를 가리키는 경우 포인터를 재설정합니다 pp.

차이점은 목록의 맨 처음에옵니다. 목록의 헤드를 가리키는 노드는 없지만 실제로는 목록의 헤드를 가리키는 포인터가 있습니다. 그리고 이것은 다른 노드 next포인터 와 마찬가지로 노드에 대한 포인터입니다. 따라서 목록의 시작을위한 특별한 조항이 필요하지 않습니다.


아 나는 지금 본다.… 매일 새로운 것을 배운다.
— Lawrence 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 ++ 사이에 실제로 차이를 만드는지 여부와 표준 구현이 무엇인지 판단하기에 능숙하지 않습니다.
— Ordous

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.