연결 리스트
- 배열로 작업했을때 느리다고 판단되면 대안으로 연결 리스트를 사용할 수 있다.
 - 일차원 배열을 사용한 곳에서는 대부분 배열을 연결 리스트로 바꿀 수 있다.
 - 임의의 요소에 접근해야 할 때는 연결 리스트보다 배열이 좋다.
 - 노드(node) 라 불리는 객체가 모여 연결 리스트를 구성한다.
 - 각 노드는 객체 레퍼런스를 통해 리스트의 다른 노드와 연결된다.
 - 다른 노드를 참조하는 레퍼런스를 링크(link)라 한다.
 
동작
- 연결 리스트는 다른 요소와의 관계를 통해 원하는 요소를 참조할 수 있다.
 - 헤더라 불리는 특별한 노드를 이용해 연결 리스트의 시작을 표현한다.
 - 새 노드를 추가하려면 삽입하려는 노드의 이전 노드 링크가 새 노드를 가리키도록 바꿔야 하고 새 노드의 링크는 이전 노드가 가리키던 노드를 가리키도록 설정해야 한다.
 - 노드를 삭제하는 일은 삭제하려는 노드의 이전에 있는 노드 링크를 삭제하려는 노드가 가리키는 노드로 바꾼 다음, 삭제하려는 노드의 링크를 Null로 설정하면 노드가 삭제된다.
 
설계
- Node 클래스
 
Node 클래스는 노드의 데이터를 저장하는 element와 연결 리스트의 다음 노드 링크를 저장하는 next, 두 가지 프로퍼티를 포함한다.
function Node(element) {
  this.element = element;
  this.next = null;
}- LinkedList 클래스
 
새 노드 삽입, 기존 노드 삭제, 리스트의 특정 데이터 검색 등의 기능 제공한다. 리스트의 헤드를 나타내는 노드에 해당하는 한개의 프로퍼티를 포함한다.
연결 리스트 코드 (단방향)
// LinkedNode
class LinkedNode<T> {
  element: T;
  next: LinkedNode<T> | null;
  constructor(element: T) {
    this.element = element;
    this.next = null;
  }
}
// LinkedList
class LinkedList {
  head: LinkedNode<string>;
  constructor() {
    this.head = new LinkedNode("head");
  }
  find(item: string) {
    let currNode = this.head;
    //
    while (currNode.next && currNode.element !== item) {
      currNode = currNode.next;
    }
    if (currNode.element === item) {
      return currNode;
    }
    throw new Error("해당 노드를 찾을 수 없습니다.");
  }
  findPrevious(item: string) {
    let currNode = this.head;
    // 현재 노드 다음 element가 item과 일치할 때까지 링크를 타고 이동시킨다.
    while (currNode.next && currNode.next.element !== item) {
      currNode = currNode.next;
    }
    if (currNode.next?.element === item) {
      return currNode;
    }
    throw new Error("해당 이전 노드를 찾을 수 없습니다.");
  }
  // 어떤 노드를 추가할 것이고, 어느 노드 앞에 추가할지를 지정해야 한다.
  insert(newElement: string, item: string) {
    const newNode = new LinkedNode(newElement);
    const currentNode = this.find(item);
    newNode.next = currentNode.next;
    currentNode.next = newNode;
  }
  remove(item: string) {
    const removeNode = this.find(item);
    const previousRemoveNode = this.findPrevious(item);
    previousRemoveNode.next = removeNode.next;
  }
  // 전체 연결 리스트 보여주기
  display() {
    let currNode = this.head;
    console.log(currNode.element);
    while (currNode.next) {
      console.log(currNode.next.element);
      currNode = currNode.next;
    }
  }
}연결 리스트 코드 (양방향)
연결 리스트를 양방향으로 구현한다면 역방향으로 노드 탐색과 삭제할때 편리하지만 노드를 추가할 때는 더 많은 작업을 수행 해야 한다.
// LinkedNode
class LinkedNode<T> {
  element: T;
  next: LinkedNode<T> | null;
  previous: LinkedNode<T> | null;
  constructor(element: T) {
    this.element = element;
    this.next = null;
    this.previous = null // 양방향을 위한 이전 노드 가리키기
  }
}
class LinkedList {
  head: LinkedNode<string>;
  tail: LinkedNode<string>;
  constructor() {
    this.head = new LinkedNode("head");
    this.tail = this.head; // 꼬리 부분도 저장해 둔다.
  }
  // (...) 이전 코드
  // 어떤 노드를 추가할 것이고, 어느 노드 앞에 추가할지를 지정해야 한다.
  insert(newElement: string, item: string) {
    const newNode = new LinkedNode(newElement);
    const currentNode = this.find(item);
    newNode.next = currentNode.next;
    newNode.previous = currentNode; // 추가
    currentNode.next = newNode;
    this.tail = newNode;
  }
  remove(item: string) {
    const removeNode = this.find(item);
    const previousRemoveNode = removeNode.previous;
    const nextRemoveNode = removeNode.next;
    // const previousRemoveNode = this.findPrevious(item);
    // previousRemoveNode.next = removeNode.next;
    if (previousRemoveNode) {
      previousRemoveNode.next = removeNode.next;
    }
    if (nextRemoveNode) {
      nextRemoveNode.previous = previousRemoveNode;
    }
    if (!nextRemoveNode && previousRemoveNode) {
      this.tail = previousRemoveNode;
    }
    removeNode.next = null;
    removeNode.previous = null;
  }
  displayReverse() {
    let currNode = this.tail;
    console.log(currNode.element);
    while (currNode.previous) {
      console.log(currNode.previous.element);
      currNode = currNode.previous;
    }
  }
}연결 리스트 코드 (순환형)
순환형 연결 리스트에서는 헤드의 next 프로퍼티가 자신을 가리킨다는 것입니다. 순환형 연결 리스트에서는 노드의 끝을 지나 계속 탐색하면 결국 역받향에 있는 노드로 이동 할 수 있습니다.
// 순환과 양방향의 합성
class LinkedList {
  head: LinkedNode<string>;
  tail: LinkedNode<string>;
  constructor() {
    this.head = new LinkedNode("head");
    this.tail = this.head;
    this.tail.next = this.head;
    this.head.previous = this.tail;
  }
  find(item: string) {
    let currNode = this.head;
    //
    while (currNode.next && currNode.element !== item) {
      currNode = currNode.next;
    }
    if (currNode.element === item) {
      return currNode;
    }
    throw new Error("해당 노드를 찾을 수 없습니다.");
  }
  findPrevious(item: string) {
    let currNode = this.head;
    // 현재 노드 다음 element가 item과 일치할 때까지 링크를 타고 이동시킨다.
    while (currNode.next && currNode.next.element !== item) {
      currNode = currNode.next;
    }
    if (currNode.next?.element === item) {
      return currNode;
    }
    throw new Error("해당 이전 노드를 찾을 수 없습니다.");
  }
  // 어떤 노드를 추가할 것이고, 어느 노드 앞에 추가할지를 지정해야 한다.
  insert(newElement: string, item: string) {
    const newNode = new LinkedNode(newElement);
    const currentNode = this.find(item);
    newNode.next = currentNode.next;
    newNode.previous = currentNode; // 추가
    currentNode.next = newNode;
    this.head.previous = newNode;
    this.tail = newNode;
  }
  remove(item: string) {
    const removeNode = this.find(item);
    const previousRemoveNode = removeNode.previous;
    const nextRemoveNode = removeNode.next;
    // const previousRemoveNode = this.findPrevious(item);
    // previousRemoveNode.next = removeNode.next;
    if (previousRemoveNode) {
      previousRemoveNode.next = removeNode.next;
    }
    if (nextRemoveNode) {
      nextRemoveNode.previous = previousRemoveNode;
    }
    if (!nextRemoveNode && previousRemoveNode) {
      this.head.previous = previousRemoveNode;
      this.tail = previousRemoveNode;
    }
    removeNode.next = null;
    removeNode.previous = null;
  }
  // 전체 연결 리스트 보여주기
  display() {
    let currNode = this.head;
    console.log(currNode.element);
    while (currNode.next && !(currNode.next.element === "head")) {
      console.log(currNode.next.element);
      currNode = currNode.next;
    }
  }
  displayReverse() {
    let currNode = this.tail;
    console.log(currNode.element);
    while (currNode.previous && !(currNode.previous === this.tail)) {
      console.log(currNode.previous.element);
      currNode = currNode.previous;
    }
  }
}수정 본
class LinkedList {
  constructor(head) {
    this.head = new LinkedNode(head);
    this.tail = this.head;
    this.tail.next = this.head;
    this.head.previous = this.tail;
  }
  find(id) {
    let currNode = this.head;
    while (
      currNode.next &&
      currNode.element.id !== id &&
      currNode.next !== this.head
    ) {
      currNode = currNode.next;
    }
    if (currNode.element.id === id) {
      return currNode;
    }
    return null;
  }
  // 어떤 노드를 추가할 것이고, 어느 노드 앞에 추가할지를 지정해야 한다.
  insert(newElement, id) {
    const newNode = new LinkedNode(newElement);
    const currentNode = this.find(id);
    if (currentNode) {
      newNode.next = currentNode.next;
      newNode.previous = currentNode; // 추가
      currentNode.next = newNode;
      this.head.previous = newNode;
      this.tail = newNode;
      return true;
    }
    return false;
  }
  insertLast(newElement) {
    const newNode = new LinkedNode(newElement);
    // 꼬리 노드의 다음 노드로 연결합니다.
    // 새로운 노드의 다음 노드로는 head, 이전 노드로는 이전 tail이 되고
    // 자신이 tail 이 됩니다.
    newNode.next = this.head;
    newNode.previous = this.tail;
    this.head.previous = newNode;
    this.tail.next = newNode;
    this.tail = newNode;
  }
  remove(id) {
    let removeNode = this.find(id);
    if (removeNode) {
      const previousRemoveNode = removeNode.previous;
      const nextRemoveNode = removeNode.next;
      // 지울 노드의 이전 노드가 있다면 이전 노드의 다음 노드를 지울 노드 다음 노드(nextRemoveNode)와 연결해준다.
      if (previousRemoveNode) {
        previousRemoveNode.next = nextRemoveNode;
      }
      // 지울 노드의 다음 노드가 있다면 다음 노드의 이전 노드를 지울 노드 이전 노드(previousRemoveNode)와 연결해준다.
      if (nextRemoveNode) {
        nextRemoveNode.previous = previousRemoveNode;
      }
      if (removeNode === this.tail && removeNode === this.head) {
        // 노드가 하나라면 전부 null 처리 한다.
        this.head = null;
        this.tail = null;
      } else if (removeNode === this.head) {
        this.head = nextRemoveNode;
      } else if (removeNode === this.tail) {
        this.tail = previousRemoveNode;
      }
      removeNode.next = null;
      removeNode.previous = null;
      removeNode = null;
      return true;
    }
    return false;
  }
  // 노드를 list 형식으로 뽑아 볼때 사용
  getList() {
    const result = [];
    let currNode = this.head;
    if (currNode !== null) {
      result.push(currNode.element);
      while (currNode.next && currNode.next !== this.head) {
        currNode = currNode.next;
        result.push(currNode.element);
      }
    }
    return result;
  }
  // 전체 연결 리스트 보여주기
  display() {
    let currNode = this.head;
    console.log(currNode.next.element);
    while (currNode.next && currNode.next !== this.head) {
      console.log(currNode.next.element);
      currNode = currNode.next;
    }
  }
}