http://blog.daum.net/broodsc/2714333
오늘은 스택을 이용한 CALC 유틸리티(이하 CALC)에 관해서 설명(??)하겠습니다. ㅋ
CALC는 그냥 계산기 정도로 생각하시면 될듯...
CALC 유틸리티
수식을 중위표기법(Infix notation)으로 받아서 그걸 후위표기법(Postfix notation)으로 바꾸고...
이 후외 표기법의 수식을 스택을 이용하여 연산하여서 화면에 출력하게 하는 것...
이게 이 프로그램의 작동방식(??) 입니다. ;;;
여기서도 제한을 둡시다.
이 CALC는 간단한 정수와 사칙연산만 가능하게 한다는것.
그리고 입력시 스페이스를 넣으면 원하지 않으면 결과가 나올 수도 있다는 것.
뭐 다른 기능을 포함시키는것도 이 소스를 바탕으로 개선하시면 될듯 합니다.
방법 )
1. 수식의 표기법
보통 우리가 사용하는 수식은 뭘까요??
2 + 3 * (1 + 2) 이런 수식이죠??
이런걸 중위표기법(Infix notation)이라고 한답니다.
이유는?? 연산자가 두 피연산자 사이에 있어서... ;;;
1 + 2 를 보면 피연산자 1, 2 사이에 연산자 '+' 가 있죠?? ㅋ
그럼 연산자가 뒤에나 앞에 올 수도 있을까요?? 정답은 "그렇다" 입니다.
1 + 2 를 전위표기법(Prefix notation)으로 고치면 + 1 2 가 되겠고...
후위표기법(Postfix notation)으로 고치면 1 2 + ㅇㅋ??
그렇다면 왜 중위표기법을 놔두고 후위표기법을 이용하여 계산을 할려고 하는지??
그 이유가 궁금하지 않으신가요?? ;;;
그 이유는 중위표기법은 우리가 이해하기는 쉽지만 괄호를 만드시 써야하는 단점이 있다는 ;;;
위에보면 2 + 3 * (1 + 2) 이 식 있죠??
이 식에서 괄호를 제외한다면... 2 + 3 * 1 + 2 ...
두개의 수식이 서로 같나요?? 다르죠??
그럼 후위표기법은 괄호 없이도 가능하다는 말인가?? 정답은 "그렇다" 입니다. ㅋ
2. 중위표기법을 후위표기법으로 변환하는 방법 1
우선 뭄풀기(쫌 힘들지만 ;;) 단계로... 이해부터 하기 위해서...
한가지 제한을 두고 합시다.
뭐냐하면... 수식을 쓸때 무조건 괄호를 씌우는걸로...
예를들면 2+3*2 같은 경우에도 (2+(3*2)) 이런 식으로...
그럼 이걸 어떻게 후위표기법으로 변환할까요??
1. '(' 문자는 무시하고 넘어간다.
2. 피연산자는 그대로 출력한다.
3. 연산자는 스택에 푸시한다.
4. ')' 를 만나면 스택에서 팝하여 출력한다.
예를 들어 봅시다. (A + (B * C)) 라는 식...
'('는 무시한다고 그랬으니깐... 넘어가고 그 다음 'A'
'A'는 여기서 피연산자죠?? 그렇다면 출력. ( 출력 : A | 스택 : )
그 다음 '+' 이건 연산자... 그러니깐 푸시 ( 출력 : A | 스택 : + )
다음은 '(' ... 이건 무시.
그 다음 'B' 피연산자니깐 출력 ( 출력 : A B | 스택 : + )
다음은 '*' 연산자니깐 푸시 ( 출력 : A B | 스택 : * + )
그 다음 'C' 피연산자이므로 출력 ( 출력 : A B C | 스택 : * + )
그 다음 ')' ...
')' 만나면 스택에서 팝하여 출력이니깐... (출력 : A B C * | 스택 : + )
그 다음 또 ')'를 만났으니깐... 팝 하여 출력 (출력 : A B C * + | 스택 : )
그럼 (A + (B * C)) 의 후위표기법은 A B C * + 라는 결과가 나왔죠??
이제 이걸 가지고 C로 만들어 봅시다.
void postfix1(char *dst, char *src)
{
char c;
init_stack(); /* 스택을 초기화 */
while(*src)
{
if(*src==')') /* ')'를 만나면 팝하여 출력(dst에 저장) */
{
*dst++=pop();
*dst++=' '; /* 문자의 구분을 위해 */
src++;
}
else if(*src=='+' || *src=='-' || *src=='*' || *src=='/')
{ /* 연산자이면 스택에 푸시 */
push(*src);
src++;
}
else if(*src>='0' && *src<='9')
{ /* 숫자는 피연산자. 숫자를 그대로 복사 */
do
{
*dst++=*src++;
} while(*src>='0' && *src<=9');
*dst++=' ';
}
else /* 그 외에는 무시 */
src++;
}
*dst=NULL; /* 마지막에 NULL 문자 추가해서 문자열로... */
}
이건 쫌 쉽죠?? ;;;
3. 중위표기법을 후위표기법으로 변환하는 방법 2
위에 방법은 괄호가 꼭 있어야 했습니다.
그렇다면 이제 괄호가 없어도 우선순위에 맞춰서 변환하는걸 해 봐야겠죠??
이게 실제적으로 더 편리하고 유용한거니깐...
일단 우선순위를 고려해 줘야하니깐... 연산자별로 우선순위를 줍시다.
'(' 는 0, '+' 와 '-' 는 1, '*' 와 '/' 는 2 ...
1. '(' 를 만나면 스택에 푸시한다.
2. ')' 를 만나면 스택에서 '(' 가 나올 때까지 팝하여 출력하고 '(' 는 팝하여 버린다.
3. 연산자를 만나면 스택에서 그 연산자보다 낮은 우선순위의 연산자를 만날 때까지 팝하여 출력한 뒤에 자신을 푸시한다.
4. 피연산자는 그대로 출력한다.
5. 모든 입력이 끝나면 스택에 있는 연산자들을 모두 팝하여 출력한다.
후... 역시 이 책의 최대 약점이 또 다시 느껴지는군요...
직접 만들어볼 틈을 주기전에 이런 알고리즘을 제공하니깐... ;;;
보고 또 보고 해서 우리걸로 만들면 되겠죠?? 전 그렇게 할 수 있다고 스스로 믿을겁니다. ^^
예를 들어 봅시다.
(2 * (3 + (6 / 2) + 2) / 4 + 3 ... 차례대로... (그림 눌러서 보세요. 3자가 잘 안보이네요 ㅋ)
휴~
이걸 가지고 함수를 만들기 전에...!!
몇가지 간단한 함수부터 만들고 넘어갑시다. ^^
먼저...
int get_stack_top(void)
{
return (top < 0) ? -1 : stack[top];
}
이 함수는 스택의 최상단값을 리턴해주는 겁니다.
푸시될 연산자보다 높은 순위의 연산자가 스택의 상단에 있는지 없는지 보기 위해서...
그 다음 주어진 문자가 연산자인지 아닌지 판별하는 함수
int is_operator(int k)
{
return (k == '+' || k == '-' || k == '*' || k == '/');
}
두개 다 간단하죠??
그 다음... 연산자 우선순위를 수치로 바꿔주는 함수
int precedence(int op)
{
if (op == '(') return 0;
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
else
return 3;
}
흠... 이제 이 세 함수를 사용해서... 중위표기법 → 후위표기법 함수를 만들어 볼까요?? ;;;
void postfix(char *dst, char *src)
{
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_empty() &&
precedence(get_stack_top()) >= precedence(*src))
{ /* 우선순위가 높은 연산자들을 모두 팝 */
*dst++ = pop();
*dst++ = ' ';
}
push(*src); /* 그리고 푸시 */
src++;
}
else if (*src >= '0' && *src <= '9') /* 피연산자는 그냥 출력 */
{
do
{
*dst++ = *src++;
} while (*src >= '0' && *src <= '9');
*dst++ = ' ';
}
else
src++;
}
while (!is_stack_empty()) /* 모두 끝났으면 스택에 있는 모든 내용을 팝 */
{
*dst++ = pop();
*dst++ = ' ';
}
dst--;
*dst = 0;
}
4. 후위표기법 수식의 평가
후위 표기법 계산을 나타내는건 의외(??)로 쉽습니다.
1. 숫자를 만나면 스택에 푸시
2. 연산자를 만나면 두번 팝해서 그 두 데이터를 가지고 연산한 다음 다시 스택에 푸시
쉽... 죠?? 그러리라 믿습니다. ㅋ
예를들어 2 + 3 * 4 ... 이걸 후위표기법으로 나타내면 2 3 4 * +
2 3 4 는 숫자이므로 차례대로 푸시 ( 스택 : 4 3 2 )
그 다음 * ... '*' 는 연산자니깐 팝 두번하면 4 3 이거 두개를 * 연산해주면 12
12을 다시 푸시 ( 스택 : 12 2 )
그 다음 + ... '+' 는 연산자니깐 팝 두번하면 12 2 이거 두개를 + 연산하면 14 끝 !!
간단하죠??
단 한가지만 조심하면 됩니다. '-' 와 '/' 연산...
'+' 나 '*' 는 교환법칙이 성립하므로 팝 두번하여 그냥 계산하면 됩니다.
그러나 '-' 또는 '/' 는 교환법칙이 성립안합니다. 그러므로 팝을 따로 해주어야합니다.
그냥 pop() - pop() 를 하면 앞에 팝이 먼저 실행될지 뒤에 팝이 먼지 실행될지 모르기때문에...
그래서 팝을 한번 해서 저장해 둔 뒤에 그 값과 다음에 팝 하는 값을 계산해 주어야함.
그럼 이제 계산하는 함수를 만들어 볼까요??
int calc(char *p)
{
int i;
init_stack();
while (*p)
{
if (*p >= '0' && *p <= '9')
{
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++;
}
else if (*p == '-')
{
i = pop();
push(pop() - i);
p++;
}
else if (*p == '/')
{
i = pop();
push(pop() / i);
p++;
}
else
p++;
}
return pop();
}
한가지 눈여겨 볼것 더... 바로 숫자열을 정수로 변환하는 것...
atoi() 라는 아시는 분이라면 쉽게 이해하실텐데...
int atoi(char *s)
{
int
i=0;
while(*s>='0' && *s<='9')
i = i * 10 + *s++ - '0';
return i;
}
이게 바로 atoi() 함수입니다. 보시고 쪼끔만 생각하시면 이해하실듯...
결과 )
실행은
"CALC(파일이름) 수식" 하면 됩니다.
단, 스페이스를 사이에 집어 넣으면 원치 않은 결과가 나오니깐...
(공백이 있다면 argc 3으로 컴퓨터가 이해하니깐 ;;;) 조심~
'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글
Red-Black Tree (0) | 2012.11.01 |
---|---|
반올림&반내림 함수 , 소수점자리 반올림&반내림 함수 (0) | 2012.10.31 |
그래프 정리 (0) | 2012.10.31 |
AOE Network( 작업 공정의 최장, 최단 시간 알아보기 ) (0) | 2012.10.31 |
순환없는방향성그래프(DAG) 에서 사용할 수 있는 위상정렬(Topological Sort) [순서적공정모델링] (0) | 2012.10.31 |