배움 저장소
[Data Structures] 챕터4. 스택(Stack) 본문
스택
- 할 일을 쌓아둔다.
- LIFO(Last in First Out), 후입선출
용도
- 편집 소프트웨어의 Undo/Redo 기능 구현
- 연산자 우선순위를 고려한 수식의 계산 예) 1 + 2 * 3 에서 + 연산을 미뤄두고 *를 먼저 수행
- 함수 실행할 때 스택에 프레임 쌓기 - 앞에서 재귀호출 가시화 도구 가시화에서 봤듯이 나중에 호출된 함수를 끝낸 후 그 전에 호출된 함수로 돌아갈 수 있습니다.
구현
int Size() const
{
return top_ + 1; // top_이 index를 가리키므로 +1을 해주자
}
// Insert item into the TOP of the stack
void Push(const T& item)
{
if (capacity_ == top_+1) // capacity는 top + 1 index를 포함해야함
Resize(capacity_ * 2);
++top_;
stack_[top_] = item;
}
Pop
T자료형이 내부에서 동적할당 메모리를 사용한다면 소멸자를 호출해주자.
void Pop()
{
assert(!IsEmpty());
stack_[top_].~T(); // T 자료형 내부에서 동적할당 메모리를 사용할 경우
top_--;
// 메모리를 아껴야 될 경우 Resize로 크기를 줄이자
}
미로
struct Pos
{
int row;
int col;
friend ostream& operator<<(ostream& os, const Pos& pos)
{
cout << "(" << pos.row << ", " << pos.col << ")" << flush;
return os;
}
};
참고 1) 9.2 입출력 연산자 오버로딩하기 https://hoplite.tistory.com/100
1. 연산자 오버로딩은 2개의 Parameter가 필요하다
2. 멤버 함수로 정의할 경우 1개의 Parameter가 필요하다
3. ostream의 멤버함수를 정의할 수 없으므로 외부 함수로 2개의 Parameter를 호출하는 형태로 함수를 정의한다.
4. 이 때 외부에서 구현하면 private 멤버변수에 접근할 수 없다.
5. 따라서 class 내부에서 정의 + friend keyword를 활용하여 해당 함수에서 멤버 변수의 접근권한을 준다.
참고 2) constexpr Keyword in C++17 https://hoplite.tistory.com/86
constexpr은 compile time에 초기화되는 변수이다.아래와 같이 정적 배열을 정의하는데 사용할 수 있다.
constexpr int kNumRows = 2;
constexpr int kNumCols = 2;
char maze[kNumRows][kNumCols] = {{0,1},{2,3};
참고 3) 구조체의 생성자와 임시 구조체
Pos start = { 1, 1 }; // i = 1, j = 1 시작 지점
//Pos start;
//start.row = 1;
//start.col = 1;
s.Push(start); // s.Push({1, 1});
구현
재귀호출
int RecurMaze(Pos p)
{
const char mark = maze[p.row][p.col];
cout << p << endl;
if (mark == 'G')
{
cout << "Found!" << endl;
return 1;
}
if (mark == '1' || mark == 'X')
return 0;
maze[p.row][p.col] = 'X';
if (RecurMaze({p.row, p.col-1})) return 1;
if (RecurMaze({p.row, p.col+1})) return 1;
if (RecurMaze({p.row+1, p.col})) return 1;
if (RecurMaze({p.row-1, p.col})) return 1;
}
문자 값 '1'으로 둘러쌓인 배열이므로 사용할 수 없는 index에 접근하는지 확인할 필요는 없다.
/*L*/if (0 < p.col - 1)
{
if (RecurMaze({p.row, p.col-1}) == 1)
return 1;
}
stack
재귀 호출은 자기함수를 호출하는 순서와 순회하는 순서가 동일하다.
stack을 사용하면 push하는 순서와 순회하는 순서가 반대가 된다.
void StackMaze()
{
Stack<Pos> s;
s.Push({1, 1});// i = 1, j = 1 시작 지점
// s.Print(); // 디버깅에 사용 가능
while (!s.IsEmpty())
{
Pos p = s.Top();
s.Pop();
cout << p << " "; // 디버깅 출력
const char mark = maze[p.row][p.col];
if (mark == 'G')
{
cout << "Found!" << endl;
break;
}
if (mark == 'X' || mark == '1')
continue;
maze[p.row][p.col] = 'X';
// 재귀와 다르게 마지막 Push부터 순회함
s.Push({ p.row, p.col-1 });
s.Push({ p.row, p.col+1 });
s.Push({ p.row+1, p.col });
s.Push({ p.row-1, p.col });
}
}
의미
재귀호출와 Stack을 사용한 구현은 유사하다.
하노이의 탑
N개의 Disk를 옮기려면
- N-1개의 Disk를 임시 위치로 옮긴다.
- 가장 아래에 있는 N 번째 Disk를 목표 위치로 옮긴다.
- 임시 위치에 있는 N-1개의 Disk를 목표위치로 옮긴다.
이 때 N-1개의 Disk를 옮기는 일은 다시 위와 같이 처리할 수 있으므로 재귀호출로 문제를 풀 수 있다.
void RecurMoveDisks(int n, int from, int temp, int to)
{
if (n == 0)
return;
// stack the rest on temp location
RecurMoveDisks(n-1, from, to, temp);
// Move bottom disk to target location
MoveDisk(from, to);
// move the rest on the temp to target location
RecurMoveDisks(n - 1, temp, from, to);
}
'CS > Data Structure' 카테고리의 다른 글
[Data Structures] 챕터6. 연결 리스트 (0) | 2023.12.13 |
---|---|
[Data Structures] 챕터5. 큐(Queue) (0) | 2023.11.30 |
[Data Structures] 챕터3. 배열 (0) | 2023.11.29 |
[Data Structures] 챕터2. 재귀(Recursion) (0) | 2023.11.27 |
[Data Structures] 챕터1. 기본개념 (0) | 2023.11.25 |
Comments