Stacks
이번에는 기초 자료구조 중에서도 어디에서도 사용되고 있는 개념인 스택에 대해서 알아보도록하자.
스택은 예시를 들어보면 사진과 같이 팬케익이 쌓여있는 느낌으로 볼 수 있다.
Stack의 자료구조는 개념적으로 객체의 물리적 스택과 동일하다. 어떤 항목을 스택에 넣으면 스택의 맨 위에 놓이게 되고 스택의 어떤 항목을 제거한다면 항상 가장 위에 있는 항목이 제거된다.
스택은 두가지 필수 작업이 존재한다.
- Push : 스택의 최상단에 요소 추가
- Pop : 스택의 최상단 요소를 제거
인터페이스를 이 두가지 작업으로 제한하는 것은 자료구조의 한 방향에서만 추가하거나 제거할 수 있다는 것이다. CS에서 스택은 LIFO(후입선출) 자료구조로 알려져 있다. 가장 마지막에 Push된 요소가 가장 먼저 Pop되어 나가지게 된다.
iOS는 네비게이션 스택을 사용해 뷰컨트롤러의 뷰 안밖으로 푸시하거나 팝하는 것으로 사용되고 메모리할당은 아키텍처레벨에서 스택을 사용한다. 지역변수를 저장하는 메모리도 스택을 사용해서 관리하게 된다.
public struct Stack<Element> {
private var storage: [Element] = []
public init() { }
}
extension Stack: CustomDebugStringConvertible {
public var debugDescription: String {
\\(storage.map { "\\($0)"}.reversed().joined(separator: "\\n"))
}
}
이 코드는 스택의 백업 스토리지를 정의한 내용이다. 스택에 적합한 스토리지 유형을 선택하는 것은 매우매우 중요하다. 배열은 append와 popLast 를 이용해 한쪽 끝에서 한쪽 끝까지 상수시간의 삽입과 삭제가 이루어진다. CustomDebugStringConvertible 프로토콜에 필요한 debugDescription의 함수 호출 체인을 위해 3가지 작업을 해야한다.
- stroage.map { ”\\($0)” } 로 문자열에 매핑하는 배열 만들기
- reversed()를 이용해 이전 배열을 뒤집는 새로운 배열을 만들기
- joined(separator:)를 이용해 배열을 문자열로 병함, “\\n” 개행 문자를 이용해 배열의 요소를 분리한다.
public mutating func push(_ element: Element) {
storage.append(element)
}
@discardableResult
public mutating func pop() -> Element? {
storage.popLast()
}
이를 바탕으로 이렇게 값을 넣으면
example(of: "using a stack") {
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
print(stack)
if let poppedElement = stack.pop() {
assert(4 == poppedElement)
print("Popped: \\(poppedElement)")
}
}
상단의 내용과 같이 출력되게 된다. 이 경우 push 와 pop은 둘 다 시간복잡도가 O(1) 입니다.
스택을 더 쉽게 사용할 수 있는 유용한 작업들이 있는데
public func peek() -> Element? {
storage.last
}
public var isEmpty: Bool {
peek() == nil
}
스택 인터페이스는 종종 peek 작업이 포함된다. peek은 안의 콘텐츠를 변경하지 않고 스택의 상단요소를 살펴보는 것이다.
스택의 목적은 데이터에 접근하는 방법의 가짓수를 제한하는 것이다. Collection과 같은 프로토콜을 채택하는 것은 반복자(iterator)와 아래 첨자(subscript) 를 통해 모든 요소를 노출하므로 스택의 목적에 반하는 내용이다.
private 저장소를 설정하는 생성자를 작성할 수 있으니 아래와 같이 스택 구현에 추가해보자
public init(_ elements: [Element]) {
storage = elements
}
example(of: "initializing a stack from an array") {
let array = ["A", "B", "C", "D"]
var stack = Stack(array)
print(stack)
stack.pop()
}
위 코드는 문자열 스택을 생성하고 최상단 요소인 “D”를 내보낸다. 스위프트 컴파일러는 배열로부터 요소의 타입을 추론할 수 있으므로 Stack<String>대신 Stack을 사용할 수 있다.
한단계 더 나아가서 배열 리터럴을 통해 스택을 초기화할 수 있도록 만들 수 있다.
extension Stack: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Element...) {
storage = elements
}
}
example(of: "initializing a stack from an array literal") {
var stack: Stack = [1.0, 2.0, 3.0, 4.0]
print(stack)
stack.pop()
}
위 코드는 Double 스택을 생성하고 최상단의 4.0 값을 내보낸다. 다시 말하면 타입추론을 통해 Stack<Double>를 입력하지 않아도 된다는 것이다!
스택은 트리와 그래프를 검색하는 문제에 매우 중요하다. 미로에서 길을 찾는 것을 상상해볼 때 왼쪽, 오른쪽, 직진을 선택해야할 때 마다 할 수 있는 모든 결정을 스택에 저장할 수 있다. 막다른 골목에 도달하면 스택에서 마지막 결정을 pop 하므로써 역 추적할 수 있고, 다른 막다른 골목에 도달하거나 탈출하기 전까지 이를 계속할 수 있다.
핵심 요약 Key points
- 스택은 LIFO, 후입선출 자료구조이다.
- 매우 단순하지만 스택은 많은 문제들의 핵심 자료구조이다.
- 스택의 두가지 필수 작업으로, 요소를 추가하는 push 메서드와 요소를 제거하는 pop 메서드가 있다.
오늘은 아주 기초적인 스택을 조금 더 깊게 앞아보는 내용을 기록해봤다 🙂 요즘 날씨가 안춥고 따듯해서 그런지 마음이 싱숭생숭하다.. 그치만 잘 맘을 다 잡고 다시 가보자!!! 빠이팅
'◽️ Programming > ◽️ Computer Science' 카테고리의 다른 글
iOS Swift의 자료 구조, 알고리즘에 대해서 알아보자 (3) (0) | 2024.11.04 |
---|---|
iOS Swift의 자료구조, 알고리즘에 대해서 알아보자 (2) (0) | 2024.10.30 |
iOS Swift의 자료 구조, 알고리즘에 대해서 알아보자 (1) (0) | 2024.10.28 |
[CS] 컴퓨터 구조에 대해서 자세하게 알아보자 (1) | 2024.07.15 |
[CS] 운영체제에 대해서 자세하게 알아보자 (0) | 2024.07.13 |