Data Structure & Algorithm
[자료구조] 수식 평가_중위, 후위, 전위 (in python)
여뉴누
2023. 6. 18. 21:05
728x90
강의 출처) 숙명여자대학교 소프트웨어융합전공 강의 "자료구조", 이현자 교수님
[목차]
1. 수식 표기 방식
2. 후위 수식 계산
3. 중위 -> 후위 변환
1. 수식 표기 방식
(1) 중위 표기법
- 연산자를 두 피연산자 사이에 표기
- 사람이 이해하기 좋은 표현법
- 우선순위에 따라 연산 순서 결정
- 괄호에 의해 연산 순서 조정
(2) 후위 표기법
- 피연산자를 먼저 표시하고 연산자를 나중에 표시
- 컴퓨터가 이해하기 좋은 표현법
- 우선순위 상관없음
- 괄호 없음
(3) 전위 표기법
- 연산자를 먼저 표시하고 연산에 필요한 피연산자를 나중에 표기
(4) 중위 vs 후위 vs 전위
(5) 연산자 우선순위 (C 기준)
2. 후위 수식 계산
(1) 기본 과정
- 수식의 왼쪽부터 토큰(문자)을 읽어냄
- 읽어낸 토큰이 피연산자 -> 스택에 push
- 읽어낸 토큰이 연산자 -> 스택에 포함되어 있는 가장 최근에 추가된 두 개의 피연산자를 pop
- pop한 피연산자 두 개와 읽어낸 연산자로 연산을 수행 후 결과를 스택에 push
- 모든 연산이 끝나고 나면 스택 내에 연산의 결과인 숫자 하나만 남게 됨
(2) 코드 구현
- 두 가지 과정으로 분류 가능
- 과정 1) 읽어낸 토큰이 연산자인지 피연산자인지 파악
- 과정 2) 후위 수식 계산
과정 1) 읽어낸 토큰이 연산자인지, 피연산자인지 파악
def getSymtype(self, sym):
if sym == '+' : sym_type = 'Sym_PLUS'
elif sym == '-' : sym_type = 'Sym_MINUS'
elif sym == '*' : sym_type = 'Sym_TIMES'
elif sym == '/' : sym_type = 'Sym_DIVIDE'
elif sym == '%' : sym_type = 'Sym_MOD'
else: sym_type = 'Sym_OPERAND'
return sym_type
과정 2) 후위 수식 계산
def eval_postfix(self):
for sym in self.expr:
print(sym, end = ' ')
sym_type = self.getSymtype(sym)
# 과정 1)의 함수를 사용하여 연산자의 종류 또는 여부를 파악
if sym_type == 'Sym_OPERAND':
# 숫자이면
self.push(int(sym))
# int형으로 변환 후 스택에 바로 추가
else:
op2 = self.pop()
# op1 (연산자) op2 순으로 연산을 진행하기 위해
op1 = self.pop()
# op2는 가장 끝의 원소, op1은 그 전 원소를 추출
# 각 연산자의 종류에 맞게 연산 후 연산 결과를 다시 스택에 넣어줌
if sym_type == 'Sym_PLUS':
self.push(op1 + op2)
elif sym_type == 'Sym_MINUS':
self.push(op1 - op2)
elif sym_type == 'Sym_TIMES':
self.push(op1 * op2)
elif sym_type == 'Sym_DIVIDE':
self.push(op1 // op2)
elif sym_type == 'Sym_MOD':
self.push(op1 % op2)
return self.pop()
# 모든 원소에 대해 연산이 이루어지고 나면 연산의 결과인 수 하나밖에 스택에 남지 않음
-> 그걸 반환해주면 그게 바로 결과
결과)
e = Expression('57*9+34/-')
e.eval_postfix()
(3) 전체 코드
class Expression:
def __init__(self, expr):
self.stack = []
self.size = 100
self.expr = expr # 연산식을 문자열로 받아 self.expr에 저장
self.top = -1
# 스택 구현
def isEmpty(self):
return len(self.stack) == 0
def isFull(self):
return self.size == len(self.stack)
def push(self,item):
if not self.isFull():
self.top += 1
self.stack.append(item)
self.view()
else: print("stack is full")
def pop(self):
if not self.isEmpty():
self.top -= 1
return self.stack.pop()
else: print('stack is empty')
def view(self):
print(self.stack)
# 후위 수식 계산
def eval_postfix(self):
for sym in self.expr:
print(sym, end = ' ')
sym_type = self.getSymtype(sym)
if sym_type == 'Sym_OPERAND':
self.push(int(sym))
else:
op2 = self.pop()
op1 = self.pop()
if sym_type == 'Sym_PLUS':
self.push(op1 + op2)
elif sym_type == 'Sym_MINUS':
self.push(op1 - op2)
elif sym_type == 'Sym_TIMES':
self.push(op1 * op2)
elif sym_type == 'Sym_DIVIDE':
self.push(op1 // op2)
elif sym_type == 'Sym_MOD':
self.push(op1 % op2)
return self.pop()
# 각 연산자 파악
def getSymtype(self, sym):
if sym == '+' : sym_type = 'Sym_PLUS'
elif sym == '-' : sym_type = 'Sym_MINUS'
elif sym == '*' : sym_type = 'Sym_TIMES'
elif sym == '/' : sym_type = 'Sym_DIVIDE'
elif sym == '%' : sym_type = 'Sym_MOD'
else: sym_type = 'Sym_OPERAND'
return sym_type
3. 중위 -> 후위 변환
(1) 기본 과정 (컴퓨터에서)
- 수식을 왼쪽부터 읽어 가면서
- 읽은 피연산자는 읽는 대로 출력
- 읽은 연산자는 스택의 top 연산자와 우선순위 비교 후 처리
- case 1) top 연산자의 우선순위 >= 읽은 연산자 우선순위
: top 연산자를 pop -> 읽은 연산자는 스택에 push - case 2) top 연산자의 우선순위 < 읽은 연산자의 우선순위
: 읽은 연산자를 스택에 push - case 3) 더이상 읽어지는 것이 없음 (== eos)
: 스택에 남은 연산자를 모두 pop
- case 1) top 연산자의 우선순위 >= 읽은 연산자 우선순위
- 만약에 중위 수식에 괄호가 포함되어 있다 ?
- '(' 는 읽히면 바로 스택에 push
- '(' 이후 연산자들에 대해서는 위의 case1, 2, 3의 과정을 따름
- ')' 이 읽히면 '(' 이후로 스택에 push되어 있는 모든 연산자를 pop
(+) 식 변환 방식에 대한 나의 이해
후위 연산자를 컴퓨터에서 받아들이고 연산을 실행할 때 앞에 나와있는 연산자부터 실행한다. 따라서 먼저 실행되어야 하는 연산자들이 앞에 위치해야 한다. 그럼 먼저 실행되어야 하는 연산자란 무엇일까 ? 우선순위가 높은 연산자이거나 우선순위가 동일함에도 먼저 식에 등장한 연산자가 바로 먼저 실행되어야 하는 연산자이다. 따라서 top 연산자와 비교했을 때 top 연산자의 우선순위가 높거나 같으면(식에 먼저 등장했다는 의미) 먼저 top 연산자를 식으로 빼줘 식에서 앞쪽에 위치하게 해준 뒤, 이번에 입력받은 연산자는 스택에 넣어주는 것이다.
(2) 코드 구현
- 두 가지 과정으로 분류 가능
- 과정 1) 연산자 우선순위 배정
- 과정 2) 중위 수식의 후위 수식으로의 변환
과정 1) 연산자 우선순위 배정
def precedence(self, op): # 연산자 우선순위 배정 함수
if op == '(': return 0
elif op in ['+', '-']: return 1
elif op in ['*', '/', '%']: return 2
과정 2) 중위 수식의 후위 수식으로의 변환
def infix_postfix(self):
for token in self.expr:
# 수식을 문자열로 받아 문자열의 문자를 token이라는 변수에 하나씩 받아옴
print(f"{self.i}번째 연산자")
print("token: ", token)
if token.isalnum():
# token이 만약에 숫자이거나 알파벳이면
self.output += token
# 후위수식결과가 될 문자열에 token을 추가
elif token == '(':
# 괄호의 시작이 들어오면
self.push(token)
# 바로 스택에 push하고
elif token == ')':
# 괄호가 끝났다면
sym = self.pop()
# 괄호가 끝나기 바로 이전에 추가된 연산자를 sym 변수에 넣고
while sym != '(' and sym != None :
# 스택에서 읽어낸 연산자가 '('이거나 더이상 읽어낼 것이 없을 때 stop
self.output += sym
# 후위수식결과가 될 문자열에 sym을 추가
sym = self.pop()
# '(' 이후에 추가된 연산자들을 계속 반복적으로 삭제하고 가져옴
else:
# 알파벳, 숫자, 괄호 모두 다 아니면 = 연산자이면
while not self.isEmpty() and self.precedence(self.stack[-1]) >= self.precedence(token):
# 스택이 비어있지 않고 top의 우선순위가 현재 삽입하려는 연산자보다 높거나 같을 동안
sym = self.pop()
# top 연산자를 삭제 + 가져와서 sym에 넣어주고
self.output += sym
# 후위수식에 추가시켜줌
self.push(token)
# while문이 종료되었으면 현재 읽어낸 연산자를 push
# 아래 세 줄은 결과를 보기 위해 임의로 추가
#print("output: ", self.output)
#print()
#self.i += 1
while not self.isEmpty():
# 스택이 빌 때까지
self.output += self.pop()
# 스택에 남아있는 모든 연산자를 pop해서 후위수식에 추가
결과)
e = Expression('5*7+1')
e.infix_postfix()
print("최종 결과: ", e.output)
(3) 전체 코드
class Expression:
def __init__(self, expr):
self.stack = []
self.size = 100
self.expr = expr # 연산식을 문자열로 받아 self.expr에 저장
self.top = -1
self.output = '' # 후위 연산식으로 변환한 결과를 저장할 str형식의 공백 문자열
self.i = 1
# 스택 생성 (self.stack)
def isEmpty(self):
return len(self.stack) == 0
def isFull(self):
return self.size == len(self.stack)
def push(self,item):
if not self.isFull():
self.top += 1
self.stack.append(item)
self.view()
else: print("stack is full")
def pop(self):
if not self.isEmpty():
self.top -= 1
return self.stack.pop()
else: print('stack is empty')
def view(self):
print("stack: ",self.stack)
def precedence(self, op): # 연산자 우선순위 배정 함수
if op == '(': return 0
elif op in ['+', '-']: return 1
elif op in ['*', '/', '%']: return 2
def infix_postfix(self): # 중위 -> 후위 변환 함수
for token in self.expr:
print(f"{self.i}번째 연산자")
print("token: ", token)
if token.isalnum():
self.output += token
elif token == '(':
self.push(token)
elif token == ')':
sym = self.pop()
while sym != '(' and sym != None :
self.output += sym
sym = self.pop()
else:
while not self.isEmpty() and self.precedence(self.stack[-1]) >= self.precedence(token):
sym = self.pop()
self.output += sym
self.push(token)
# 아래 세 줄은 결과를 보기 위해 임의로 추가
#print("output: ", self.output)
#print()
#self.i += 1
while not self.isEmpty():
self.output += self.pop()
728x90