수식계산을 위해 고려해야할 두가지
1. 소괄호 먼저 연산
2. 연산자 우선순위를 근거로 연산
수식 표기법
중위 표기법 (infix notation) ex) 5 + 2 / 7
: 수식내에 연산 순서 정보 없음. 소괄호와 연산자 우선 순위를 정의하여 연산 순서 명시. 일반적인 수식은 바로 infix
전위 표기법 (prefix notation) ex) +5 / 2 7
후위 표기법 (postfix notation) ex) 5 2 7 / +
: 전위와 후위는 수식내에 연산순서의 정보가 담겨있다. 따라서 소괄호가 필요없고 연산 우선순위를 결정할 필요가 없다.
중위 표기법은 우리가 이해하기는 쉽다. 하지만 연산자 우선 순위를 조절하기 위해 소괄호가 필요하다.
따라서, 수식의 값을 구하기 쉽고 괄호없이 표현이 정확한 후위 표기법을 사용한다.
stack을 이용한 중위 -> 후위 수식 변환 과정
수식을 이루는 왼쪽 문자부터 하나씩 처리
1. 피 연산자는 오른쪽(변환된 수식이 위치할 자리)으로 이동
2. 연산자는 쟁반(stack)으로 이동
3. stack에 기존 연산자가 있을 때는?
stack에 위치한 연산자 우선 순위가 높으면
-> stack에 위치한 연산자를 꺼내 오른쪽으로 이동, 새 연산자를 stack으로 이동
stack에 위치한 연산자 우선 순위가 낮으면
-> stack에 위치한 연산자 위에 새 연산자 쌓음
서로의 연산자 우선 순위가 같다면
-> stack에 위치한 연산자 우선 순위가 높다고 가정하여 오른쪽으로 옮기고, 새 연산자를 stack으로 이동
둘 이상의 연산자가 쌓여있을 때에도 1:1로 계속 비교하여 진행해야 한다.
4. 마지막으로 위와 같이 쟁반(stack)의 데이터 차례대로 꺼내 옮김
중위 -> 후위 수식 변환 과정 : 소괄호 고려
) 연산자를 만나면 stack의 data를 ( 연산자 만날때까지 오른쪽으로 옮긴다. 그리고 괄호는 사라진다.
위의 그림에서도 이 같은 과정을 거친 후, / 연산자를 stack으로 4를 오른쪽으로 옮기고 마지막으로 stack의 데이터( / )를 꺼내 옮긴다.
-> 1 2 3 * + 4 /
중위 -> 후위 수식 변환 프로그램 구현
위의 과정을 코드로 옮기면 다음과 같다.
일단 stack의 정의와 연결리스트 초기화는 이전 포스팅 참조 (http://smart2016.tistory.com/113)
1 2 3 4 5 6 7 8 9 | void ConvToRPN(char MExp[]) { . . . .} int main(void) { char MExp[] = "3-2+4"; ConvToRPN(MExp); . . . . } | cs |
중위 표기법 수식을 배열에 담아 함수 인자로 전달, 호출된 함수의 인자 MExp에는 변환된 수식이 담기도록 한다.
아래 함수는 연산자의 우선 순위 정보를 반환하는 함수로 ConvToRPN의 helper function이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int GetOpPrec(char op)// Operator precedence { switch (op) { case '*': case '/': return 3; // 최우선 연산 순위 case '+': case '-': return 2; case '(': return 1; } return -1; // 연산자가 아닐 때 return값 } | cs |
( 는 ) 연산자가 등장할 때까지 stack에 남아있어야 하기 때문에 우선순위가 가장 낮다 ( 1 )
) 연산자는 stack으로 가지 않기 때문에 return 값 정의 없음
두 연산자 우선순위 비교결과를 반환하는 함수 - ConvToRPN의 실질적인 helper function
op1이 우선순위가 높다면 1 반환, op2가 높다면 -1 반환, 같다면 0 을 반환하도록 정의
1 2 3 4 5 6 7 8 9 10 11 12 | int WhoPrecOp(char op1, char op2) { int op1Prec = GetOpPrec(op1); int op2Prec = GetOpPrec(op2); if (op1Prec > op2Prec) return 1; else if (op1Prec < op2Prec) return -1; else // 우선순위 같음 return 0; } | cs |
후위 표기법의 수식 계산
1. 피연산자는 stack으로 옮김
2. 연산자를 만나면 stack에서 두개의 피연산자를 꺼내 계산
3. 계산결과를 다시 stack에 넣음
stack을 이용한 계산기
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
| //stack을 이용한 계산기
#include <stdio.h>
#include <stdlib.h>
#define STACKSIZE 100
int stack[STACKSIZE];
int top;
////////////////////////////////////////////////////////////////////////// 스택 초기화
void init_stack(void)
{
top = -1;
}
////////////////////////////////////////////////////////////////////////// push
int push(int t)
{
if (top >= STACKSIZE - 1)
{
printf("\n Stack overflow.");
exit(1);
}
stack[++top] = t;
return t;
}
////////////////////////////////////////////////////////////////////////// pop
int pop(void)
{
if (top < 0)
{
printf("\n Stack underflow.");
exit(1);
}
return stack[top--];
}
////////////////////////////////////////////////////////////////////////// 스택 top의 값 구한다.
int get_stack_top(void)
{
return (top < 0) ? -1 : stack[top];
}
////////////////////////////////////////////////////////////////////////// 스택이 비어있는지 확인
int is_stack_empry(void)
{
return (top < 0);
}
////////////////////////////////////////////////////////////////////////// 연산자 확인
// 이후 확장시 변경
int is_operator(int k)
{
return (k == '+' || k == '-' || k == '*' || k == '/');
}
////////////////////////////////////////////////////////////////////////// 피연산사 확인
int is_operand(int src)
{
return (src >= '0' && src <= '9');
}
////////////////////////////////////////////////////////////////////////// 후위 표기법 적합성 확인
int is_legal(char *s)
{
int f = 0;
while (*s)
{
while (*s == ' ') // remove space
s++;
if (is_operator(*s))
f--; // 연산자이면 감소
else
{
f++; // 피연산자이면 증가
while (*s != ' ')
s++;
}
if (f < 1) break; // f가 1보다 적으면 언더플로
s++;
}
return (f == 1); // 피연산자의 수 - 연산자의 수 = 1;
}
////////////////////////////////////////////////////////////////////////// 연산자의 우선순위를 수치로 변환
int precedence(int op)
{
if (op == '(') return 0;
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
else return 3;
}
////////////////////////////////////////////////////////////////////////// 중위표기법을 후위표기법으로 변환
// dst : 후위표기법으로 변환한 식 저장, src : 중위 표기법이 저장된 인자
void postfix(char *dst, char *src)
{
// char c;
init_stack(); // 스택 초기화
while (*src) // 중위 표기법이 남아있는 동안
{
if (*src == '(') // '('를 만나면 스택에 푸시
{
push(*src);
src++;
}
else if (*src == ')') // ')'를 만나면 '('가 나올 때까지 팝
{
while (get_stack_top() != '(')
{
*dst++ = pop();
*dst++ = ' ';
}
pop(); // '('는 버린다.
src++;
}
else if (is_operator(*src)) // 연산자
{
while (!is_stack_empry() && precedence(get_stack_top()) >= precedence(*src))
{ // 자신보다 높은 우선순위의 연산자는 스택에서 팝
*dst++ = pop();
*dst++ = ' ';
}
push(*src); // 자신을 push
src++;
}
else if (is_operand(*src))
{
do {
*dst++ = *src++;
} while (is_operand(*src));
*dst++ = ' ';
}
else src++;
// 설정한 문자 외의 값들은 무시한다. 공백, 오타 등.
}
while (!is_stack_empry())
{ // 스택에 남은 연산자를 모두 팝
*dst++ = pop();
*dst++ = ' ';
}
dst--; // ' '제거
*dst = 0;
}
int calc(char *p)
{ // 후위표기법 수식을 연산
int i;
init_stack();
while (*p)
{
if (is_operand(*p))
{
i = 0;
do {
i = i * 10 + *p - '0';
p++;
} while (*p >= '0' && *p <= '9');
push(i); // 피연산자는 스택에 푸시.
}
else if (*p == '+')
{ // 연산자가 팝 두 번해서 계산하여 그 결과를 푸시
push(pop() + pop());
p++;
}
else if (*p == '*')
{
push(pop() * pop());
p++;
}
// '-'와 '/'는 교환 법칙이 성립하지 않는다. push(pop() - pop())에서 어느 pop이 먼저인지 컴파일러에 따라 다르다.
else if (*p == '-')
{
i = pop();
push(pop() - i);
p++;
}
else if (*p == '/')
{
i = pop();
push(pop() / i);
p++;
}
else p++;
}
return pop(); // 연산 결과
}
int main()
{
int r;
char exp[256];
char argv[] = { 0 };
char postExp1[50] = { 0 };
printf("수식 입력: ");
fgets(postExp1, sizeof(postExp1), stdin);
postExp1[strlen(postExp1) - 1] = 0;
postfix(exp, postExp1); // 후위표기법으로 바꿈
printf("\nPostfix : %s", exp);
if (!is_legal(exp)) // 수식이 적법한가 점검
{
printf("\n Expression is not legal!");
exit(1);
}
r = calc(exp); // 연산
printf("\nAnswer : %d \n", r);
return 0;
} |
'Data Structure' 카테고리의 다른 글
자료 구조 - Stack (0) | 2016.11.05 |
---|---|
연결리스트와 파일 입출력 (0) | 2016.11.02 |
이진 트리의 구현과 순회(Traversal) (0) | 2016.09.12 |
Binary Tree (0) | 2016.09.12 |
Doubly Linked List (0) | 2016.09.09 |