트리로 수식 표현과 계산
들어가는 말
스택으로 수식 변환과 계산에서는 전위, 중위, 후위 표현식이 무엇인지 살펴봤다.
스택을 이용해 중위 표현식을 후위 표기식으로 바꾸고, 그 결과를 계산하는 과정까지 다뤘다.
수식은 트리 자료구조로도 표현할 수 있으며, 이를 수식 트리라고 한다.
이 글에서는 후위 표기식으로 수식 트리를 만들고, 수식 트리를 이용해 사칙연산을 계산하는 방법을 살펴본다.
수식 트리
전위, 중위, 후위 표기식은 모두 수식 트리로 표현할 수 있다.
이 중 후위 표기식은 스택을 이용해 수식 트리를 비교적 간단히 만들 수 있다.
중위 표기식을 후위 표기식으로 변환하는 방법은 이전 글에서 다뤘으므로, 이 글에서는 변환이 끝난 후위 표기식이 주어진다고 가정한다.
후위 표기식 23+4*는 다음과 같은 수식 트리로 표현할 수 있다.
*
/ \
+ 4
/ \
2 3
수식 트리에서 단말 노드는 피연산자를, 내부 노드는 연산자를 나타낸다.
사칙연산은 모두 이항 연산이므로 각 내부 노드는 왼쪽과 오른쪽 자식을 가진다.
자식 노드는 숫자일 수도 있고, 다른 연산과 피연산자로 이루어진 서브트리일 수도 있다.
수식 트리에서는 노드를 방문하는 순서에 따라 서로 다른 표기식을 얻을 수 있다.
위 트리를 전위, 중위, 후위 순회하면 각각 다음과 같은 결과를 얻는다.
루트 노드를 먼저 방문한 뒤 왼쪽과 오른쪽 서브트리를 방문하는 전위 순회의 결과는 *+234이다.
왼쪽 서브트리, 루트 노드, 오른쪽 서브트리 순으로 방문하는 중위 순회의 결과는 2+3*4이다.
다만 수식 트리의 연산 순서를 그대로 나타내려면 (2+3)*4처럼 괄호를 추가해야 한다.
왼쪽과 오른쪽 서브트리를 모두 방문한 뒤 루트 노드를 방문하는 후위 순회의 결과는 23+4*이다.
수식 트리는 후위 순회를 이용해 계산할 수 있다.
단말 노드에서는 숫자 값을 반환하고, 내부 노드에서는 왼쪽과 오른쪽 서브트리의 계산 결과에 연산자를 적용한다.
소스 코드
#include <cstdio>
#include <stack>
#include <string>
using namespace std;
struct Node
{
char value;
Node* left;
Node* right;
};
static Node* create_node(char value)
{
Node* node = new Node();
node->value = value;
node->left = nullptr;
node->right = nullptr;
return node;
}
Node* build_expression_tree(const string* postfix_expression)
{
stack<Node*> node_stack;
for (char ch : *postfix_expression)
{
if (ch >= '0' && ch <= '9')
{
node_stack.push(create_node(ch));
continue;
}
Node* operator_node = create_node(ch);
operator_node->right = node_stack.top();
node_stack.pop();
operator_node->left = node_stack.top();
node_stack.pop();
node_stack.push(operator_node);
}
return node_stack.top();
}
int evaluate_expression_tree(const Node* root)
{
if (root->left == nullptr && root->right == nullptr)
return root->value - '0';
int left_value = evaluate_expression_tree(root->left);
int right_value = evaluate_expression_tree(root->right);
switch (root->value)
{
case '+':
return left_value + right_value;
case '-':
return left_value - right_value;
case '*':
return left_value * right_value;
case '/':
return left_value / right_value;
}
return 0;
}
void destroy_expression_tree(Node* root)
{
if (root == nullptr)
return;
destroy_expression_tree(root->left);
destroy_expression_tree(root->right);
delete root;
}
int main()
{
string postfix_expression = "23+4*";
Node* root = build_expression_tree(&postfix_expression);
int expression_result = evaluate_expression_tree(root);
printf("%d\n", expression_result);
destroy_expression_tree(root);
return 0;
}
구현 설명
수식 트리 만들기
build_expression_tree() 함수는 후위 표기식의 문자를 왼쪽부터 하나씩 읽어 수식 트리를 만든다.
숫자를 읽으면 그 숫자를 값으로 갖는 노드를 만들어 스택에 넣는다.
연산자를 읽으면 해당 연산자를 값으로 가지는 연산자 노드를 만든다.
이후 스택의 맨 위에서 노드 두 개를 꺼내 연산자 노드의 자식으로 연결하고, 연산자 노드를 스택에 넣는다.
따라서 연산자 노드는 숫자를 값으로 가지는 노드뿐 아니라 다른 연산자 노드를 루트로 하는 서브트리도 자식으로 가질 수 있다.
| 읽은 문자 | 스택 상태 | 처리 |
|---|---|---|
2 |
2 |
2를 값으로 갖는 노드를 스택에 넣는다. |
3 |
2, 3 |
3을 값으로 갖는 노드를 스택에 넣는다. |
+ |
(2+3) |
3을 값으로 갖는 노드를 오른쪽 자식으로, 2를 값으로 갖는 노드를 왼쪽 자식으로 연결한 + 노드를 넣는다. |
4 |
(2+3), 4 |
4를 값으로 갖는 노드를 스택에 넣는다. |
* |
((2+3)*4) |
4를 값으로 갖는 노드를 오른쪽 자식으로, (2+3) 서브트리의 루트 노드를 왼쪽 자식으로 연결한 * 노드를 넣는다. |
모든 문자를 처리한 뒤 스택에 남은 하나의 노드가 수식 트리의 루트가 된다.
수식 트리 계산하기
evaluate_expression_tree() 함수는 왼쪽과 오른쪽 서브트리의 값을 먼저 계산한 뒤 루트 연산자를 적용한다.
이는 노드를 마지막에 방문하는 후위 순회와 같은 순서다.
단말 노드에서는 저장된 숫자를 반환하고, 연산자 노드에서는 두 자식의 계산 결과에 해당 연산을 적용한 값을 반환한다.
예제의 후위 표기식 23+4*를 계산한 결과는 20이다.