한쪽 끝에서 자료를 넣거나 뺼 수 있는, 데이터를 제한적으로 접근할 수 있는 구조
스택(Stack)은 컴퓨터 과학에서 매우 중요한 자료구조 중 하나다. 스택은 항목들이 쌓여 있는 구조를 뜻하며, LIFO(Last-In, First-Out) 원칙을 따른다. 이 말은, 가장 마지막에 스택에 추가된 항목이 가장 먼저 제거된다는 뜻이다.
스택은 일상생활에서도 쉽게 볼 수 있다. 예를 들어, 접시를 쌓아두는 곳을 생각해보자. 새로운 접시는 스택의 맨 위에 놓이며, 사용할 때는 맨 위의 접시부터 꺼내게 된다.
스택은 다음과 같은 기본 연산을 제공한다.
스택은 여러가지 문제를 해결하는 데 유용하게 사용된다. 함수 호출 스택, 실행 취소 스택, 메모리 관리 등 다양한 컴퓨팅 문제에 적용되며, 괄호 짝 맞추기, 히스토리 관리(웹 브라우저의 앞/뒤 버튼), 트리 또는 그래프 데이터 구조의 깊이 우선 탐색(DFS) 등 다양한 알고리즘에도 사용된다.
큐는 FIFO, 스택은 LIFO를 쓰는것이 차이가 있다.
스택은 다양한 프로그래밍 시나리오와 알고리즘에서 중요하게 활용됩니다. 몇 가지 주요한 사용 예시는 다음과 같다.
위와 같은 방식으로 스택은 다양한 문제를 풀거나 특정 작업을 수행하는 데 유용한 도구로 활용된다.
# 스택 생성
stack = []
# 스택에 요소 추가 (push 연산)
stack.append('apple')
stack.append('banana')
stack.append('cherry')
print(stack) # 출력: ['apple', 'banana', 'cherry']
# 스택에서 요소 제거 (pop 연산)
item = stack.pop()
print(item) # 출력: 'cherry'
print(stack) # 출력: ['apple', 'banana']
이 예제에서는, 'cherry'가 스택에 마지막에 추가되었으므로 'cherry'가 먼저 제거된다. 이러한 행동은 스택의 LIFO(Last In, First Out) 원칙을 따르는 것이다.
위와 같은 방식으로 파이썬의 기본 데이터 타입을 활용해 스택을 간단하게 구현하고 활용할 수 있다. 이 외에도 파이썬 표준 라이브러리의 collections.deque나 queue.LifoQueue 등을 이용하여 스택을 구현할 수도 있다.
def recursive(data):
if data < 0:
print('ended')
else:
print(data)
recursive(data - 1)
print('returned', data)
이 코드는 재귀적(recursive) 함수를 사용하여 입력된 숫자부터 0까지 카운트다운하는 간단한 프로그램이다.
재귀적 함수는 함수가 자신을 다시 호출하는 것을 의미한다. 이 경우에는 recursive 함수는 자신을 호출하면서 입력값 data를 1씩 감소시킨다.
함수의 동작은 다음과 같다.
이를 통해 재귀의 개념과 스택의 원칙(Last-In, First-Out)을 쉽게 이해할 수 있다. 마지막으로 호출된 재귀 함수가 가장 먼저 종료되고, 이후에는 이전에 호출된 함수 순서대로 종료되는 것을 볼 수 있다.
예를 들어, 이 함수에 3을 입력하면 다음과 같이 출력된다.
3
2
1
0
ended
returned 0
returned 1
returned 2
returned 3
즉, 재귀 호출을 통해 3부터 0까지 카운트다운하고, 그 후에는 함수 호출이 이전 순서대로 종료되면서 'returned' 메시지를 출력한다.
하지만, ended에서 끝나지 않고, returned 3까지 출력되는 이유는 'recursive'함수가 재귀적으로 동작하기 때문이다.
함수는 입력값 data가 0보다 작을 때까지 자신을 계속 호출하며, 이 때 data는 매번 1씩 감소한다.
3을 입력값으로 주었을 때 함수는 아래와 같이 동작한다.
하지만 이제 함수의 실행이 종료되는 것이 아니다. 아직 실행이 종료되지 않은 recursive 함수 호출이 스택에 남아 있기 때문이다. 이 때 스택의 특성(Last In, First Out)이 작용하여 가장 마지막에 호출된 recursive 함수부터 종료가 시작된다.
따라서 이 코드는 data 값이 감소하면서 함수를 호출하고, 이후에 함수 호출이 종료될 때 각 data 값을 사용하여 'returned' 메시지를 출력한다. 이는 재귀와 스택의 동작 원리를 보여주는 좋은 예시다.
리스트의 append() 메소드는 스택의 push 연산에 해당하며, pop() 메소드는 스택의 pop 연산에 해당한다.
# 스택을 생성하고 초기화합니다.
stack = []
# 스택에 요소를 push합니다.
stack.append("A")
stack.append("B")
stack.append("C")
print(stack) # 출력: ['A', 'B', 'C']
# 스택에서 요소를 pop합니다.
print(stack.pop()) # 출력: 'C'
print(stack) # 출력: ['A', 'B']
위의 코드에서, append() 메소드는 리스트의 끝에 요소를 추가하며, pop() 메소드는 리스트의 마지막 요소를 제거하고 반환합니다. 이러한 메소드들을 사용하여 파이썬 리스트를 스택처럼 사용할 수 있다.
스택의 특징인 LIFO(Last In, First Out) 원칙에 따라, 가장 마지막에 추가된 요소 'C'가 가장 먼저 제거되고 반환되었다.
stack_list = list()
def push(data):
stack_list.append(data)
def pop():
data = stack_list[-1]
del stack_list[-1]
return data
for index in range(10):
push(index) #0번부터 9번까지 데이터가 들어간다.
pop() #9가 출력된다.
스택 자료구조는 다양한 문맥에서 여러 가지 장점을 제공합니다. 몇 가지 주요 장점은 다음과 같다.
이런 장점들 덕분에 스택은 많은 컴퓨팅 문제를 해결하는 데 유용하게 사용된다.
스택이 유용한 도구이긴 하지만, 몇 가지 단점도 존재한다.
이러한 단점들은 스택이 적합하지 않은 일부 사용 사례를 나타냅니다. 따라서, 스택을 사용할 때는 이러한 단점을 고려해야 합니다. 다른 데이터 구조(예: 큐, 링크드 리스트, 해시 테이블 등)가 더 적합한 경우도 많습니다.