Data Structure

[기초 자료구조] Linked List

noahkim_ 2023. 11. 8. 15:30

1. Linked List

  • 각 요소들을 연쇄적으로 연결한 선형 자료구조
  • ✅ 각 요소마다 이전/다음 노드의 포인터를 보유함
  • ✅ Head 포인터: 맨 처음 요소를 가리키는 포인터 (링크드리스트 마다 가지고 있음)

 

Node

  • 각 요소를 표현하는 자료구조
  •  구성: 값, next 포인터 (다음 노드를 가리키는 포인터)

 

장점

구분 설명 장점
동적 할당 크기가 동적으로 할당됨 유연한 메모리 사용 가능
메모리 비연속적 메모리 연속적이지 않고 흩어져 저장됨
✅ 다른 요소에 독립적으로 존재함
✅ 각 노드는 next 포인터를 가짐
요소 추가/삭제 시 전체 재배치 불필요
효율적 연산 배열처럼 연속된 메모리를 고려하지 않아도 됨 삽입/삭제 등의 연산이 효율적임

 

단점

구분 설명 단점
추가 메모리 비용 각 노드가 데이터를 저장할 뿐만 아니라 next 포인터도 가져야 함 배열보다 메모리 사용량이 더 많음
조회 성능 Random Access 불가능 조회 속도가 느림
복잡성 포인터 연산을 통해 연산 수행 (노드 연결/해제 등) 배열보다 구현과 관리가 복잡함

 

2. 연산

연산 설명 시간복잡도
search 순차적으로 탐색 (from Head pointer) O(n)
insert - 첫번째 위치
- 중간 위치 (탐색필요)
O(1)
O(n)
delete - 첫번째 위치
- 중간 위치 (탐색필요)
O(1)
O(n)

 

3. 종류

Singly LinkedList

  • 각 노드에 값과 next 포인터를 가지는 링크드리스트

 

Doubly LinkedList

  • 각 노드에 값과 prev, next 포인터를 가지는 링크드리스트
  • ✅ 빠른 연산 (조회, 삽입, 삭제)

 

Circularly LinkedList

  • 원형 형태의 링크드리스트 (각 노드에 값과 prev, next 포인터를 가짐)
  •  맨 첫번째 노드의 prev: 맨 마지막 노드를 가리킵니다.
  •  맨 마지막 노드의 next: 맨 첫번째 노드를 가리킵니다.

 

4. 사용 사례

종류  특징 활용
Singly LinkedList Head, Tail 포인터 보유  Stack & Queue
 Recursion Algorithm
Doubly LinkedList 연산이 빈번한 경우 (삽입, 삭제) ✅ 브라우저 앞뒤로 가기
 LRU 캐시, 작업 큐
Circularly LinkedList 순환하는 경우  오디오 스트리밍 (무한반복)
 프로세스 스케줄링 (순환 관리)
 로드밸런싱 (작업 분배)

 

5. 구현

Singly LinkedList

더보기
public class MySinglyLinkedList<V> {

    class Node {
        final V data;
        Node next;

        public Node(V data) {
            this.data = data;
        }
    }

    private Node head, tail;
    private int cnt;

    public Node searchByIdx(int idx) {
        rangeCheck(idx);
        emptyCheck();

        Node target = head;

        for (int i = 0; i < idx; i++) target = target.next;

        return target;
    }

    public Node searchByVal(V val) {
        emptyCheck();

        Node target = head;

        while (target != null && !target.data.equals(val)) target = target.next;

        return target;
    }

    public void add(int idx, V val) {
        rangeCheckForAdd(idx);

        if (idx == 0) {
            addFirst(val);
            return;
        } else if (idx == cnt) {
            addLast(val);
            return;
        }

        Node newNode = new Node(val);
        Node prev = searchByIdx(idx-1);

        newNode.next = prev.next;
        prev.next = newNode;

        cnt++;
    }

    public void remove(V val) {
        emptyCheck();

        if (head.data.equals(val)) {
            removeFirst();
            return;
        } else if (tail.data.equals(val)) {
            removeLast();
            return;
        }

        Node target = head, prev = null;
        while (target != null && !target.data.equals(val)) {
            prev = target;
            target = target.next;
        }

        if (target == null) throw new IllegalStateException("Node with the specified val not found");

        prev.next = target.next;

        cnt--;
    }

    private void addFirst(V val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            newNode.next = head;
            head = newNode;
        } else {
            tail = head = newNode;
        }

        cnt++;
    }

    private void addLast(V val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            tail.next = newNode;
            tail = tail.next;
        } else {
            head = tail = newNode;
        }

        cnt++;
    }

    private void removeFirst() {
        if (cnt > 1) {
            head = head.next;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void removeLast() {
        if (cnt > 1) {
            int tailPrevIdx = cnt-2;
            Node tailPrev = searchByIdx(tailPrevIdx);
            tailPrev.next = null;
            tail = tailPrev;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void rangeCheck(int idx) {
        if (idx < 0 || idx >= cnt) throw new IllegalStateException("out of bounds");
    }

    private void rangeCheckForAdd(int idx) {
        if (idx < 0 || idx > cnt) throw new IllegalStateException("out of bounds");
    }

    private void emptyCheck() {
        if (head == null) throw new IllegalStateException("data not exist");
    }
}

 

Doubly LinkedList

더보기
public class MyDoublyLinkedList2<T> {

    class Node {
        T data;
        Node prev, next;

        public Node (T data) {
            this.data = data;
        }
    }

    private Node head, tail;
    private int cnt;

    public Node searchByIdx(int idx) {
        rangeCheck(idx);
        emptyCheck();

        Node target = head;

        for (int i = 0; i < idx; i++) target = target.next;

        return target;
    }

    public Node searchByVal(T val) {
        emptyCheck();

        Node target = head;

        while (target != null && !target.data.equals(val)) target = target.next;

        return target;
    }

    public void add(int idx, T val) {
        rangeCheckForAdd(idx);

        if (idx == 0) {
            addFirst(val);
            return;
        } else if (idx == cnt) {
            addLast(val);
            return;
        }

        Node newNode = new Node(val);
        Node prev = searchByIdx(idx);

        newNode.prev = prev;
        newNode.next = prev.next;
        prev.next = newNode;
        prev.next.prev = newNode;

        cnt++;
    }

    public void remove(T val) {
        emptyCheck();

        if (head.data.equals(val)) {
            removeFirst();
            return;
        } else if (tail.data.equals(val)) {
            removeLast();
            return;
        }

        Node target = searchByVal(val);
        if (target == null) throw new IllegalStateException("Node with the specified val not found");

        target.prev.next = target.next;
        target.next.prev = target.prev;

        cnt--;
    }

    private void addFirst(T val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            newNode.next = head;
            head.prev = newNode;
            head = newNode;
        } else {
            head = tail = newNode;
        }

        cnt++;
    }

    private void addLast(T val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            newNode.prev = tail;
            tail.next = newNode;
            tail = newNode;
        } else {
            head = tail = newNode;
        }

        cnt++;
    }

    private void removeFirst() {
        if (cnt > 1) {
            head = head.next;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void removeLast() {
        if (cnt > 1) {
            tail = tail.prev;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void rangeCheck(int idx) {
        if (idx < 0 || idx >= cnt) throw new IllegalStateException();
    }

    private void rangeCheckForAdd(int idx) {
        if (idx < 0 || idx > cnt) throw new IllegalStateException("out of bounds");
    }

    private void emptyCheck() {
        if (head == null) throw new IllegalStateException("data not exist");
    }
}

 

Circularly LinkedList

더보기
public class MyCircularlyLinkedList<T> {

    class Node {
        final T data;
        Node prev, next;

        public Node(T data) {
            this.data = data;
        }
    }

    private Node head, tail;
    private int cnt;

    public Node searchByIdx(int idx) {
        emptyCheck();
        rangeCheck(idx);

        Node target = head;

        for (int i = 0; i < idx; i++) target = target.next;

        return target;
    }

    public Node searchByVal(T val) {
        emptyCheck();

        Node target = head;
        do {
            if (target.data.equals(val)) return target;

            target = target.next;
        } while (target != head);

        throw new IllegalStateException();
    }

    public void add(int idx, T val) {
        rangeCheckForAdd(idx);

        if (cnt == 0) {
            addFirst(val);
            return;
        } else if (cnt == idx) {
            addLast(val);
            return;
        }

        Node newNode = new Node(val);
        Node prev = searchByIdx(idx);

        newNode.prev = prev;
        newNode.next = prev.next;
        prev.next.prev = newNode;
        prev.next = newNode;

        cnt++;
    }

    public void remove(T val) {
        emptyCheck();

        if (head.data.equals(val)) {
            removeFirst();
            return;
        } else if (tail.data.equals(val)) {
            removeLast();
            return;
        }

        Node target = searchByVal(val);
        if (target == null) throw new IllegalStateException("Node with the specified val not found");

        target.prev.next = target.next;
        target.next.prev = target.prev;

        cnt--;
    }

    private void addFirst(T val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            newNode.next = head;
            newNode.prev = tail;
            head.prev = newNode;
            tail.next = newNode;
            head = head.prev;
        } else {
            head = tail = newNode;
            head.next = head.prev = head;
        }

        cnt++;
    }

    private void addLast(T val) {
        Node newNode = new Node(val);

        if (cnt > 0) {
            newNode.prev = tail;
            newNode.next = head;
            tail.next = newNode;
            head.prev = newNode;
            tail = tail.next;
        } else {
            head = tail = newNode;
            head.next = head.prev = head;
        }

        cnt++;
    }

    private void removeFirst() {
        if (cnt > 1) {
            head = head.next;
            head.prev = tail;
            tail.next = head;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void removeLast() {
        if (cnt > 1) {
            tail = tail.prev;
            tail.next = head;
            head.prev = tail;
        } else {
            head = tail = null;
        }

        cnt--;
    }

    private void rangeCheck(int idx) {
        if (idx < 0 || idx >= cnt) throw new IllegalStateException();
    }

    private void rangeCheckForAdd(int idx) {
        if (idx < 0 || idx > cnt) throw new IllegalStateException();
    }

    private void emptyCheck() {
        if (head == null) throw new IllegalStateException();
    }
}

 

 

출처