연결 리스트 (Linked List)

한눈에 보기

노드를 포인터로 이어 데이터를 저장하는 연결 리스트를 다룬다. 단일, 이중, 원형 구조의 차이와 연산별 비용, 배열과의 실제 성능 비교, 사이클 검출 같은 대표 문제 유형까지 정리했다.

최종 업데이트 2026.09.17

연결 리스트(Linked List)는 값과 다음 노드를 가리키는 포인터(pointer)를 묶은 노드(node)들을 사슬처럼 이어서 데이터를 순서대로 저장하는 선형 자료구조다. 연결 리스트는 메모리의 연속성을 요구하지 않기 때문에 크기를 자유롭게 늘릴 수 있고, 노드의 연결만 바꾸면 되므로 삽입과 삭제가 가볍다는 특징이 있다. 대신 원하는 위치에 접근하려면 머리부터 포인터를 따라가야 하므로 임의 접근이 느리다.

구조와 종류

가장 단순한 형태인 단일 연결 리스트(singly linked list)는 각 노드가 값과 다음 노드의 참조 하나만 가진다. 리스트 전체는 첫 노드를 가리키는 머리(head) 포인터로 표현되며, 마지막 노드의 다음 참조는 널(null)이다.

이중 연결 리스트(doubly linked list)는 각 노드가 이전 노드의 참조까지 갖는다. 양방향 순회가 가능하고, 어떤 노드를 쥐고 있을 때 그 노드 자신을 O(1)에 삭제할 수 있다는 것이 큰 장점이다. 단일 연결 리스트에서는 삭제할 노드의 이전 노드를 알아야 하므로 같은 작업에 탐색이 필요하다. 대신 포인터가 하나 더 붙어 메모리를 더 쓴다.

원형 연결 리스트(circular linked list)는 마지막 노드가 다시 첫 노드를 가리켜 끝이 없는 고리를 이룬다. 라운드 로빈(round robin) 방식의 스케줄링처럼 순환 구조가 자연스러운 문제에 쓰인다.

주요 연산과 시간 복잡도

연산시간 복잡도비고
머리에 삽입/삭제O(1)head 포인터만 바꾼다
꼬리에 삽입O(1)tail 포인터를 유지할 때
인덱스 접근O(n)머리부터 순차 이동한다
값 탐색O(n)정렬 여부와 무관하게 순차 탐색만 가능하다
노드 참조를 쥔 상태의 삽입/삭제O(1)포인터 연결만 바꾼다(이중 연결 기준)

핵심은 "위치를 이미 알고 있는가"이다. 노드 참조를 쥐고 있다면 삽입과 삭제는 포인터 두어 개를 고쳐 쓰는 것으로 끝나지만, 위치를 찾는 것부터 시작해야 한다면 탐색 비용 O(n)이 앞에 붙는다. 정렬된 연결 리스트라도 이진 탐색(binary search)을 쓸 수 없다는 점도 배열과의 중요한 차이다. 중간 지점으로 한 번에 점프할 방법이 없기 때문이다.

코드 예시

다음은 단일 연결 리스트의 노드 정의와 머리 삽입, 그리고 특정 값을 가진 노드를 삭제하는 자바(Java) 예시다. 삭제는 이전 노드의 next를 건너뛰게 만드는 것이 전부다.

class Node {
    int value;
    Node next;
    Node(int value) { this.value = value; }
}

// 머리에 삽입: O(1)
Node insertFirst(Node head, int value) {
    Node node = new Node(value);
    node.next = head;
    return node; // 새 head
}

// 값으로 삭제: 탐색 O(n) + 연결 변경 O(1)
Node delete(Node head, int target) {
    if (head == null) return null;
    if (head.value == target) return head.next;
    Node prev = head;
    while (prev.next != null && prev.next.value != target) {
        prev = prev.next;
    }
    if (prev.next != null) prev.next = prev.next.next;
    return head;
}

배열과의 비교와 실전 감각

배열(Array)은 연속 메모리 덕분에 O(1) 임의 접근과 높은 캐시 적중률을 얻지만 중간 삽입과 삭제에 O(n)의 이동 비용을 치른다. 연결 리스트는 정반대로 삽입과 삭제의 연결 변경은 싸지만 접근과 순회가 느리다. 특히 노드가 힙(heap) 여기저기에 흩어져 있어 순회 시 캐시 미스(cache miss)가 잦고, 노드마다 포인터와 할당 오버헤드가 붙어 메모리 효율도 떨어진다. 그래서 이론상 연결 리스트가 유리해 보이는 상황에서도 실측하면 동적 배열이 이기는 경우가 많다.

그럼에도 연결 리스트가 여전히 중요한 이유는 다른 구조의 재료가 되기 때문이다. 해시 테이블(hash table)의 체이닝(chaining) 충돌 처리, LRU 캐시(Least Recently Used cache)에서 해시맵과 이중 연결 리스트를 결합해 O(1) 갱신을 만드는 패턴, 운영체제의 자유 메모리 블록 관리 등이 대표적인 활용처다. 또한 두 포인터(two pointers) 기법, 리스트 뒤집기, 사이클 검출 같은 문제는 코딩 테스트의 단골 주제다.

대표 문제 유형

연결 리스트에 사이클이 있는지 판별할 때는 플로이드의 토끼와 거북이(Floyd's tortoise and hare) 알고리즘을 쓴다. 한 칸씩 가는 포인터와 두 칸씩 가는 포인터를 함께 전진시키면, 사이클이 있을 때 두 포인터가 반드시 만나며 추가 메모리 없이 O(n)에 판별된다. 리스트 뒤집기는 prev, current, next 세 포인터를 유지하며 연결 방향을 하나씩 반대로 돌리는 방식으로 O(n)에 해결한다. 이 두 문제는 포인터 조작 능력을 확인하는 검증 문제로 면접에서 자주 등장한다.

면접에서 이렇게 물어본다

  • 연결 리스트와 배열의 삽입, 삭제, 접근 비용을 비교하고 각각 언제 선택할지 설명해 보라.
  • 단일 연결 리스트와 이중 연결 리스트의 차이, 그리고 이중 연결이 필요한 대표 사례를 말해 보라.
  • 정렬된 연결 리스트에서 이진 탐색을 쓸 수 없는 이유는 무엇인가.
  • 연결 리스트에 사이클이 존재하는지 O(1) 공간으로 판별하는 방법을 설명해 보라.
  • LRU 캐시를 구현할 때 이중 연결 리스트가 쓰이는 이유를 설명해 보라.
자료구조 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기