책상 위에 책이 쌓여 있습니다. 가장 밑에 있는 책이 가장 먼저 쌓였을 겁니다. 그리고 가장 위에 있는 책이 가장 최근에 쌓은 책이겠죠? 가장 밑에 있는 책을 꺼내려면 어떻게 해야 할까요? 물론 한꺼번에 책을 들어서 꺼낼 수 있겠지만;;
책이 매우 무거워서 한 권씩만 꺼낼 수 있다고 가정해 봅시다. 그럼 가장 위에 있는 책부터 한 권씩 꺼내야만 가장 밑에 있는 책에 도달할 수 있습니다.
이것이 바로 스택(Stack)입니다. 스택은 가장 최근에 추가된 자료가 가장 먼저 제거됩니다. 이런 순서 규칙을 우리는 LIFO(Last-in first-out)이라고 부릅니다.
스택은 한 쪽 끝에서만 자료의 추가와 삭제가 일어나는 순서있는(ordered) 집합입니다. 이러한 끝을 top
그리고 그 반대편을 base
라고 합니다.
스택은 넣은 순서와 반대로 꺼내게 되는 특징이 있습니다.
💡 실생활 예시: 웹브라우저의 뒤로가기 버튼. 방문한 페이지 URL을 스택에 쌓아두고, 뒤로가기를 누르면 top에서 하나를 팝(pop)합니다.
앞서 살펴본 스택의 특징으로부터, 스택이 어떤 데이터(data)와 어떤 기능(Operations)을 가져야 할지 예상할 수 있습니다.
스택은 top에서 데이터의 입출력이 일어나므로, 이 위치(top)를 기억하고 있어야 합니다. 또한 스택의 크기(size)를 저장하고 있어야 자료의 개수를 파악하는데 도움이 될 것입니다.
스택에 핵심 기능은 자료를 넣고 빼는 것입니다. 그 외에 부가적인 기능이 추가되면 Stack의 ADT가 완성됩니다.
top
: 스택의 첫 번째 요소를 가리키는 포인터size
: 요소의 개수push(item)
: 새로운 항목을 스택의 맨 위에 추가pop()
: 스택의 맨 위 항목을 반환하고 제거topValue()
: 맨 위 요소의 복사본을 반환length()
: 스택의 요소 개수를 반환isEmpty()
: 스택이 비어 있는지 여부를 반환clear()
: 스택을 다시 초기화다음과 같이 꼭 필요한 기능들만 추상 클래스로 만들어서 구현에 독립된 기능을 보장하도록 해보겠습니다.
#pragma once
template <typename T>
class Stack {
public:
virtual ~Stack() {}
// Reinitialize the stack.
virtual void clear() = 0;
// Push "it" onto the top of the stack.
virtual bool push(T it) = 0;
// Remove and return the element at the top of the stack.
virtual T pop() = 0;
// Return a copy of the top element.
virtual T topValue() = 0;
// Return the number of elements in the stack.
virtual int length() = 0;
// Tell if the stack is empty or not.
virtual bool isEmpty() = 0;
};
#include <iostream>
#include "Stack.h"
using namespace std;
template <typename T>
class AStack : public Stack<T> {
public:
T* stackArray; // Array holding stack elements
static const int DEFAULT_SIZE = 10; // Default size
int maxSize; // 할당 크기를 위해
int top; // top의 위치
// 생성자
AStack(int size = DEFAULT_SIZE) {
stackArray = new T[size]; // 동적 할당
top = 0;
maxSize = size;
}
// 소멸자
~AStack() { delete[] stackArray; }
void clear() { top = 0; }
bool push(T it) {
if (top >= maxSize)
return false;
stackArray[top++] = it;
return true;
}
T pop() {
if (top == 0)
{
cout << "빈 스택입니다\n";
return T();
}
return stackArray[--top];
}
T topValue() { return stackArray[top - 1]; }
int length() { return top; }
bool isEmpty() { return top == 0; }
};
Linked Stack
은 Single Linked List
로 구현가능합니다. 데이터의 입출력이 한 쪽 끝에서만 이루어지고, 랜덤 엑세스를 하지 않기 때문입니다.
Linked Stack에서 pop()하는 과정을 살펴보겠습니다. 🏃♂️💨
값의 반환을 위해 top의 값
을 it
변수에 복사해놓습니다.
그리고 top이 top의 next를 가리키도록 합니다. 그리고 원래 top이 가리키던 메모리 공간은 반납해버리면 되겠죠.
it
을 반환하면 끝입니다.
Single Linked List
의 노드를 표현할 때 썼던 Link 클래스
를 그대로 사용하여 Linked Stack을 구현해 보겠습니다.
#include <iostream>
#include "Link.cpp"
#include "Stack.h"
using namespace std;
template <typename T>
class LStack : public Stack<T> {
public:
Link<T>* top;
int stackSize;
//생성자
LStack() { top = nullptr; stackSize = 0; }
//소멸자
~LStack() {
// 명시적으로 delete
while (top != nullptr) { // top이 nullptr이 될 때까지 반복
Link<T>* tmp = top;
top = top->next(); // 다음 노드로 이동
delete tmp; // 현재 노드 메모리 해제
}
}
void clear() {
// 명시적으로 delete
while (top != nullptr) {
Link<T>* tmp = top;
top = top->next();
delete tmp;
}
stackSize = 0;
}
bool push(T it) {
top = new Link<T>(it, top);
stackSize++;
return true;
}
T pop() {
if (stackSize == 0) {
cout << "스택이 비었음\n";
return T();
}
Link<T>* tmp = top;
T it = top->element();
top = top->next();
stackSize--;
delete tmp;
return it;
}
T topValue() {
if (top == nullptr) {
cout << "빈 스택입니다.\n";
return T();
}
return top->element(); }
int length() { return stackSize; }
bool isEmpty() { return stackSize == 0; }
};
✅ 요약: 스택은 LIFO 규칙을 가진 간단하지만 강력한 자료구조로, 배열 기반(고정 크기)과 연결 리스트 기반(동적 크기) 두 방식으로 구현할 수 있습니다. 🎉