CHAP 5: 스택
스택이란? 스택 (stack): 쌓아놓은더미
스택의특징 후입선출 (LIFO:Last-In First-Out): 가장최근에들어온데이터가가장먼저나감. D C B C B C B C B A A A A
스택의구조 요소 (element) C 스택상단 (top) B A 스택하단 (bottom)
스택추상데이터타입 (ADT) 객체 : n 개의 element 형의요소들의선형리스트 연산 : create() ::= 스택을생성한다. is_empty(s) ::= 스택이비어있는지를검사한다. is_full(s) ::= 스택이가득찼는가를검사한다. push(s, e) ::= 스택의맨위에요소 e 를추가한다. pop(s) ::= 스택의맨위에있는요소를삭제한다. peek(s) ::= 스택의맨위에있는요소를삭제하지않고반환한다.
스택의연산 push(): 스택에데이터를추가 pop(): 스택에서데이터를삭제 push(a) push(b) push(c) C pop() B B B A A A A 초기상태
스택의연산 is_empty(s): 스택이공백상태인지검사 is_full(s): 스택이포화상태인지검사 create(): 스택을생성 peek(s): 요소를스택에서삭제하지않고보기만하는연산 ( 참고 )pop 연산은요소를스택에서완전히삭제하면서가져온다.
스택의용도 입력과역순의출력이필요한경우 에디터에서되돌리기 (undo) 기능 함수호출에서복귀주소기억 1 20 int main() { int i=3; sub1(i);... sub2 PC=150 b=5 100 150 int sub1(int a) { int j=5; sub2(j);... main PC=1 sub1 PC=20 a=3 main i=3 sub1 PC=20 a=3 j=5 main i=3 sub1 PC=20 a=3 j=5 main i=3 main i=3 200 void sub2(int b) {... 시스템스택 시스템스택 시스템스택 시스템스택 시스템스택
배열을이용한스택의구현 1 차원배열 stack[ ] 스택에서가장최근에입력되었던자료를가리키는 top 변수 가장먼저들어온요소는 stack[0] 에, 가장최근에들어온요소는 stack[top] 에저장 스택이공백상태이면 top 은 -1 4 4 4 top 3 3 3 2 2 top 2 1 1 1 0-1 공백상태 top 0-1 0-1 포화상태
is_empty, is_full 연산의구현 is_empty(s) is_full(s) if top = -1 then return TRUE else return FALSE if top = (MAX_STACK_SIZE-1) then return TRUE else return FALSE 4 3 2 1 4 3 2 1 top 0 0-1 공백상태 top -1 포화상태
push 연산 push(s, x) if is_full(s) then error "overflow" else top top+1 stack[top] x 4 3 2 1 0 top 4 3 2 1 0 top
pop 연산 pop(s, x) if is_empty(s) then error "underflow" else e stack[top] top top-1 return e 4 3 2 1 0 top 4 3 2 1 0 top
C 언어구현 typedef int element; typedef struct { element stack[max_stack_size]; int top; StackType; // 스택초기화함수 void init(stacktype *s) { s->top = -1; // 공백상태검출함수 int is_empty(stacktype *s) { return (s->top == -1); // 포화상태검출함수 int is_full(stacktype *s) { return (s->top == (MAX_STACK_SIZE-1)); 배열의요소는 element 타입으로선언 관련데이터를구조체로묶어서함수의파라미터로전달
// 삽입함수 void push(stacktype *s, element item) { if( is_full(s) ) { fprintf(stderr," 스택포화에러 \n"); return; else s->stack[++(s->top)] = item; // 삭제함수 element pop(stacktype *s) { if( is_empty(s) ) { fprintf(stderr, " 스택공백에러 \n"); exit(1); else return s->stack[(s->top)--]; // 피크함수 element peek(stacktype *s) { if( is_empty(s) ) { fprintf(stderr, " 스택공백에러 \n"); exit(1); else return s->stack[s->top];
연결된스택 연결된스택 (linked stack): 연결리스트를이용하여구현한스택 장점 : 크기가제한되지않음 단점 : 구현이복잡하고삽입이나삭제시간이오래걸린다. 4 3 9 top 2 1 9 7 top 7 0 3 3 NULL
연결된스택정의 typedef int element; typedef struct StackNode { element item; struct StackNode *link; StackeNode; 요소의타입 노드의타입 typedef struct { StackNode *top; LinkedStackType; 연결된스택의관련데이터 9 top 7 3 NULL
연결된스택에서 push 연산 top C B A NULL temp (2) D (1) // 삽입함수 void push(linkedstacktype *s, element item) { StackNode *temp=(stacknode *)malloc(sizeof(stacknode)); if( temp == NULL ){ else{ fprintf(stderr, " 메모리할당에러 \n"); return; temp->item = item; temp->link = s->top; s->top = temp;
연결된스택에서 pop 연산 top // 삭제함수 element pop(linkedstacktype *s) { if( is_empty(s) ) { else{ temp fprintf(stderr, " 스택이비어있음 \n"); exit(1); StackNode *temp=s->top; int item = temp->item; s->top = s->top->link; free(temp); C B A NULL return item;
스택의응용 : 괄호검사 괄호의종류 : 대괄호 ( [, ] ), 중괄호 ( {, ), 소괄호 ( (, ) ) 조건 1. 왼쪽괄호의개수와오른쪽괄호의개수가같아야한다. 2. 같은괄호에서왼쪽괄호는오른쪽괄호보다먼저나와야한다. 3. 괄호사이에는포함관계만존재한다. 잘못된괄호사용의예 (a(b) a(b)c) a{b(c[d]ef)
스택을이용한괄호검사 if( ( i==0 ) && (j==0 ) 비교 ( ( 비교 오류 ( ( ( ( ( ( ( ( { A [ (i+1 ) ]=0; 비교비교비교성공 ( [ { { [ { ( [ [ { { {
알고리즘 알고리즘의개요 문자열에있는괄호를차례대로조사하면서왼쪽괄호를만나면스택에삽입하고, 오른쪽괄호를만나면스택에서 top 괄호를삭제한후오른쪽괄호와짝이맞는지를검사한다. 이때, 스택이비어있으면조건 1 또는조건 2 등을위배하게되고괄호의짝이맞지않으면조건 3 등에위배된다. 마지막괄호까지를조사한후에도스택에괄호가남아있으면조건 1 에위배되므로 0( 거짓 ) 을반환하고, 그렇지않으면 1( 참 ) 을반환한다.
괄호검사알고리즘 check_matching(expr) while ( 입력 expr 의끝이아니면 ) ch expr 의다음글자 switch(ch) case '(': case '[': case '{': ch 를스택에삽입 break case ')': case ']': case ']': if ( 스택이비어있으면 ) then 오류 else 스택에서 open_ch 를꺼낸다 break if (ch 와 open_ch 가같은짝이아니면 ) then 오류보고 if( 스택이비어있지않으면 ) then 오류 왼쪽괄호이면스택에삽입 오른쪽괄호이면스택에서삭제, 비교
괄호검사프로그램 // int check_matching(char *in) { StackType s; char ch, open_ch; int i, n = strlen(in); init(&s); for (i = 0; i < n; i++) { ch = in[i]; switch(ch){ case '(': case '[': push(&s, ch); break; case '{':
case ')': case ']': case '': if(is_empty(&s)) return FALSE; else { open_ch = pop(&s); if ((open_ch == '(' && ch!= ')') (open_ch == '[' && ch!= ']') (open_ch == '{' && ch!= '')) { return FALSE; break; if(!is_empty(&s)) return FALSE; return TRUE; int main() { if( check_matching("{ A[(i+1)]=0; ") == TRUE ) printf(" 괄호검사성공 \n"); else printf(" 괄호검사실패 \n");
수식의계산 수식의표기방법 : 전위 (prefix), 중위 (infix), 후위 (postfix) 중위표기법 전위표기법 후위표기법 2+3*4 +2*34 234*+ a*b+5 +5*ab ab*5+ (1+2)+7 +7+12 12+7+ 컴퓨터에서의수식계산순서 중위표기식 -> 후위표기식 -> 계산 2+3*4 -> 234*+ -> 14 모두스택을사용 먼저후위표기식의계산법을알아보자
후위표기식의계산 수식을왼쪽에서오른쪽으로스캔하여피연산자이면스택에저장하고연산자이면필요한수만큼의피연산자를스택에서꺼내연산을실행하고연산의결과를다시스택에저장 ( 예 ) 82/3-32*+ 토큰 8 8 스택 [0] [1] [2] [3] [4] [5] [6] 2 8 2 / 4 3 4 3-1 3 1 3 2 1 3 2 * 1 6 + 7
0 1 2 3 8 8 2 / 3-0 1 2 3 8 8 2 / 3-2 0 1 2 3 4 8 2 / 3 - 피연산자 -> 삽입피연산자 -> 삽입연산자 -> 8/2=4 삽입 0 1 2 3 4 8 2 / 3-0 1 2 3 1 8 2 / 3-0 1 2 3 1 8 2 / 3-3 피연산자 -> 삽입연산자 -> 4-1=1 삽입종료 -> 전체연산결과 =1
후위표기식계산알고리즘 스택 s 를생성하고초기화한다. for 항목 in 후위표기식 do if ( 항목이피연산자이면 ) push(s, item) if ( 항목이연산자 op 이면 ) then second pop(s) first pop(s) result first op second // op 는 +-*/ 중의하나 push(s, result) final_result pop(s);
// 후위표기수식계산함수 eval(char exp[]) { int op1, op2, value, i=0; int len = strlen(exp); char ch; StackType s; init(&s); for( i=0; i<len; i++){ ch = exp[i]; if( ch!= '+' && ch!= '-' && ch!= '*' && ch!= '/' ){ value = ch - '0'; // 입력이피연산자이면 push(&s, value); else{ // 연산자이면피연산자를스택에서제거 op2 = pop(&s); op1 = pop(&s); switch(ch){ // 연산을수행하고스택에저장 return pop(&s); case '+': push(&s,op1+op2); break; case '-': push(&s,op1-op2); break; case '*': push(&s,op1*op2); break; case '/': push(&s,op1/op2); break;
중위표기식 -> 후위표기식 중위표기와후위표기 중위표기법과후위표기법의공통점은피연산자의순서는동일 연산자들의순서만다름 ( 우선순위순서 ) -> 연산자만스택에저장했다가출력하면된다. 2+3*4 -> 234*+ 알고리즘 피연산자를만나면그대로출력 연산자를만나면스택에저장했다가스택보다우선순위가낮은연산자가나오면그때출력 왼쪽괄호는우선순위가가장낮은연산자로취급하며, 무조건스택에저장 오른쪽괄호가나오면스택에서왼쪽괄호위에쌓여있는모든연산자를출력
( a + b ) * c (
( a + b ) * c a (
( a + b ) * c + a (
( a + b ) * c + a b (
( a + b ) * c a b +
( a + b ) * c a b + *
( a + b ) * c a b + c *
( a + b ) * c a b + c *
infix_to_postfix(exp) 스택 s 를생성하고초기화 while (exp 에처리할문자가남아있으면 ) ch 다음에처리할문자 switch (ch) case 연산자 : while ( peek(s) 의우선순위 ch 의우선순위 ) do e pop(s) e 를출력 push(s, ch); break; case 왼쪽괄호 : push(s, ch); break; case 오른쪽괄호 : e pop(s); while( e 왼쪽괄호 ) do e 를출력 e pop(s) break; case 피연산자 : ch 를출력 break; while( not is_empty(s) ) do e pop(s) e 를출력
프로그램 // 중위표기수식 -> 후위표기수식 void infix_to_postfix(char exp[]) { int i = 0; char ch, top_op; int len = strlen(exp); StackType s; init(&s); for (i = 0; i<len; i++) { ch = exp[i]; // 연산자이면 switch (ch) { // 스택초기화 case '+': case '-': case '*': case '/': // 연산자 // 스택에있는연산자의우선순위가더크거나같으면출력 while (!is_empty(&s) && (prec(ch) <= prec(peek(&s)))) push(&s, ch); break; printf("%c", pop(&s)); case '(':// 왼쪽괄호 push(&s, ch); break;
프로그램 case ')':// 오른쪽괄호 top_op = pop(&s); // // 왼쪽괄호를만날때까지출력 while( top_op!= '(' ){ printf("%c", top_op); top_op = pop(&s); break; default: printf("%c", ch); break; // 피연산자 while(!is_empty(&s) ) // 스택에저장된연산자들출력 printf("%c", pop(&s)); main() { infix_to_postfix("(2+3)*4+9");
미로탐색문제 체계적인방법필요 현재의위치에서가능한방향을스택에저장해놓았다가막다른길을만나면스택에서다음탐색위치를꺼낸다. 1 1 1 1 1 1 1 1 1 1 입구 0 0 0 0 1 0 0 0 0 1 1 0 0 0 1 0 0 0 0 1 1 0 1 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 0 1 0 1 출구 1 0 1 0 1 m 0 1 0 1 1 0 1 0 0 0 0 1 0 x 1 1 1 1 1 1 1 1 1 1
미로탐색알고리즘 스택 s 과출구의위치 x, 현재생쥐의위치를초기화 while( 현재의위치가출구가아니면 ) do 현재위치를방문한것으로표기 성공 ; if( 현재위치의위, 아래, 왼쪽, 오른쪽위치가아직방문되지않았고갈수있으면 ) then 그위치들을스택에 push if( is_empty(s) ) then 실패 else 스택에서하나의위치를꺼내어현재위치로만든다 ;
미로프로그램 #define MAX_STACK_SIZE 100 #define MAZE_SIZE 6 typedef struct StackObjectRec { short r; short c; StackObject; StackObject stack[max_stack_size]; int top = -1; StackObject here={1,0, entry={1,0; char maze[maze_size][maze_size] = { {'1', '1', '1', '1', '1', '1', {'e', '0', '1', '0', '0', '1', {'1', '0', '0', '0', '1', '1', {'1', '0', '1', '0', '1', '1', {'1', '0', '1', '0', '0', 'x', {'1', '1', '1', '1', '1', '1', ;
미로프로그램 void pushloc(int r, int c) { if( r < 0 c < 0 ) return; if( maze[r][c]!= '1' && maze[r][c]!= '.' ){ StackObject tmp; tmp.r = r; tmp.c = c; push(tmp); void printmaze(char m[maze_size][maze_size]) {
미로프로그램 void printstack() { int i; for(i=5;i>top;i--) printf(" for(i=top;i>=0;i--) \n"); printf(" (%01d,%01d) \n", stack[i].r, stack[i].c); printf("-------\n");
미로프로그램 void main() { int r,c; here = entry; printmaze(maze); printstack(); while ( maze[here.r][here.c]!='x' ){ printmaze(maze); r = here.r; c = here.c; maze[r][c] = '.'; pushloc(r-1,c); pushloc(r+1,c); pushloc(r,c-1); pushloc(r,c+1);
미로프로그램 printstack(); if( isempty() ){ else printf(" 실패 \n"); return; here = pop(); printmaze(maze); printstack(); getch(); printf(" 성공 \n");