"단일 연결 리스트는 앞만 봐. 뒤로 가는 포인터를 하나 더 달면 선 자리에서 바로 지우고, 양쪽으로 걸어 다니고, 부서지기 쉬운 예외 처리 코드를 안 써도 되게 돼."
단일의 사각지대
단일 연결 노드는 자기 next만 알아. 이 한쪽 시야가 실제로 아픈 데를 만들어. 노드를 지우려면 그 앞 노드가 있어야 하거든. prev.next를 지울 노드 너머로 다시 이어줘야 하니까. 그런데 단일 리스트에는 뒤로 갈 길이 없어서 head부터 다시 걸어 앞 노드를 찾아야 해. 이미 손에 쥐고 있는 노드 하나 지우자고 O(n)을 쓰는 거야.
이중 연결 리스트는 노드마다 next와 prev를 같이 들고 있어. 지울 노드의 참조와 소유 리스트의 경계 정보가 있으면 이웃 포인터 몇 개만 고쳐서 O(1)에 떼어낼 수 있지. 물론 head나 tail을 지우는 거라면 소유 객체의 head/tail도 같이 갱신해야 하고, 센티넬이 없으면 양 끝은 따로 처리해 줘야 해. 뒤로도 걸어갈 수 있다는 점 역시 단일 리스트와 갈리는 중요한 차이고.
원형: 끝이 다시 시작으로
원형 연결 리스트는 마지막 노드가 None 대신 head를 가리켜서 고리를 만들어. 라운드 로빈처럼 끝까지 갔다가 다시 처음으로 돌아와야 하는 순환 처리에 딱 맞지. 원형 이중 리스트라면 head의 prev가 tail을 가리키기도 하고. 다만 고정 크기 버퍼나 큐를 꼭 이 구조로 만들어야 하는 건 아니야. 배열이나 블록 구조로 만드는 경우도 흔해.
프로의 한 수: 센티넬 노드
실제 연결 리스트 코드는 예외 상황투성이야. 빈 리스트, head 삭제, tail 삭제, 원소가 하나뿐인 리스트. 하나하나 if가 따로 붙고, 그 if 하나하나가 틀릴 자리지. 고전적인 해법이 센티넬, 다른 말로 더미 노드야. 진짜 head 앞에, 그리고 자주는 진짜 tail 뒤에도 값 없는 노드를 영구히 하나 세워 두는 거지. 그러면 모든 진짜 노드가 항상 진짜 prev를 갖게 되니까 "head 삭제"가 더 이상 특별한 경우가 아니게 돼. 노드 하나 분량의 메모리를 내주고 버그 한 범주를 통째로 지우는 거야. 실무에서 쓰는 덱과 여러 표준 라이브러리가 정확히 이 방식을 써.