저번주 금요일에 복습을 까먹고 안 한 내 죗값..
Ch.05 복습을 끝냈으니, 이번엔 Ch.06 복습을 진행해보겠다. 아마 계산기 구현 전까지 하지 않을까 싶다.
Ch 06. STACK
STACK. 일단 단어부터 "쌓다"라는 의미를 가지고 있다.
어느정도의 정형화된 정의를 보자.
메모리에 새로 들어오는 데이터의 위치가 메모리 말단이고, 써먹기 위해 내보내는 데이터 역시 메모리 말단을 거친다. - 출처 : 꺼무위키 -
이걸 쉽게 말하면, 그냥 쌓여있는 박스라고 생각하면 된다.
박스 세개가 이미 쌓여있다 가정하자. 우리가 새로운 박스를 쌓아올리려면, 바닥에 있는 다른 박스 하나를 이 세개의 박스 위에 올려서 네개가 된다. 이게 "매모리에 새로 들어오는 데이터의 위치가 메모리 말단이고," 의 설명.
그리고 이 네개가 쌓여있는 박스에서, 밑에서 두번째에 있는 박스를 빼려면?
바로 두번째 박스를 뺄 수는 없고, 위에서부터 차곡차곡 하나씩 꺼내야 한다. 이게 "써먹기 위해 내보내는 데이터 역시 메모리 말단을 거친다." 의 설명이다.
그냥 이걸 한줄로 말하면 쌓여 올려져 있는 박스들, 쟁반 위에 쌓여있는 접시들을 생각하면 된다. 아니면 "후입선출(後入先出)".
그리고 주의해야 할게, 스택의 의미를 말할 때, Heap 영역 메모리에서 일반적인 데이터를 저장하는 스택 / 스택 영역 메모리에서 프로그램에 정보를 저장하기 위한 스택 두가지를 잘 구분 하자. 지금 우리가 다룰건 후자다.
그러면 스택 자료구조의 ADT를 정의해보자.
필요한 기능은 총 네개다.
"넣기" / "빼기" / "뭐가 들어있나 확인하기" / "비었는지 확인하기". 앞에서부터 세개를 각각 Push / Pop / Peek 이라고 하고, 맨 마지막 하나는 그냥 ADT를 정의할때 Empty라고 하겠다.
아 그리고, 너무 당연하지만 Stack도 초기화를 해줘야 하니 Init 함수도 포함된다.
/* stack의 ADT*/
void StackInit (Stack *pstack);
int SIsEmpty(Stack *pstack);
void SPush(Stack *pstack, Data data);
Data SPop(Stack *pstack);
Data SPeek(Stack *pstack);
이렇게 다섯가지다. 위의 함수들이 뭘 의미하는지는 방금 코드 위에 적어놓은 말들과 비교하며 보자.
그리고 스택도 배열 / 연결리스트 등 자료구조를 활용해서 새로운 자료구조의 구현에 써먹을 수 있는데, 일단 배열을 기반으로 한 스택 구현을 보자.
이 배열 기반 스택 구현에선 List처럼 뭐 복잡하게 여기 잇고 저기 잇고 그런게 없이, 그냥 넣고 빼는데에만 신경쓰면 된다.
그림은 생략하겠지만, 앞서 예를 들었던 "박스 쌓기"를 생각해라.
여기서 중요한 포인트 두개는,
1번은 만약 ch[3] = {1, 2, 3} 이라는 배열이 있으면, 여기서 가장 첫 요소는 ch[0]인 1이다. 그래서 인덱스 0의 배열 요소라고 말을 했고, 이 요소가 가장 먼저 Stack에 들어가게 되므로 스택의 바닥으로 정의되는 것이다.
2번은 앞서 박스 예시처럼 넣고 빼야하고, 몇개의 데이터가 저장되어있는지 알 수 있게 저장된 데이터 위치를 기억한다. 이를 topIndex라고 하겠다.
그러면 각 함수들은 어떻게 구현이 될까?
push, topIndex를 한 칸 올리고, 그 올라가있는 칸에 데이터를 추가.
pop. topIndex에 있는 데이터를 빼내고, topIndex를 한 칸 내린다.
/* ABS.h */
#ifndef __A_B_S_H__
#define __A_B_S_H__
#define TRUE 1
#define FALSE 0
#define STACK_LEN 100
typedef int Data;
typedef struct _arrayStack {
Data stackArr[STACK_LEN];
int topIndex;
} ArrayStack;
typedef ArrayStack Stack;
void StackInit (Stack *pstack);
int SIsEmpty(Stack *pstack);
void SPush(Stack *pstack, Data data);
Data SPop(Stack *pstack);
Data SPeek(Stack *pstack);
#endif
이걸 대충 ADT와 함께 헤더파일을 정의해주면 요렇게 된다.
이제 이 ADT에 정의된 함수들을 구현해보자.
/* ABS.c */
void StackInit (Stack *pstack) {
pstack->topIndex = -1;
} // -1인 이유는, Index의 바닥이 0부터 시작되니까.
int SIsEmpty(Stack *pstack) {
if(pstack->topIndex == -1) { // 비어있다면, TRUE 반환
return TRUE;
} else { // 뭔가 있다면, FALSE 반환
return FALSE;
}
}
void SPush(Stack *pstack, Data data) {
(pstack->topIndex)++; // topIndex를 한 칸 올리고
pstack->stackArr[pstack->topIndex] = data; // 데이터넣기
}
Data SPop(Stack *pstack) {
int rIdx;
if(SIsEmpty(pstack)) { // 들어있는 데이터가 없다면
printf("뽑을 데이터가 없는데요\n");
exit(-1);
}
rIdx = pstack->topIndex;
pstack->topIndex -= 1;
// 데이터는 꺼냈으니 삭제됨. 그냥 topIndex를 -1 하기만 해도 데이터 소멸 끝.
return pstack->stackArr[rIdx];
}
Data SPeek(Stack *pstack) { // 맨 위에 뭐가있나 보기만 할게요!
if(SIsEmpty(pstack)) {
printf("볼게 없어요\n");
exit(-1);
}
return pstack->stackArr[pstack->topIndex];
}
뭐, 요정도다.
오늘은 복습만 하고, 진도는 내일부터 다시 나가야지.
나머지 Stack은 내일 복습하도록 하겠다.