기록하는삶

[코딩 테스트 연습(파이썬/Python)] 백준(BOJ) 1918번 _ 후위 표기식 본문

백준(Python)/구현(implementation)

[코딩 테스트 연습(파이썬/Python)] 백준(BOJ) 1918번 _ 후위 표기식

mingchin 2022. 2. 7. 23:22
728x90
반응형

https://www.acmicpc.net/problem/1918

 

1918번: 후위 표기식

첫째 줄에 중위 표기식이 주어진다. 단 이 수식의 피연산자는 알파벳 대문자로 이루어지며 수식에서 한 번씩만 등장한다. 그리고 -A+B와 같이 -가 가장 앞에 오거나 AB와 같이 *가 생략되는 등의

www.acmicpc.net

[문제]

수식은 일반적으로 3가지 표기법으로 표현할 수 있다. 연산자가 피연산자 가운데 위치하는 중위 표기법(일반적으로 우리가 쓰는 방법이다), 연산자가 피연산자 앞에 위치하는 전위 표기법(prefix notation), 연산자가 피연산자 뒤에 위치하는 후위 표기법(postfix notation)이 그것이다. 예를 들어 중위 표기법으로 표현된 a+b는 전위 표기법으로는 +ab이고, 후위 표기법으로는 ab+가 된다.

이 문제에서 우리가 다룰 표기법은 후위 표기법이다. 후위 표기법은 위에서 말한 법과 같이 연산자가 피연산자 뒤에 위치하는 방법이다. 이 방법의 장점은 다음과 같다. 우리가 흔히 쓰는 중위 표기식 같은 경우에는 덧셈과 곱셈의 우선순위에 차이가 있어 왼쪽부터 차례로 계산할 수 없지만 후위 표기식을 사용하면 순서를 적절히 조절하여 순서를 정해줄 수 있다. 또한 같은 방법으로 괄호 등도 필요 없게 된다. 예를 들어 a+b*c를 후위 표기식으로 바꾸면 abc*+가 된다.

중위 표기식을 후위 표기식으로 바꾸는 방법을 간단히 설명하면 이렇다. 우선 주어진 중위 표기식을 연산자의 우선순위에 따라 괄호로 묶어준다. 그런 다음에 괄호 안의 연산자를 괄호의 오른쪽으로 옮겨주면 된다.

예를 들어 a+b*c는 (a+(b*c))의 식과 같게 된다. 그 다음에 안에 있는 괄호의 연산자 *를 괄호 밖으로 꺼내게 되면 (a+bc*)가 된다. 마지막으로 또 +를 괄호의 오른쪽으로 고치면 abc*+가 되게 된다.

다른 예를 들어 그림으로 표현하면 A+B*C-D/E를 완전하게 괄호로 묶고 연산자를 이동시킬 장소를 표시하면 다음과 같이 된다.

결과: ABC*+DE/-

이러한 사실을 알고 중위 표기식이 주어졌을 때 후위 표기식으로 고치는 프로그램을 작성하시오.

 

[입력]

첫째 줄에 중위 표기식이 주어진다. 단 이 수식의 피연산자는 알파벳 대문자로 이루어지며 수식에서 한 번씩만 등장한다. 그리고 -A+B와 같이 -가 가장 앞에 오거나 AB와 같이 *가 생략되는 등의 수식은 주어지지 않는다. 표기식은 알파벳 대문자와 +, -, *, /, (, )로만 이루어져 있으며, 길이는 100을 넘지 않는다. 

[출력]

첫째 줄에 후위 표기식으로 바뀐 식을 출력하시오

 

[아이디어]

1) 알파벳은 무조건 순서대로 나온다.

2) 알파벳을 순서대로 쌓아나가면서, (+,-)가 나왔을 때와 (*,/)가 나왔을 때 각각 적절한 로직을 수행한다.

3) 사칙연산은 stack에서 관리하면서, 특정 조건을 만족했을 때 뒤에 이어붙인다.

3-1) (+,-)는 연산을 기준으로 완성된 후위 표기식끼리의 연산을 취해야한다. 즉, X + Y = XY+로 기록되어야한다. 다만, X,Y가 각각 연산이 완료되어야 할 덩어리임을 확인하기 위해서는 그 뒤에 등장하는 (+,-)를 만나거나, 전체 반복문이 종료되어야 한다. 즉, (+,-)가 등장하면 일단 stack에 포함된 모든 연산을 역순으로 가져다 붙인 뒤, (+,-)를 다시 stack에 추가해둔다.

3-2) (*,/)는 (+,-)보다 우선되는 연산이다. 다만 이미 'A*B*C'의 두 번째 '*'연산처럼 (*,/)가 앞에 등장한 적이 있는 경우에는 해당 연산들이 먼저 수행된 후 이후 연산이 진행되는 'AB*C*'로 바뀌어야 하므로, stack에 포함된 연산들을 역순으로 가져다 붙이되, (*,/) 보다 우선될 수 있는 (*,/)들만 가져다 붙인다. 이후 해당 (*,/)를 다시 stack에 넣어둔다. 즉, stack에 보관할 수 있는 연속된 (*,/)의 개수는 최대 1개이다.

4) 괄호는 따로 생각한다. 괄호 여부는 시작 괄호를 stack에 담아두었다가, 닫는 괄호가 나왔을 때 시작 괄호 이후에 등장한 모든 연산을 역순으로 가져다 붙인다. 괄호의 쌍이 만들어질 때마다 이를 해결하고 넘어간다고 생각하면 된다. 이후 한번 더 stack.pop()을 해서 시작 괄호를 제거한다.

 

s = input()
tmp = []
ans = ''
for x in s:
    if x.isalpha():
        ans+=x
    elif x == '+' or x=='-':
        while tmp and tmp[-1]!='(':
            ans+=tmp.pop()
        tmp.append(x)
    elif x == '*' or x=='/':
        while tmp and (tmp[-1]=='*' or tmp[-1]=='/'):
            ans+=tmp.pop()
        tmp.append(x)
    elif x == '(':
        tmp.append(x)
    elif x == ')':
        while tmp and tmp[-1]!='(':
            ans+=tmp.pop()
        tmp.pop()
while tmp:
    ans+=tmp.pop()
print(ans)

아이디어 자체는 금방 떠올렸는데,, (*,/)가 반복 등장하는 경우가 좀 헷갈렸다. 그리고 처음에는 tmp(stack)가 비었을 때와 그렇지 않을 때를 나누어 작성했는데, -1 indexing은 비어 있어도 사용할 수 있어 편하다. 괄호를 생각 안하고 푼 뒤에, 괄호를 따로 생각하는게 더 쉬운 것 같다.

728x90
반응형