CS/자료구조

자료구조 - 링크드 리스트(Linked List)

고코모옹 2021. 6. 30. 23:54

- 링크드 리스트 구조

  • 연결 리스트
  • 떨어진 곳에 존재하는 데이터를 화살표로 연결해서 관리하는 데이터 구조
  • 배열의 미리 공간을 지정해야 하는 단점을 극복한 자료구조

 

- 링크드 리스트 기본 구조와 용어

  • 노드(Node): 데이터 저장 단위(데이터값, 포인터)로 구성
  • 포인터(Pointer): 각 노드 안에서, 다음이나 이전의 노드와의 연결 정보를 가지고 있는 공간

단일 연결 리스트( 출처: https://en.wikipedia.org/wiki/Linked_list )

 

- Linked List 장단점

  • 장점
    • 미리 데이터 공간을 할당하지 않아도 됨

  • 단점
    • 연결을 위한 별도 데이터 공간이 필요하므로 저장공간 효율이 높지 않음
    • 연결 정보를 찾는 시간이 필요하므로 접근 속도가 느림
    • 중간 데이터 삭제 시, 앞뒤 데이터의 연결을 재구성해야 하는 부가적인 작업 필요

 

- 자바스크립트로 Linked List 구현

  • Linked List, Node 구현
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
  }

  function Node(data, next=null) {
    this.data = data;
    this.next = next;
  }

  return LinkedList;

})();

 

  • Node 추가
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
  }

  function Node(data, next=null) {
    this.data = data;
    this.next = next;
  }

  // 노드 추가
  LinkedList.prototype.add = function(data) {
    let node = new Node(data);
    let curNode = this.head;

    // 노드가 없을 경우, 해당 노드를 head에 추가
    if( !curNode ) {
      this.head = node;
    } else {
      // 마지막 노드 추적
      while(curNode.next) {
        curNode = curNode.next;
      }

      // 마지막 노드의 next에 해당 노드 추가
      curNode.next = node;
    }     
  }

  return LinkedList;

})();

 

  • Node 삭제
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
  }

  function Node(data, next=null) {
    this.data = data;
    this.next = next;
  }

  /**
   *  노드 삭제
   *  1. head 삭제
   *  2. 마지막 노드 삭제
   *  3. 중간 노드 삭제
   */
  LinkedList.prototype.remove = function(data) {
    let curNode = this.head;    
    if( !curNode ) return;

    // head를 삭제하는 경우
    if( curNode.data === data ) {
      this.head = this.head.next; // 두번째 노드를 head로 변경

    // 그 외의 노드를 삭제하는 경우  
    } else {
      while(curNode.next) {
        // 다음 노드가 삭제할 노드일 경우
        if( curNode.next.data === data ) {
          curNode.next = curNode.next.next || null;    // 삭제할 노드의 다음노드를 연결
          break;
        }

        curNode = curNode.next;
      }
    }
  }

  return LinkedList;

})();

 

  • 최종 코드
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
  }

  function Node(data, next=null) {
    this.data = data;
    this.next = next;
  }

  // 노드 추가
  LinkedList.prototype.add = function(data) {
    let node = new Node(data);
    let curNode = this.head;

    // 노드가 없을 경우, 해당 노드를 head에 추가
    if( !curNode ) {
      this.head = node;
    } else {
      // 마지막 노드 추적
      while(curNode.next) {
        curNode = curNode.next;
      }

      // 마지막 노드의 next에 해당 노드 추가
      curNode.next = node;
    }     
  }

  /**
   *  노드 삭제
   *  1. head 삭제
   *  2. 마지막 노드 삭제
   *  3. 중간 노드 삭제
   */
  LinkedList.prototype.remove = function(data) {
    let curNode = this.head;    
    if( !curNode ) return;

    // head를 삭제하는 경우
    if( curNode.data === data ) {
      this.head = this.head.next; // 두번째 노드를 head로 변경

    // 그 외의 노드를 삭제하는 경우  
    } else {
      while(curNode.next) {
        // 다음 노드가 삭제할 노드일 경우
        if( curNode.next.data === data ) {
          curNode.next = curNode.next.next || null;    // 삭제할 노드의 다음노드를 연결
          break;
        }

        curNode = curNode.next;
      }
    }
  }

  // 노드 검색
  LinkedList.prototype.search = function(data) {
    let curNode = this.head;

    while(curNode) {
      if( curNode.data === data ) {
        return curNode;
      }

      curNode = curNode.next;
    }

    return false;
  }

  LinkedList.prototype.print = function() {
    let node = this.head;

    while(node) {
      console.log(node.data);
      node = node.next;
    }
  }

  return LinkedList;

})();

 

  • 테스트
const linkedList = new LinkedList();

linkedList.add(1);    // 1추가
linkedList.add(2);    // 2추가
linkedList.add(3);    // 3추가
linkedList.add(4);    // 4추가
linkedList.add(5);    // 5추가
linkedList.remove(3);    // 3제거
linkedList.remove(1);    // 1제거(head)
linkedList.print();    // 2, 4, 5 출력

- 더블 링크드 리스트(Double Linked List)

  • 이중 연결 리스트
  • 양방향으로 연결되어 있어서 노드 탐색이 양쪽으로 가능

이중 연결 리스트( 출처: https://en.wikipedia.org/wiki/Linked_list )

  • Linked List, Node 구현
    • Linked List에 tail property 추가
    • Node에 prev property 추가
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
    this.tail = null;
  }

  function Node(data, prev=null, next=null) {    
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  return LinkedList;

})();

 

  • Node 추가
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
    this.tail = null;
  }

  function Node(data, prev=null, next=null) {    
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  LinkedList.prototype.add = function(data) {
    let node = new Node(data);
    let curNode = this.head;
    this.tail = node;    // tail에 해당 노드 추가

    if( !curNode ) {
      this.head = node;      
    } else {
      while(curNode.next) {
        curNode = curNode.next;        
      }

      curNode.next = node;
      node.prev = curNode;    // 해당 노드의 이전에 현재 노드 매핑
    }     
  }

  return LinkedList;

})();

 

  • 특정 노드 앞 또는 뒤에 노드 추가
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
    this.tail = null;
  }

  function Node(data, prev=null, next=null) {    
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  LinkedList.prototype.insert_before = function(data, before_data) {
    let node = new Node(data);

    if( !this.head ) {
      this.head = node;
      this.tail = node;
      return true;
    } else {
      let curNode = this.tail;

      while(curNode.data !== before_data) {
        curNode = curNode.prev;

        // before_data가 없는 경우 false return
        if( !curNode ) return false;
      }

      // before_data의 바로 앞의 노드
      let beforeNode = curNode.prev;

      beforeNode.next = node; // 앞의 노드의 next를 추가할 노드로 변경
      curNode.prev = node;  // 현재 노드의 prev를 추가할 노드로 변경
      node.prev = beforeNode; // 추가할 노드의 prev는 before_data의 앞 노드
      node.next = curNode;  //  추가할 노드의 next는 현재 노드

      return true;
    }
  }

  LinkedList.prototype.insert_after = function(data, after_data) {
    let node = new Node(data);

    if( !this.head ) {
      this.head = node;
      this.tail = node;
      return true;
    } else {
      let curNode = this.head;

      while(curNode.data !== after_data) {
        curNode = curNode.next;

        // after_data가 없는 경우 false return
        if( !curNode ) return false;
      }

      // after_data의 바로 뒤의 노드
      let afterNode = curNode.next;

      afterNode.prev = node; // after_data 뒤의 노드의 prev를 추가할 노드로 변경
      curNode.next = node;  // 현재 노드의 next를 추가할 노드로 변경
      node.prev = curNode; // 추가할 노드의 prev는 현재 노드
      node.next = afterNode;  //  추가할 노드의 next는 after_data의 뒤의 노드

      return true;
    }
  }

  return LinkedList;

})();

 

  • 앞 또는 뒤에서 노드 검색
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
    this.tail = null;
  }

  function Node(data, prev=null, next=null) {    
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  LinkedList.prototype.search_from_head = function(data) {
    let curNode = this.head;

    while(curNode) {
      if( curNode.data === data ) {
        return curNode;
      }

      curNode = curNode.next;
    }

    return false;
  }

  LinkedList.prototype.search_from_tail = function(data) {
    let curNode = this.tail;

    while(curNode) {
      if( curNode.data === data ) {
        return curNode;
      }

      curNode = curNode.prev;
    }

    return false;
  }

  return LinkedList;

})();

 

  • 최종 코드
const LinkedList = (function() {
  function LinkedList() {
    this.head = null;
    this.tail = null;
  }

  function Node(data, prev=null, next=null) {    
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  LinkedList.prototype.add = function(data) {
    let node = new Node(data);
    let curNode = this.head;
    this.tail = node;

    if( !curNode ) {
      this.head = node;      
    } else {
      while(curNode.next) {
        curNode = curNode.next;        
      }

      curNode.next = node;
      node.prev = curNode;
    }     
  }

  LinkedList.prototype.search_from_head = function(data) {
    let curNode = this.head;

    while(curNode) {
      if( curNode.data === data ) {
        return curNode;
      }

      curNode = curNode.next;
    }

    return false;
  }

  LinkedList.prototype.search_from_tail = function(data) {
    let curNode = this.tail;

    while(curNode) {
      if( curNode.data === data ) {
        return curNode;
      }

      curNode = curNode.prev;
    }

    return false;
  }

  LinkedList.prototype.insert_before = function(data, before_data) {
    let node = new Node(data);

    if( !this.head ) {
      this.head = node;
      this.tail = node;
      return true;
    } else {
      let curNode = this.tail;

      while(curNode.data !== before_data) {
        curNode = curNode.prev;

        // before_data가 없는 경우 false return
        if( !curNode ) return false;
      }

      // before_data의 바로 앞의 노드
      let beforeNode = curNode.prev;

      beforeNode.next = node; // 앞의 노드의 next를 추가할 노드로 변경
      curNode.prev = node;  // 현재 노드의 prev를 추가할 노드로 변경
      node.prev = beforeNode; // 추가할 노드의 prev는 before_data의 앞 노드
      node.next = curNode;  //  추가할 노드의 next는 현재 노드

      return true;
    }
  }

  LinkedList.prototype.insert_after = function(data, after_data) {
    let node = new Node(data);

    if( !this.head ) {
      this.head = node;
      this.tail = node;
      return true;
    } else {
      let curNode = this.head;

      while(curNode.data !== after_data) {
        curNode = curNode.next;

        // after_data가 없는 경우 false return
        if( !curNode ) return false;
      }

      // after_data의 바로 뒤의 노드
      let afterNode = curNode.next;

      afterNode.prev = node; // after_data 뒤의 노드의 prev를 추가할 노드로 변경
      curNode.next = node;  // 현재 노드의 next를 추가할 노드로 변경
      node.prev = curNode; // 추가할 노드의 prev는 현재 노드
      node.next = afterNode;  //  추가할 노드의 next는 after_data의 뒤의 노드

      return true;
    }
  }

  LinkedList.prototype.print = function() {
    let node = this.head;

    while(node) {
      console.log(node.data);
      node = node.next;
    }
  }

  return LinkedList;

})();

 

  • 테스트
const linkedList = new LinkedList();

linkedList.add(1);    // 1추가
linkedList.add(2);    // 2추가
linkedList.add(3);    // 3추가
linkedList.add(4);    // 4추가
linkedList.add(5);    // 5추가

linkedList.insert_before(3.5,4); // 4앞에 3.5추가  
linkedList.insert_after(2.5,2); // 2뒤에 2.5추가  
linkedList.print(); // 1, 2, 2.5, 3, 3.5, 4, 5 출력