study/Java

LinkedList

으녕오리 2025. 4. 4. 10:24

노드와 연결

 

배열 리스트의 단점

배열 리스트는 내부에 배열을 사용해서 데이터를 보관하고 관리한다.

배열은 필요한 배열의 크기를 미리 확보해야 한다. -> 공간 낭비 발생

앞이나 중간에 데이터를 추가하거나 삭제하는 경우 -> 데이터를 이동으로 인한 성능 저하 발생

 

노드와 연결(노드를 만들고 각 노드를 서로 연결)

낭비되는 메모리 x, 앞이나 중간에 데이터를 추가, 삭제할 때 효율적

 

  • 노드에 데이터 추가

 

코드 구현

더보기
public class Node {
    Object item;
    Node next;
    public Node(Object item) {
        this.item = item;
    }
}
  • 모든 노드 탐색하기

더보기
Node x = first;
while (x != null) {
        System.out.println(x.item);
x = x.next;
}
  • toString()을 직접 구현
더보기
public class Node {
    Object item;
    Node next;
    public Node(Object item) {
        this.item = item;
    }
}

public class Node {
    Object item;
    Node next;
    public Node(Object item) {
        this.item = item;
    }
    /*
    //IDE 생성 toString()
    @Override
    public String toString() {
    return "Node{" +
    "item=" + item +
    ", next=" + next +
    '}';
    }
    */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Node x = this;
        sb.append("[");
        while (x != null) {
            sb.append(x.item);
            if (x.next != null) {
                sb.append("->");
            }
            x = x.next;
        }
        sb.append("]");
        return sb.toString();
    }
}
  • 노드와 연결을 활용한 다양한 기능
    • 모든 노드 탐색
    • 마지막 노드 조회
    • 특정 index의 노드 조회
    • 노드에 데이터 추가
더보기
public class MyLinkedListV1 {
    private Node first;
    private int size = 0;
    public void add(Object e) {
        Node newNode = new Node(e);
        if (first == null) {
            first = newNode;
        } else {
            Node lastNode = getLastNode();
            lastNode.next = newNode;
        }
        size++;
    }
    private Node getLastNode() {
        Node x = first;
        while (x.next != null) {
            x = x.next;
        }
        return x;
    }
    public Object set(int index, Object element) {
        Node x = getNode(index);
        Object oldValue = x.item;
        x.item = element;
        return oldValue;
    }
    public Object get(int index) {
        Node node = getNode(index);
        return node.item;
    }
    private Node getNode(int index) {
        Node x = first;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    }
    public int indexOf(Object o) {
        int index = 0;
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
        return -1;
    }
    public int size() {
        return size;
    }
    @Override
    public String toString() {
        return "MyLinkedListV1{" +
                "first=" + first +
                ", size=" + size +
                '}';
    }
}
  • 정리
    • 노드는 내부에 데이터와 다음 노드에 대한 참조를 가지고 있다.
    • next 필드를 통해 참조값을 보관 -> 메모리 낭비가 발생하긴 한다.
    • 노드를 연결해서 사용하는 자료 구조로 리스트를 만듦 -> 연결 리스트

 

연결 리스트1 - 시작

 

  • 리스트(순서 O, 중복 O)의 내부에서
    • 배열을 사용 -> ArrayList
    • 노드와 연결 구조를 사용 -> LinkedLlist
연결 리스트와 빅오
더보기
public class MyLinkedListV1 {
    private Node first;
    private int size = 0;
    public void add(Object e) {
        Node newNode = new Node(e);
        if (first == null) {
            first = newNode;
        } else {
            Node lastNode = getLastNode();
            lastNode.next = newNode;
        }
        size++;
    }
    private Node getLastNode() {
        Node x = first;
        while (x.next != null) {
            x = x.next;
        }
        return x;
    }
    public Object set(int index, Object element) {
        Node x = getNode(index);
        Object oldValue = x.item;
        x.item = element;
        return oldValue;
    }
    public Object get(int index) {
        Node node = getNode(index);
        return node.item;
    }
    private Node getNode(int index) {
        Node x = first;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    }
    public int indexOf(Object o) {
        int index = 0;
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
        return -1;
    }
    public int size() {
        return size;
    }
    @Override
    public String toString() {
        return "MyLinkedListV1{" +
                "first=" + first +
                ", size=" + size +
                '}';
    }
}

 

  • Object get(int index)
    • 특정 위치에 있는 데이터를 반환
    • O(n) -> 특정 위치 찾기
  • void add(Object e)
    • 마지막에 데이터 추가
    • O(n) -> 마지막 노드 찾기
  • Object set(int index, Object element)
    • 특정 위치의 데이터를 찾아서 변경 후 기존 값 반환
    • O(n) -> 특정 위치 찾기
  • int indexOf(Object o)
    • 데이터를 검색 후 검색된 위치 반환
    • O(n) -> 모든 노드 순회

 

 

연결 리스트2 - 특정 위치에 데이터를 추가, 삭제

 

  • 첫번째 위치의 데이터 추가, 삭제 -> O(1)
  배열 리스트
index 실제 index가 존재 존재x, 가정일 뿐이고 연결된 순서를 뜻한다.
이동 여부 O(모든 데이터를 하나씩 이동) X(새로 생성한 노드의 참조만 변경)

 

  • 특정 위치에 데이터 추가 -> O(n)
    1. 새로운 노드를 생성하고, 노드가 입력될 위치의 직전 노드(prev) 찾기
    2. 신규 노드와 다음 노드를 연결, 직전 노드(prev) 다음 노드를 연결
    3. 직전 노드(prev)에 신규 노드를 연결
  • 특정 위치의 데이터 삭제 -> O(n)
    • 삭제 대상을 찾고 삭제 대상의 직전 노드(prev) 찾기
    • 직전 노드(prev)의 다음 노드를 삭제 후 노드의 다음 노드와 연결
    • 삭제 노드의 데이터를 초기화
  • 코드 구현
더보기
public class MyLinkedListV2 {
    private Node first;
    private int size = 0;
    public void add(Object e) {
        Node newNode = new Node(e);
        if (first == null) {
            first = newNode;
        } else {
            Node lastNode = getLastNode();
            lastNode.next = newNode;
        }
        size++;
    }
    private Node getLastNode() {
        Node x = first;
        while (x.next != null) {
            x = x.next;
        }
        return x;
    }
    //추가 코드
    public void add(int index, Object e) {
        Node newNode = new Node(e);
        if (index == 0) {
            newNode.next = first;
            first = newNode;
        } else {
            Node prev = getNode(index - 1);
            newNode.next = prev.next;
            prev.next = newNode;
        }
        size++;
    }
    public Object set(int index, Object element) {
        Node x = getNode(index);
        Object oldValue = x.item;
        x.item = element;
        return oldValue;
    }
    //추가 코드
    public Object remove(int index) {
        Node removeNode = getNode(index);
        Object removedItem = removeNode.item;
        if (index == 0) {
            first = removeNode.next;
        } else {
            Node prev = getNode(index - 1);
            prev.next = removeNode.next;
        }
        removeNode.item = null;
        removeNode.next = null;
        size--;
        return removedItem;
    }
    public Object get(int index) {
        Node node = getNode(index);
        return node.item;
    }
    private Node getNode(int index) {
        Node x = first;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    }
    public int indexOf(Object o) {
        int index = 0;
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
        return -1;
    }
    public int size() {
        return size;
    }
    @Override
    public String toString() {
        return "MyLinkedListV2{" +
                "first=" + first +
                ", size=" + size +
                '}';
    }
}
  • 성능 비교표

 

데이터를 조회할 일이 많다, 뒷 부분에 데이터 추가 -> 배열 리스트

앞쪽에 데이터 추가, 삭제 -> 연결 리스트

 

  • 이중 연결 리스트
    • 성능을 더 개선할 수 있다.
    • 자바가 제공하는 연결 리스트는 이중 연결 리스트이다.
    • 마지막 노드를 참조하는 변수를 가지고 있다. -> 뒤에 추가, 삭제 : O(1)

 

연결 리스트3 - 제네릭 도입

 

제네릭을 도입하면 타입 안전성이 보장된 자료 구조를 만들 수 있다.

Node는 연결 리스트 내부에서만 사용되므로 중첩 클래스로 만든다.

 

코드 구현

더보기
public class MyLinkedListV3<E> {
    private Node<E> first;
    private int size = 0;
    public void add(E e) {
        Node<E> newNode = new Node<>(e);
        if (first == null) {
            first = newNode;
        } else {
            Node<E> lastNode = getLastNode();
            lastNode.next = newNode;
        }
        size++;
    }
    private Node<E> getLastNode() {
        Node<E> x = first;
        while (x.next != null) {
            x = x.next;
        }
        return x;
    }
    public void add(int index, E e) {
        Node<E> newNode = new Node<>(e);
        if (index == 0) {
            newNode.next = first;
            first = newNode;
        } else {
            Node<E> prev = getNode(index - 1);
            newNode.next = prev.next;
            prev.next = newNode;
        }
        size++;
    }
    public E set(int index, E element) {
        Node<E> x = getNode(index);
        E oldValue = x.item;
        x.item = element;
        return oldValue;
    }
    public E remove(int index) {
        Node<E> removeNode = getNode(index);
        E removedItem = removeNode.item;
        if (index == 0) {
            first = removeNode.next;
        } else {
            Node<E> prev = getNode(index - 1);
            prev.next = removeNode.next;
        }
        removeNode.item = null;
        removeNode.next = null;
        size--;
        return removedItem;
    }
    public E get(int index) {
        Node<E> node = getNode(index);
        return node.item;
    }
    private Node<E> getNode(int index) {
        Node<E> x = first;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    }
    public int indexOf(E o) {
        int index = 0;
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
        return -1;
    }
    public int size() {
        return size;
    }
    @Override
    public String toString() {
        return "MyLinkedListV3{" +
                "first=" + first +
                ", size=" + size +
                '}';
    }
    private static class Node<E> {
        E item;
        Node<E> next;
        public Node(E item) {
            this.item = item;
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            Node<E> temp = this;
            sb.append("[");
            while (temp != null) {
                sb.append(temp.item);
                if (temp.next != null) {
                    sb.append("->");
                }
                temp = temp.next;
            }
            sb.append("]");
            return sb.toString();
        }
    }
}