자료구조 & 알고리즘

[자료구조] Stack (스택)

seominki 2024. 8. 2. 09:30

스택이란?

어떠한 데이터의 구체적인 구현 방식은 생략하고 데이터의 추상적인 형태와 그 데이터를 다루는 방법만 정해놓은 것을 가지고 ADT (Abstract Data Type) 또는 추상 자료형이라고 한다. 그 중 스택이란 고전적인 자료구조 중 하나이다. 스택은 자바스크립트에 내장되어 있진 않지만 배열과 내장함수들을 이용하여 스택을 흉내낼 수 있다.

  • LIFO (Last In First out) 원칙 (나중에 들어온 데이터가 먼저 나감, 드럼통 안에 물건을 담는 느낌)
  • 함수 실행 콘텍스들이 쌓이는 Call Stack, 브라우저 방문 기록이 쌓이는 History Stack이 대표적
  • 여러 작업을 연달아 수행하면서 이전의 작업 내용을 저장해야 할 때 자주 사용
  • 스택이 비어있을 때 pop 을 시도 -> stack underflow, stack의 최대 크기를 초과하면 stack overflow (무한 재귀, 깊은 재귀, 비효율적 재귀)

속성

  • top: 스택에 쌓여있는 맨 위의 아이템

메소드

  • push: 스택의 맨위에 데이터 삽입
  • pop: 스택의 맨위에 데이터를 제거하고 데이터 return
  • contains: 해당 데이터가 스택안에 존재하는지 확인
  • size: 스택의 개수

사용 사례

  • 재귀 알고리즘
  • 괄호 검사 (수식이나 코드에서 괄호의 짝이 맞는지)
  • 수식 계산 (후위 표기법)
  • 실행 취소
  • 브라우저 뒤로가기

스택의 시간복잡도

  • Insertion(삽입): 스택의 맨위에 추가하기 때문 O(1)
  • delete(삭제): 최상위 데이터 삭제 O(1)
  • Access(접근): 최상위 데이터부터 순회 O(n)
  • Search(검색): 최상위 데이터 제외 O(n)

배열로 스택 구현

export default class Stack {
  constructor() {
    this.items = [];
  }

  push(item) {
    this.items.push(item);
  }

  pop() {
    return this.items.pop();
  }

  peek() {
    if (this.items.length === 0) {
      return null;
    }
    return this.items[this.items.length - 1];
  }

  getSize() {
    return this.items.length;
  }

  isEmpty() {
    return this.getSize() === 0;
  }
}

배열을 이용하지 않는 스택 구현 (Linked List)

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

class Stack {
  constructor() {
    this.top = null;
  }

  isEmpty() {
    return this.top === null;
  }

  push(data) {
    // 새로운 데이터를 받은 노드생성,
    const newNode = new Node(data);
    // 현재 최상위 노트(현재의 this.top노드) 기 새로 노드의 next가됨
    newNode.next = this.top;
    // 그리고 최상위 노드는 새로 들어온 newNode가 된다.
    this.top = newNode;
  }

  pop() {
    if (this.isEmpty()) return;
    // 최상단 노드가 pop()되면서 최상단 밑에있던 노드가 최상단 노드(this.top)가 된다.
    this.top = this.top.next;
  }

  peek() {
    if (this.isEmpty()) return;
    return this.top.data;
  }

  display() {
    if (this.isEmpty()) return;
    let curr = this.top;

    while (curr.next) {
      console.log(`${curr.data} ---> `);
      curr = curr.next;
    }
  }
}

 

재귀 함수

let company = {
  sales: [
   {name: 'John', salary: 1000},
   {name: 'Alice', salary: 1600}
  ],
  development: {
    sites: [
      {name: 'Peter', salary: 2000},
      {name: 'Alex', salary: 1800 }
    ],
    internals: [
      {name: 'Jack', salary: 1300}
    ]
  }
};

// 급여 합계를 구해주는 함수
function sumSalaries(department) {
  // array 형태일 경우
  if (Array.isArray(department)) {
    return department.reduce((prev, current) => prev + current.salary, 0); // 배열의 요소를 합함
  } else {
    let sum = 0;
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // 재귀 호출로 각 하위 부서 임직원의 급여 총합을 구함
    }
    return sum;
  }
}

alert(sumSalaries(company)); // 7700