연결된 목록에는 항상 꼬리 포인터가 있어야합니까? O (1)입니다. 불리: 사소한 양의

내 이해 …

장점 :

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

불리:

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



답변

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

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


답변

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


답변

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

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

예를 들어 400×400 그리드를 사용하여 충돌 감지를 가속화하기 위해 서로 이동하고 튀어 나오는 백만 개의 입자를 분할하는 비디오 게임을 상상해보십시오. 이 경우 저장하는 매우 효율적인 방법은 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와 같습니다. 결과적으로, 적은 바이트 절약은 캐시 노드에 두 배나 많은 노드를 저장하는 것과의 차이를 의미하는 경우, 예를 들어 수백만 배를 곱한 위의 입자 시뮬레이션과 같은 성능에 중요한 영역에서 실제로 중요 할 수 있습니다.