Write a C function that transforms a prefix expression into a postfix one. Carefully state any assumptions you make regarding the input. How much time and space does your function take?

전위표기법의 식을 후위표기법으로 변환하는 함수를 작성하는 문제이다. 이 문제를 해결하기 위해서 입력에 대한 몇 가지 가정을 하였다.

  • 식의 입력은 문자열로 한다.
  • 입력되는 식은 모두 표현될 수 있는 전위표기법의 식이다. 잘못된 식인 경우 오류가 발생한다. (제대로 실행이 안되며, 전위표기법인지 확인하는 예외처리 매커니즘이 필요하다)
  • 입력되는 식의 길이가 80이하로 가정한다.
  • 연산자를 제외한 모든 문자는 피연산자로 간주한다. (_ ^ 등도 피연산자)

이 문제를 해결하는 방법에 대해 생각해 보았다.

  • 중위표기법으로 변형했다가 다시 후위표기법으로 변형한다.
  • 트리를 이용한다.
  • 스택을 이용한다.
  • 재귀 함수를 이용한다.

첫번째와 두번째경우 중간과정을 거치는 것 해법이므로, 일단 전위표기법을 한 번에 후위표기법으로 만드는 함수를 구현하기로 한다. 세번쨰와 네번째의 기본적인 과정은 똑같다. 연산자가 단 한 개인 전위표기법을 후위표기법으로 바꾸는 것은 매우 쉽다.

operator operand1 operand2   (prefix expression)
operand1 operand2 operator   (postfix expression)

이렇게 간단한 식은 변형하기가 매우 쉽다. 연산자가 매우 많은 복잡한 식도 이 기본적인 룰을 이용하면 고칠 수 있다. 복잡한 식도 기본적으로는 이 하나의 룰로 변형이 되기 때문이다. 이 방법을 이용해 재귀적으로 문제를 쉽게 풀 수 있다.

#define TRUE 1
#define FALSE 0

#include <stdio.h>
#include <string.h>

char *PreToPostFix(char *, char *);
int isoperator(char );

char *PreToPostFix(char *Pre, char *Post)
{
    int index; // operand1과 operand2의 사이를 가르키는 index
    int NumOperator=0; // 피연산자를 구분할 때 쓰이는 연산자 갯수 카운터
    int NumOperand=0; // 피연산자를 구분할 때 쓰이는 피연산자 갯수 카운터

    int PreLen = strlen(Pre); // 주어진 prifix expression의 길이

    char Operator[2]; // 연산자를 저장할 문자열
    char operand1[80]; // 피연산자1을 저장할 문자열
    char operand2[80]; // 피연산자2를 저장할 문자열
    if(PreLen == 1) { // 길이가 1인 경우에 자기자신을 return
        strcpy(Post, Pre);
        return Post;
    }

    *operand1 = '\0'; // string.h 함수를 이용하기위해 초기화
    *operand2 = '\0';                    

    *Operator        = *Pre; // operator를 얻는다
    *(Operator+1)    = '\0';

    for(index=1; index<PreLen; index++) // 피연산자1과 피연산자 2를 구분하기 위한 index 찾기
    {
        if(isoperator(Pre[index]))
            NumOperator++;
        else
            NumOperand++;

        if(NumOperator +1 == NumOperand)
            break;
    }

    strncat(operand1, Pre+1, index); // 피연산자1과 피연산자2를 각각 변수에 대입
    strcat(operand2, Pre+index+1);

    PreToPostFix(operand1, operand1); // 재귀적으로 각 연산자의 postfix형을 구한다.
    PreToPostFix(operand2, operand2);

    strcpy(Post, strcat(strcat(operand1, operand2), Operator));    //결과값을 구한다.

    return Post; // 결과값 return
}

int isoperator(char ch) // 피연산자와 연산자를 구별하기 위한 함수
{
    return (ch == '*' || ch == '/' || ch == '+' || ch == '-' || ch == '%');
}

이 소스 코드에 대한 Space complexity와 Time complexity를 구해본다. 이 소스에서 함수가 한번 실행되는데 필요한 메모리 공간은 다음과 같다.

변수 바이트
char *Pre 4
char *Post 4
int index 4
int NumberOperator 4
int NumberOpreand 4
int PreLen 4
char Operator[2] 2
char operand1[80] 80
char operand2[80] 80
return adresss 4
총 바이트 수 190

길이가 n인 prefix 로 표현된 식을 입력받았을 때 recursive로 호출되는 함수의 개수는 n-1 이므로, 공간복잡도는 190*(n-1) 이다.

시간복잡도는 가장 최악의 경우 **abcdefgef 와 같이 operand1이 최대로 길때이다. 이유는, 함수내 operand1과 operand2를 구별할 때 for문이 가장 오래 돌기 때문이다. 그 외의 stirng.h의 함수의 경우, 길이가 n정도 되는 문자열을 쭉 읽으면서 처리하는 것이기 때문에 O(n)이다. 따라서 함수를 쭉 한번 읽는 것의 최대차항 계수는 ‘Cn + …’ (C는 상수) 로 나타내 수 있다. 그런데 함수를 재귀적으로 n-1 번 호출하기 때문에, 단계의 수는 대략 (Cn + …)(n-1) 가 됨을 알 수 있다. 따라서 이 함수의 시간 복잡도는 O(n2)가 된다.

그 외에 스택을 이용하여 구현할 수 있는 방법은 어렵지 않게 생각 할 수 있다.