Data Structures & Algorithms

[Data Structures] 스택 (Stack)

숄구-ml 2022. 5. 6. 18:48

1. 스택이란

  • 선형 자료구조의 일종
  • 한쪽은 막히고 한쪽은 뚫려있는 초코볼 통에 비유할 수 있다
  • 후입선출 = Last In, First Out! (LIFO)

2. 스택 ADT와 헤더 파일 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef __AB_STACK_H__
#define __AB_STACK_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);
    //스택이 빈 경우 TRUE(1), 그렇지 않은 경우 FALSE(0) 반환
void SPush(Stack * pstack, Data data);
    //스택에 데이터 저장, 매개변수 data로 전달된 값을 저장
Data SPop(Stack * pstack);
    //마지막에 저장된 요소를 삭제
    //삭제된 데이터는 반환
    //본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다
Data SPeek(Stack * pstack);
    //마지막에 저장된 요소를 반환하되 삭제하지는 않는다
    //본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야
 
#endif
cs
 

3. 스택의 배열 기반 구현

출처: https://velog.io/@mmindoong/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8A%A4%ED%83%9D

    • 인덱스 0의 배열 요소가 '스택의 바닥'으로 정의된다
    • 마지막에 저장된 데이터의 위치를 기억해야 한다 - topIndex
    • 스택 구현 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//"ArrayBaseStack.h"
#ifndef __AB_STACK_H__
#define __AB_STACK_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);
    //스택이 빈 경우 TRUE(1), 그렇지 않은 경우 FALSE(0) 반환
void SPush(Stack * pstack, Data data);
    //스택에 데이터 저장, 매개변수 data로 전달된 값을 저장
Data SPop(Stack * pstack);
    //마지막에 저장된 요소를 삭제
    //삭제된 데이터는 반환
    //본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다
Data SPeek(Stack * pstack);
    //마지막에 저장된 요소를 반환하되 삭제하지는 않는다
    //본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야
 
#endif
 
 
 
//"ArrayBaseStack.c"
void StackInit(Stack * pstack)
{
    pstack->topIndex = -1;     //-1은 빈 상태를 가리킴
}
 
void SIsEmpty(Stack * pstack)
{
    if(pstack->topIndex == -1)
        return TRUE;
    else
        return FALSE;
}
 
void SPush(Stack * pstack, Data data)
{
    pstack->topIndex += 1;
    pstack->stackArr[pstack->topIndex] = data;
}
 
Data SPop(Stack * pstack)
{
    int rIdx; 
 
    if (SIsEmpty(pstack))
    {
        printf("stack memory error!");
        exit(-1);
    }
    
    rIdx = pstack->topIndex;
    pstack->topIndex -= 1;
    return pstack->stackArr[rIdx];
    
}
 
Data SPeek(Stack * pstack)
{
    if(SIsEmpty(pstack))
    {
        printf("stack memory error!");
        exit(-1);
    }
    
    return pstack->stackArr[pstack->topIndex];
}
 
 
//"ArrayBaseStackMain.c"
#include <stdio.h>
#include "ArrayBaseStack.h"
 
int main(void)
{
    //Stack 생성 및 초기화
    Stack stack;
    StackInit(&stack);
 
    //데이터 넣기
    SPush(&stack1); SPush(&stack2);
 
    //데이터 꺼내기
    while(!SIsEmpty(&stack))
        printf("%d ", SPop(&Stack));
 
    return 0;
}
cs

 

4. 스택의 연결리스트 기반 구현

출처:&nbsp;https://velog.io/@mmindoong/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8A%A4%ED%83%9D
    • head 포인터를 가지고, 머리에 새로운 노드를 저장하는 연결리스트가 바로 스택
    • 연결리스트 기반 스택 구현 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//"ListBaseStack.h"
 
#ifndef __LB_STACK_H__
#define __LB_STACK_H__
#define TRUE 1
#define FALSE 0
typedef int Data;
 
typedef struct _node
{
    Data data;
    struct _node * next;
} Node;
 
typedef struct _listStack
{
    Node * head;
} ListStack;
 
typedef ListStack Stack;
 
void StackInit(Stack * pstack);
int SIsEmpty(Stack * pstack);
void SPush(Stack * pstack, Data data);
Data SPop(Stack * pstack);
Data SPeek(Stack * pstack);
 
#endif


//"ListBaseStack.c"
#include <stdio.h>
#include <stdlib.h>
#include "ListBaseStack.h"
 
void StackInit(Stack * pstack)
{
    pstack->head = NULL;
}
void SIsEmpty(Stack * pstack)
{
    if (pstack->head == NULL)
        return TRUE;
    else
        return FALSE;
}
void SPush(Stack * pstack, Data data)
{
    Node * newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
 
    newNode->next = pstack->head;
    pstack->head = newNode;
}
Data SPop(Stack * pstack)
{
    Node * delNode;
    Data delData;
 
    if(SIsEmpty(pstack))
    {
        printf("stack memory error!");
        exit(-1);
    }
    
    delNode = pstack->head;
    delData = pstack->head->data;
    
    pstack->head = pstack->head->next;
    free(delNode);
    return delData;
}
Data SPeek(Stack * pstack)
{
    
    if(SIsEmpty(pstack))
    {
        printf("stack memory error!");
        exit(-1);
    }
    return pstack->head->data;    
}
cs

 

5. 스택 기반의 계산기 프로그램 구현

  • 수식의 표기법을 이해해보자
    1. 중위표기법 => 5 + 2 / 7
    2. 전위표기법 => + 5 / 2 7
    3. 후위표기법 => 5 2 7 / +
  • 중위표기법을 후위표기법으로 변환하는 방법
    • Stack 쟁반에는 연산자를 옮긴다
    • 연산자간의 우선순위를 비교해 행동한다
      1. 쟁반에 위치한 연산자의 우선순위가 높다면 -> 쟁반에 위치한 연산자를 꺼내서 변환된 수식이 위치할 자리로 옮긴다 / 그리고 새 연산자는 쟁반으로 옮긴다 (사칙연산의 경우 연산자의 우선순위가 동일하면, 먼저 등장한 연산자를 먼저 진행한다)
      2. 쟁반에 위치한 연산자의 우선순위가 낮다면 -> 쟁반에 위치한 연산자의 위에 새 연산자를 쌓는다
출처: https://hyunyoung2.github.io/2016/03/18/Utilization_Of_Stack_For_calculator/
    •  
    •  
    • 3. 소괄호 ( 연산자는 ) 연산자가 등장할 때까지 쟁반 위에 남아있어야 하기 때문에 사칙 연산자들보다 연산의 우선순위가 낮다고 간주한다
    •  
     
출처:&nbsp;https://hyunyoung2.github.io/2016/03/18/Utilization_Of_Stack_For_calculator/

 

    • 후위 표기법을 이용한 계산기 코드 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//"InfixToPostfix.c"
void GetOpPrec(char op)    //연산자의 연산 우선순위 정보를 반환
{
    swith(op)
    {
    case '*':
    case '/':
        return 5;     //가장 높은 연산의 우선순위
    case '+':
    case '-':
        return 3;
    case '(':
        return 1;    //가장 낮은 연산의 우선순위
    }
    
    return -1;         //등록되지 않은 연산자임을 알림
}
 
int WhoPrecOp(char op1, char op2)    //GetOpPrec 함수의 호출결과를 바탕으로 두 연산자의 우선순위를 비교하여 결과 반환하는 함수
{
    int op1Prec = GetOpPrec(op1);
    int op2Prec = GetOpPrec(op2);
    
    if(op1Prec > op2Prec)
        return 1;
    else if(op1Prec < op2Prec)
        return -1;
    else
        return 0;    // op1과 op2의 우선순위가 같다면
}
 
void ConvToRPNExp(char exp[])    //중위표기법을 후위표기법으로 변경하는 함수
{
    Stack stack;         //stack 쟁반 만들기
    StackInit(&stack);
    
    int i, idx = 0
    char tok;
    int expLen = strlen(exp);
    char * convExp = (char*)malloc(expLen+1);     //변환된 수식을 담을 공간 마련
    memset(convExp, 0sizeof(char)*expLen+1);     //할당된 배열을 0으로 초기화
 
    for(i=0; i<expLen; i++)
    {
        tok = exp[i];
        if(isdigit(tok))    //tok에 저장된 문자가 숫자인지 확인
        {
            convExp[idx++= tok; //숫자이면 배열 convExp에 그냥 저장
        } 
        else    //숫자가 아닌 연산자라면
        {
            switch(tok)
            {
            case '(':
                SPush(&stack, tok); //여는 소괄호라면 스택에 쌓는다
                break;
            case ')':
                while(1)
                {
                    popOp = SPop(&stack);
                    if (popOp == '(')
                        break;
                    convExp[idx++= popOp;
                }
                break;
            case '+'case '-':
            case '*'case '/':
                while(!SIsEmpty(&stack&& WhoPrecOp(SPeek(&stack), tok) >= 0)
                    convExp[idx++= SPop(&stack);
                SPush(&stack, tok);
                break;
            }    
        }
    }
 
    while(!SIsEmpty(&stack))            //스택에 남아있는 모든 연산자를
        conExp[idx++= SPop(&stack);    //배열 convExp로 이동한다
    strcpy(exp, convExp);                //변환된 수식을 exp에 복사하고,
    free(convExp);                        //convExp는 소멸시킨다
}
 
 
//후위 표기법으로 표현된 수식을 계산하는 프로그램 구현
//1. 피연산자는 무조건 스택으로 옮긴다
//2. 연산자를 만나면 스택에서 두개의 피연산자를 꺼내서 계산을 한다
//3. 계산결과는 다시 스택에 넣는다
int EvalRPNExp(char exp[])
{
    Stack stack;
    int expLen = strlen(exp);
    int i;
    char tok, op1, op2;
 
    StackInit(&stack);
    for (i=0; i<expLen; i++)
    {
        tok = exp[i];
        if(isdigit(tok))
        {
            SPush(&stack, tok-'0'//정수라면 숫자로 변환 후 스택에 push
        }
        else
        {
            op2 = SPop(&stack);
            op1 = SPop(&stack);
            swith(tok)
            {
            case '+':
                SPuch(&stack, op1+op2);
                break;
            case '-':
                SPush(&stack, op1-op2);
                break;
            case '*':
                SPush(&stack, op1*op2);
                break;
            case '/':
                SPush(&stack, op1/op2);
                break;
            }
        }
    }
    return SPop(&stack);    
}
 
 
cs
728x90