연결된 목록에는 항상 꼬리 포인터가 있어야합니까?


11

내 이해 ...

장점 :

  • 끝에 삽입하는 것은 O (N) 대신 O (1)입니다.
  • 목록이 이중 연결 목록 인 경우 끝에서 제거하는 것도 O (N) 대신 O (1)입니다.

불리:

이러한 장단점을 살펴보면 연결 목록이 꼬리 포인터를 사용하지 않는 이유를 알 수 없습니다. 내가 놓친 것이 있습니까?


1
꼬리 포인터는 4-8 바이트입니다 (32 또는 64 비트 시스템에 따라 다름)
래칫 괴물

1
이미 요약 한 것처럼 들립니다.
Robert Harvey

@RobertHarvey 저는 지금 데이터 구조를 연구하고 있으며 모범 사례가 무엇인지 알지 못합니다. 제가 쓴 것은 저의 인상이지만, 제가 요구하는 것은 그들이 맞는지입니다. 그러나 명확하게 해주셔서 감사합니다!
Adam Zerner

7
"모범 사례"는 대중의 아편입니다 . 여전히 자신을 생각할 수있는 능력이 있다는 사실을 축하하십시오.
Robert Harvey

링크 @RobertHarvey에 감사드립니다-나는 그 점을 좋아합니다! 나는 상황의 세부 사항을 보는 비용 편익 접근법을 확실히 취합니다.
Adam Zerner

답변:


7

당신은 맞습니다. 꼬리 포인터는 결코 아프지 않으며 도움이 될 수 있습니다. 그러나 테일 포인터가 전혀 필요하지 않은 상황이 있습니다.

스택을 구현하기 위해 연결된 목록을 사용하는 경우 모든 액세스, 삽입 및 제거가 헤드에서 발생하도록 보장 할 수 있으므로 테일 포인터가 필요하지 않습니다. 그것은 라이브러리 또는 플랫폼의 표준 구현이며 메모리가 저렴하지만 필요 하지 않기 때문에 어쨌든 꼬리 포인터가있는 이중 연결 목록을 사용할 수 있다고 합니다.


9

연결된 목록은 매우 일반적 이며 영구적 이며 불변입니다. 실제로 함수형 프로그래밍 언어에서이 사용법은 어디에나 있습니다. 꼬리 포인터는 두 속성을 모두 구분합니다. 그러나 불변성이나 지속성에 신경 쓰지 않으면 꼬리 포인터를 포함하는 데 단점이 거의 없습니다.


3
지속성과 불변성을 깨는 이유를 설명해 주시겠습니까?
Adam

캐시 친 화성 문제를 추가하십시오
Basilevs

이 질문 에서 나의 예를보십시오 . 목록의 헤드에서만 작업하고 변경할 수없는 경우 꼬리를 공유 할 수 있습니다. 꼬리 포인터를 사용하는 경우이 기술을 사용하여 불변성을 공유하고 유지 관리 할 수 ​​없습니다.
Karl Bielefeldt

실제로 불변으로 꼬리 포인터는 쓸모없는 옆에 있습니다. 왜냐하면 당신이 할 수있는 유일한 일은 마지막 요소가 무엇인지 알기 때문입니다. 다른 모든 것은 머리에서 작동해야합니다.
chet 괴물

0

나는 연결된 목록에 꼬리 포인터를 거의 사용하지 않으며 스택과 같은 삽입 및 제거 (또는 중간에서 선형 시간 제거) 패턴과 같은 푸시 / 팝 패턴이 충분할 때 단일 링크 목록을 더 자주 사용하는 경향이 있습니다. 일반적인 사용 사례에서는 단일 연결 목록을 이중 연결 목록으로 만드는 것처럼 꼬리 포인터가 실제로 비싸기 때문입니다.

단일 연결 목록에 대한 일반적인 사용 사례는 각각 몇 개의 목록 노드 만 포함하는 수십만 개의 연결 목록을 저장할 수 있습니다. 또한 일반적으로 연결된 목록에 대한 포인터를 사용하지 않습니다. 인덱스는 32 비트가 될 수 있기 때문에 대신 배열에 인덱스를 사용합니다. 예를 들어 64 비트 포인터의 절반을 차지합니다. 또한 일반적으로 목록 노드를 한 번에 하나씩 할당하지 않고 대신 큰 배열을 사용하여 모든 노드를 저장 한 다음 32 비트 인덱스를 사용하여 노드를 서로 연결합니다.

예를 들어 400x400 그리드를 사용하여 충돌 감지를 가속화하기 위해 서로 이동하고 튀어 나오는 백만 개의 입자를 분할하는 비디오 게임을 상상해보십시오. 이 경우 저장하는 매우 효율적인 방법은 160,000 개의 단일 연결 목록을 저장하는 것입니다.이 경우 160,000 개의 32 비트 정수 (~ 640 킬로바이트) 및 입자 당 하나의 32 비트 정수 오버 헤드로 변환됩니다. 파티클이 화면에서 움직일 때, 한 32 개의 정수를 업데이트하여 한 셀에서 다른 셀로 파티클을 옮기는 것입니다.

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

... next입자 노드 의 인덱스 ( "포인터")를 사용하여 셀에서 다음 파티클에 대한 인덱스 역할을하거나 파티클이 죽었을 경우 다음 프리 파티클을 회수합니다 (기본적으로 인덱스를 사용한 무료 목록 할당 자 구현).

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

셀에서 선형 시간 제거는 셀의 입자를 반복하여 입자 논리를 처리하기 때문에 실제로 오버 헤드가 아닙니다. 따라서 이중 연결 목록은 단지 유익하지 않은 종류의 오버 헤드를 추가합니다. 내 경우에는 꼬리가 나에게 전혀 도움이되지 않는 것처럼.

테일 포인터는 그리드의 메모리 사용량을 두 배로 늘리고 캐시 누락 수를 증가시킵니다. 또한 분기가 없어서 목록이 비어 있는지 확인하기 위해 분기가 필요합니다. 이중 연결 목록으로 만들면 각 입자의 목록 오버 헤드가 두 배가됩니다. 링크 된 목록을 사용하는 시간의 90 %는 이러한 경우에 해당하므로 꼬리 포인터는 실제로 저장하는 데 상당히 비쌉니다.

따라서 4-8 바이트는 실제로 링크 된 목록을 처음 사용하는 대부분의 컨텍스트에서 사소한 것이 아닙니다. 데이터 구조를 사용하여 요소의 보트로드를 저장하는 경우 4-8 ​​바이트가 항상 무시할 수있는 것은 아니기 때문에 칩을 넣고 싶었습니다. 사실에 링크 된 목록을 사용 감소 폭발적인 메모리를 사용했을 그리드 성장 160,000 동적 배열을 저장, 숫자 메모리 할당과 말, 반대로 필요한 메모리의 양을 (일반적으로 한 포인터 플러스 그리드 셀 당 적어도 두 개의 정수 셀당 하나의 정수 및 0 힙 할당이 아닌 그리드 셀당 힙 할당과 함께).

LL이 일반적으로 연속성이 없기 때문에 LL이 종종 좋지 않은 경우 전면 / 중간 제거 및 전면 / 중간 삽입과 관련된 상수가 복잡하기 때문에 많은 사람들이 링크 된 목록에 도달하는 경우가 많습니다. LL이 성능 관점에서 나에게 아름다운 곳은 단지 몇 개의 포인터를 조작하여 하나의 요소를 한 목록에서 다른 목록으로 옮길 수 있으며 가변 크기의 메모리 할당 자없이 가변 크기의 데이터 구조를 달성 할 수 있다는 것입니다 각 노드는 크기가 균일합니다. 예를 들어 무료 목록을 사용할 수 있습니다. 각 목록 노드가 범용 할당 자에 대해 개별적으로 할당되는 경우 일반적으로 연결된 목록이 대안에 비해 훨씬 나빠질 때입니다. '

대신 링크 된 목록이 간단한 대안에 비해 매우 효과적인 최적화 역할을하는 대부분의 경우 가장 유용한 형식은 일반적으로 단일 링크되고 헤드 포인터 만 필요하며 일반적인 메모리 할당이 필요하지 않습니다. 대신 노드 당 이미 할당 된 메모리를 풀링 할 수 있습니다 (예 : 이미 사전에 할당 된 큰 배열에서). 또한 각 SLL은 일반적으로 그래프 노드에 연결된 에지 (하나의 대규모 링크 된리스트가 아닌 여러 개의 작은 링크 된리스트)와 같은 경우에 매우 적은 수의 요소를 저장합니다.

요즘에는 많은 양의 DRAM이 있지만 두 번째로 느린 메모리 유형입니다. 64 바이트 캐시 라인이있는 L1 캐시에 관해서는 여전히 코어 당 64KB와 같습니다. 결과적으로, 적은 바이트 절약은 캐시 노드에 두 배나 많은 노드를 저장하는 것과의 차이를 의미하는 경우, 예를 들어 수백만 배를 곱한 위의 입자 시뮬레이션과 같은 성능에 중요한 영역에서 실제로 중요 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.