Skip to main content

Command Palette

Search for a command to run...

[data structure] linked list

Published
•4 min read•View as Markdown

linked list 정의

  • 데이터를 일렬로 저장하는 자료 구조

  • 각 노드는 값이 저장된 데이터와 다음 노드를 가르키는 포인터(참조)로 구성됨

linked list와 array의 차이

array

linked list

저장 방식

메모리 상에서 연속적으로 저장된다.

메모리 상에서 각 노드가 흩어져 저장된다.

접근 속도

인덱스로 빠르게 조회할 수 있다.

head 노드부터 순차적으로 탐색해야해서 느리다

삽입과 삭제

연속적으로 저장되어 있어서, 중간 요소 삽입하거나 삭제하면 많은 요소가 이동해야 해서 중간 삽입/삭제가 느리다.

노드 포인터만 변경하면 되므로, 삽입과 삭제가 효율적이다.

크기 조정

고정 크기로 선언되므로, 크기 조정하려면 새로운 배열을 만들어 데이터를 복사해야 한다.

동적으로 크기 조정할 수 있어, 메모리 낭비가 적다.

linked list 시간복잡도

의미시간 복잡도
append맨 끝에 추가O(1)
prepend맨 앞에 추가O(1)
lookup조회O(n)
insert노드 찾아서 삽입O(n)
delete노드 찾아서 삭제O(n)

singly linked list 구현

// 새로운 노드를 생성하기 위한 클래스
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}

// 링크드 리스트를 생성하기 위한 클래스
class LinkedList {
  constructor(val) {
    this.head = {
      val,
      next: null,
    };
    this.tail = this.head;
    this.length = 1;
  }

  append(val) {
    const newNode = new Node(val);
    this.tail.next = newNode;
    this.tail = newNode;
    this.length++;
    return this;
  }

  prepend(val) {
    const newNode = new Node(val);
    newNode.next = this.head;
    this.head = newNode;
    this.length++;
    return this;
  }

  printList() {
    const array = [];
    let currentNode = this.head;

    while (currentNode) {
      array.push(currentNode.val);
      currentNode = currentNode.next;
    }

    return array;
  }

  insert(index, value) {
    // index가 0인 경우
    if (index === 0) {
      return this.prepend(value);
    }

    // index가 링크드 리스트 길이보다 큰 경우
    if (index >= this.length) {
      return this.append(value);
    }

    // index가 링크드 리스트 index의 중앙인 경우
    // 삽입할 새로운 노드 만들기
    const newNode = new Node(value);
    // 삽입하고 싶은 위치의 앞 노드 찾기
    const targetNode = this.traverseToIndex(index - 1);
    newNode.next = targetNode.next;
    targetNode.next = newNode;
    this.length++;
  }

  // 전달받은 index 노드 찾기
  // 카운트랑 index가 같으면 타겟 노드 발견!
  traverseToIndex(index) {
    let currentNode = this.head;
    let count = 0;

    while (count !== index) {
      currentNode = currentNode.next;
      count++;
    }

    return currentNode;
  }

  remove(index) {
    if (index === 0) {
      this.head = this.head.next;
      this.length--;
    } else {
      const targetNode = this.traverseToIndex(index - 1);
      targetNode.next = targetNode.next.next;
      this.length--;
    }
    return this;
  }

  reverse() {
    // 노드가 헤드노드만 존재하는 경우는 리버스 적용하지 않고 바로 헤드노드 리턴함
    if (!this.head.next) return this.head;

    // 1 -> 10 -> 5

    // 첫 번째 노드, 두 번째 노드, 세 번쨰 노드를 지정
    let first = this.head;
    let second = this.head.next;

    // 역순할 두 번째 노드가 존재할 때만 reverse 실행
    while (second) {
      let third = second.next;
      // 10 -> 1
      // reverse 하기위해서 타겟 노드의 포인터 변경
      second.next = first;
      first = second;
      second = third;
    }

    // 초기 헤드노드가 테일 노드가 되었기 때문에, 헤드노드의 포인터도 초기화가 필요
    this.tail = this.head;
    this.head.next = null;
    this.head = first;

    return this.printList();
  }
}

let myLinkedList = new LinkedList(10);
myLinkedList.prepend(1);
myLinkedList.append(5);
myLinkedList.append(16);
myLinkedList.insert(2, 99);
myLinkedList.insert(0, 88);
myLinkedList.insert(20, 8800);
myLinkedList.remove(0);

doubly linked list 구현

  • 이전 노드를 가리키는 prev 포인터가 포함되어서, 리스트를 양방향으로 탐색할 수 있다.

  • 양방향 탐색이 가능하기 때문에, singly linked list보다 삽입, 삭제가 더 편리하다.

class Node {
  constructor(val) {
    this.val = val;
    this.prev = null;
    this.next = null;
  }
}

class DoublyLinkedList {
  constructor(val) {
    this.head = {
      val,
      prev: null,
      next: null,
    };
    this.tail = this.head;
    this.length = 1;
  }

  append(val) {
    const newNode = new Node(val);
    newNode.prev = this.tail;
    this.tail.next = newNode;
    this.tail = newNode;
    this.length++;
    return this;
  }

  prepend(val) {
    const newNode = new Node(val);
    newNode.next = this.head;
    this.head.prev = newNode;
    this.head = newNode;
    this.length++;
    return this;
  }

  printList() {
    const array = [];
    let currentNode = this.head;

    while (currentNode) {
      array.push(currentNode.val);
      currentNode = currentNode.next;
    }

    return array;
  }

  insert(index, value) {
    // index가 0인 경우
    if (index === 0) {
      return this.prepend(value);
    }

    // index가 링크드 리스트 길이보다 큰 경우
    if (index >= this.length) {
      return this.append(value);
    }

    // index가 링크드 리스트 index의 중앙인 경우
    // 삽입할 새로운 노드 만들기
    const newNode = new Node(value);

    // 삽입하고 싶은 위치의 앞 노드 찾기
    const targetNode = this.traverseToIndex(index - 1);
    newNode.next = targetNode.next;
    newNode.next.prev = newNode;
    targetNode.next = newNode;
    newNode.prev = targetNode;
    this.length++;
  }

  // 전달받은 index 노드 찾기
  // 전체 순회하기
  // 카운트랑 index가 같으면 타겟 노드 발견!
  traverseToIndex(index) {
    let currentNode = this.head;
    let count = 0;

    while (count !== index) {
      currentNode = currentNode.next;
      count++;
    }

    return currentNode;
  }

  remove(index) {
    if (index === 0) {
      this.head = this.head.next;
      this.length--;
    } else {
      const targetNode = this.traverseToIndex(index - 1);
      targetNode.next = targetNode.next.next;
      targetNode.next.prev = targetNode;
      this.length--;
    }

    return this;
  }
}

let myLinkedList = new DoublyLinkedList(10);
myLinkedList.append(5);
myLinkedList.append(16);
myLinkedList.prepend(1);
myLinkedList.insert(2, 99);

More from this blog

Conductor 워크스페이스마다 .env 파일 자동 복사하기

문제를 만나다 개인 프로젝트를 진행할 때 기능을 병렬로 구현하기 위해서 Conductor를 사용하고 있다. 여러 개의 워크 스페이스를 새로 만들 때마다 환경변수를 매번 수동 복사해야 하는 게 매우 귀찮았다. 워크트리 개념이 익숙하지 않아서 처음에는 워크트리 생성 시 모든 파일을 다 가져오는 줄 알았다. 워크트리의 동작 방식을 찾아보니, git에 저장된 파일만 생성된다는 걸 새로 알게 되었다. 도움 받은 블로그 글 링크 : Git에서 다수...

Dec 28, 20251 min read

[ts-challenges] Includes / Push / Unshift / Parameters

Includes 설계 의도 튜플 타입 T를 첫 번째 요소인 Head와 나머지 요소들인 Tail로 분리해서 추론한다. Head가 제네릭 U 타입과 동일하지 않으면 Includes 타입을 Tail에 대해 재귀적으로 다시 실행하면서 다음 요소 검사하기 boolean 타입의 경우, true와 false이 모두 할당가능하니까 엄격하게 동등한지 비교하기 위해서 Equal 헬퍼함수 사용 코드 type Equal<X, Y> = (<T>() => ...

May 23, 20253 min read

Untitled Publication

37 posts