스택
특징
- Last in First Out(LIFO)
→ 가장 최근에 들어온 데이터가 가장 먼저 나감 - 입력의 역순 출력이 필요한 이유
- 에디터에서 되돌리기 기능
- 함수호출에서 복귀주소 기능
ADT
- 객체 : 0개 이상의 원소를 가지는 유한 리스트
- 연산 :
create(size)
::= 최대 크기가 size인 빈 스택을 생성is_empty(s)
::= 스택 s가 비어있는지를 검사is_full(s)
::= 스택 s가 가득 찼는가를 검사push(s, x)
::= 스택 s의 맨 위에 요소 x를 추가pop(s)
::= 스택 s의 맨 위에 있는 요소를 삭제peek(s)
::= 스택 s의 맨 위에 있는 요소를 삭제하지 않고 반환
구현
배열을 이용한 스택
- 1차원 배열 stack [MAX_STACK_SIZE] 사용
- 스택이 공백상태이면 top은 -1
- 가장 먼저 들어온 요소는 stack [0]에 저장
- 가장 최근에 들어온 요소는 stack [top]에 저장
- top이 MAX_STACK_SIZE-1이면 스택은 포화상태
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK 100
int stack[MAX_STACK];
int top = -1;
void error(const char* msg){
printf("%s\n",msg);
exit(1);
}
int isFull()
{
return (top == MAX_STACK-1);
}
int isEmpty(){
return (top == -1);
}
//삽입함수
void push(int item){
if(isFull()) error("큐가 다 찼습니다");
stack[++top] = item;
}
//삭제함수
int pop(){
if(isEmpty()) error("큐가 비었습니다");
return stack[top--];
}
//피크함수
int peek(){
if(isEmpty()) error("큐가 비었습니다");
return stack[top];
}
int main(){
push(3);
push(2);
push(1);
printf("%d\n",pop());
printf("%d\n",pop());
printf("%d\n",pop());
printf("%d\n",pop());
}
스택 구조체 배열
- 복잡한 자료형이 섞여있을 경우 → 스택 구조체로 구현
// 구조체 element 정의
typedef struct{
int number;
char* name;
}element;
...
element stack[MAX_STACK]; // stack을 element 자료형을 선언
...
//스택에 넣을때 매개변수를 구조체 형식으로
void push(element item){
if(isFull()) error("큐가 다 찼습니다");
stack[++top] = item;
}
//반환을 구조체로 해야하기 때문에 element로 반환
element pop(){
if(isEmpty()) error("큐가 비었습니다");
return stack[top--];
}
//마찬가지
element peek(){
if(isEmpty()) error("큐가 비었습니다");
return stack[top];
}
int main(){
element in = {1,"hong"};
push(in);
element out = pop();
printf("%s\n",out.name);
printf("%d",out.number);
}
스택을 함수 매개변수로 전달
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK 100
typedef struct{
int top;
int data[MAX_STACK];
}Stack;
void init(Stack* s){
s->top = -1;
}
void error(const char* msg){
printf("%s\n",msg);
exit(1);
}
int isFull(Stack* s)
{
return (s->top == MAX_STACK-1);
}
int isEmpty(Stack* s){
return (s->top == -1);
}
void push(Stack* s,int item){
if(isFull(s)) error("큐가 다 찼습니다");
s ->data[++(s->top)] = item;
}
int pop(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)--];
}
int peek(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)];
}
int main(){
Stack s;
init(&s);
push(&s, 1);
push(&s, 2);
push(&s, 3);
printf("%d\n",pop(&s));
printf("%d\n",pop(&s));
printf("%d\n",pop(&s));
}
동적 배열 스택
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK 100
typedef struct{
int top;
int* data;// 동적이니까 포인터
int capacity;
}Stack;
void init(Stack* s){
s->top = -1;
s->capacity = 1;
s->data = (int *)malloc(sizeof(int));
}
void error(const char* msg){
printf("%s\n",msg);
exit(1);
}
int isFull(Stack* s)
{
return (s->top == MAX_STACK-1);
}
int isEmpty(Stack* s){
return (s->top == -1);
}
void push(Stack* s,int item){
if(isFull(s)){
s -> capacity *= 2;
s ->data = (int *)realloc(s -> data ,s -> capacity * sizeof(int));
}
s ->data[++(s->top)] = item;
}
int pop(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)--];
}
int peek(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)];
}
int main(){
Stack s;
init(&s);
push(&s, 1);
push(&s, 2);
push(&s, 3);
printf("%d\n",pop(&s));
printf("%d\n",pop(&s));
printf("%d\n",pop(&s));
free(s.data); // data 할당 해제
}
스택 응용 : 괄호검사
- 조건
- 왼쪽 괄호의 개수와 오른쪽 괄호의 개수가 같아야 한다.
- 같은 괄호에서 왼쪽 괄호는 오른쪽 괄호보다 먼저 나와야 한다.
- 괄호 사이에는 포함 관계만 존재한다
- 괄호의 종류 : 대괄호[], 중괄호{}, 소괄호()
- 잘못된 사용 예
- (a(b)
- a(b))
- a {b(c [d] e] f}
괄호 검사 알고리즘
- 괄호를 처음부터 하나씩 탐색한다.
- 해당 인덱스가 왼쪽 괄호 { [ ( 이면 스택에 삽입, 오른쪽 괄호면 } ] ) 스택 가장 맨 위에 있는 것을 꺼내서 비교
- 같은 쌍이면 꺼낸 왼쪽 괄호 삭제 후 다음 인덱스를 2번 과정 반복
- 다른 쌍이거나 인덱스가 비어있으면 오류 보고 → 잘못됐음을 보고
- 해당 알고리즘 가능한 이유:
- LIFO 특징 때문에 가능
→ {(})같은 모양은 존재하지 않는다. 즉 ‘(’이 들어갔을 때 옳은 식이 되려면 바로 ‘)’이 나와야 한다.
→ 스택에 LIFO 특성을 이용하면 쉽게 구현 가능하다.
- LIFO 특징 때문에 가능
- 구현
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STACK 100
typedef struct{
int top;
char data[MAX_STACK];
}Stack;
void init(Stack* s){
s->top = -1;
}
void error(const char* msg){
printf("%s\n",msg);
exit(1);
}
int isFull(Stack* s)
{
return (s->top == MAX_STACK-1);
}
int isEmpty(Stack* s){
return (s->top == -1);
}
void push(Stack* s,char item){
if(isFull(s)) error("큐가 다 찼습니다");
s ->data[++(s->top)] = item;
}
char pop(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)--];
}
char peek(Stack* s){
if(isEmpty(s)) error("큐가 비었습니다");
return s->data[(s->top)];
}
int check(Stack* s,const char* str,int len){
for(int i = 0; i < len; i++){
switch(str[i]){
case'{':case'[':case'(': // 왼쪽 괄호이면 스택에 삽입
push(s,str[i]);
break;
case'}':case']':case')': // 오른쪽 괄호이면 스택에서 빼서 현재 str이랑 비교
if(isEmpty(s)) return 0;
char top = pop(s);
if(!((str[i] == '}' && top == '{') || (str[i] == ']' && top == '[') || (str[i] == ')' && top == '('))) {
return 0;
}
break;
}
}
if(isEmpty(s)) return 1;
else return 0;
}
int main(){
Stack s;
init(&s);
const char* str = "{()}";
int len = strlen(str);
printf("%d",check(&s,str,len));
}
수식 표기 및 계산 순서
컴퓨터에서의 수식 계산 순서
- 중위 표기식 → 후위 표기식 → 계산
- 2+34x → 234+x → 14
- 스택 사용
후위 표기식 계산
- 동작 원리
- 수식을 왼쪽에서 오른쪽으로 탐색
- 피연산자이면 스택에 저장, 연산자이면 필요한 수만큼 스택에 꺼내 연산
- 연산의 결과를 다시 스택에 저장
- Quiz
- 여기서 가장 먼저 수행되는 연산은 -> B+E
A | B | E | + | D | * | - |
중위표기식
- 중위 표기법과 후위 표기법의 공통점 = 피연산자의 순서 동일
→ 연산자의 순서만 다름(우선순위 순서) → 연산자만 스택에 저장해 놨다가 출력하면 됨- 2+34* → 234*+ 이런 식으로 변환 가능
- 알고리즘
- 중위 표기 수식을 순서대로 탐색
- 피연산자를 만나면 후위 표기식으로 출력
- 왼쪽 괄호는 우선순위가 가장 낮은 연산자로 취급
- 오른쪽 괄호가 나오면 스택에서 왼쪽 괄호 위에 쌓여있는 모든 연산자 출력
- 현재 연산자보다 우선순위가 높거나 같은 연산자는 모두 출력한 후 현재 연산자를 스택에 넣음
- 입력 수식이 끝나면 스택에 남은 연산자들을 모두 pop()해서 출력
미로탐색문제
- 현재 위치에서 가능한 방향을 스택에 저장해 놓았다가
막다른 길을 만나면 스택에서 다음 탐색 위치를 꺼낸다