자료구조 알고리즘

스택을 활용한 계산기 만들기

khon98 2020. 12. 10. 15:49

중위 표기법

- 중위 표기법이란 일반적으로 사람이 수식을 표기할 때 사용하는 표기 방법

- 중위 표기법의 예시 : 7 * 5 + 3

 

후위 표기법

- 후위 표기법이란 컴퓨터가 계산하기에 편한 수식의 형태

- 후위 표기법에서 연산자는 뒤쪽에 위치

- 후위 표기법 예시 : 7 5 * 3 +

 

스택을 활용해 수식을 계산하는 방법

1. 수식을 후위 표기법으로 변환

2. 후위 표기법을 계산하여 결과를 도출

 

* 스택 구현하기

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { // 노드라는 구조체 정의
    char data[100]; // 하나의 문자열을 다룰수 있도록 함
    struct Node *next;
} Node;

typedef struct Stack {
    Node *top;
} Stack;

 

* 스택 함수 구현하기

void push(Stack *stack, char *data) {
    Node *node = (Node*)malloc(sizeof(Node));
    strcpy(node->data, data);
    node->next = stack->top;
    stack->top = node;
}

char* getTop(Stack *stack) {
    Node *top = stack->top;
    return top->data;
}

char* pop(Stack *stack) {
    if (stack->top == NULL) {
        printf("스택 언더플로우 발생.\n");
        return NULL;
    }
    Node *node = stack->top;
    char *data = (char*)malloc(sizeof(char) * 100);
    strcpy(data, node->data);
    stack->top = node->next;
    free(node);
    return data;
}

 

중위 표기법을 후위 표기법으로 바꾸는 방법

- 피연산자가 들어오면 바로 출력

- 연산자가 들어오면 자기보다 운선순위가 높거나 같은 것들을 빼고 자신을 스택에 담음

- 여는 괄호 '('를 만나면 무조건 스택에 담음

- 닫는 괄호 ')'를 만나면 '('를 만날 때까지 스택에서 출력

 

* 우선순위 함수 만들기

int getPriority(char *i) {
    if (!strcmp(i, "(")) return 0;
    if (!strcmp(i, "+") || !strcmp(i, "-")) return 1;
    if (!strcmp(i, "*") || !strcmp(i, "/")) return 2;
    return 3;
}

 

* 변환 함수 만들기

// 나뉘어 들어오는 문자열들의 배열을 s라고 말함
char* transition(Stack *stack, char **s, int size) { // 들어오는 각 문자열의 갯수는 size
    char res[1000] = ""; // res 같은 경우는 후위 표기법으로 바뀐 결과가 담길 문자열
    for (int i = 0; i < size; i++) {
        
        // 해당 문자열이 연산자라면
        if (!strcmp(s[i], "+") || !strcmp(s[i], "*") || !strcmp(s[i], "/")) {
            
            // 자기보다 연산자 우선순위가 높은 것들을 다
            while (stack->top != NULL && getPriority(getTop(stack)) >= getPriority(s[i])) {
                
                // 스택에서 뽑은 뒤에
                strcat(res, pop(stack)); strcat(res, " ");
            }
            
            // 자신을 스택에 넣는것
            push(stack, s[i]);
        }
        // 기본 연산자가 아니라 여는 괄호라면 바로 스택에 넣을 수 있도록 하고
        else if (!strcmp(s[i], "(")) push(stack, s[i]);
        // 닫는 괄호인 경우 여는 괄호가 나올때까지 스택에서 뽑아서 바로 결과 배열에 담음
        else if (!strcmp(s[i], ")")) {
            while (strcmp(getTop(stack), "(")) {
                strcat(res, pop(stack)); strcat(res, " ");
            }
            // pop을 통해 여는 괄호도 나올수 있게 해줌
            pop(stack);
        }
        // 일반 숫자인 경우 바로 출력
        else {strcat(res, s[i]); strcat(res, " "); }
    }
    while (stack->top != NULL) {
        // 스택에 남아있는 원소가 있다면 다 꺼내주는게 기본적인 동작 과정
        strcat(res, pop(stack)); strcat(res, " ");
    }
    // result를 반환해서 후위 표기법을 뽑아낼 수 있음
    return res;
}

 

후위 표기법을 계산하는 방법

- 피연산자가 들어오면 스택에 담음

- 연산자를 만나면 스택에서 두 개의 연산자를 꺼내서 연산한 뒤에 그 결과를 스택에 담음

- 연산을 마친 뒤에 스택에 남아있는 하나의 피연산자가 연산 수행 결과임

 

* 후위 표기법 계산

void calculate(Stack *stack, char **s, int size) {
    int x, y, z;
    for (int i = 0; i < size; i++) {
        if (!strcmp(s[i], "+") || !strcmp(s[i], "-") || !strcmp(s[i], "*") || !strcmp(s[i], "/")) {
            x = atoi(pop(stack));
            y = atoi(pop(stack));
            if (!strcmp(s[i], "+")) z = y + x;
            if (!strcmp(s[i], "-")) z = y - x;
            if (!strcmp(s[i], "*")) z = y * x;
            if (!strcmp(s[i], "/")) z = y / x;
            char buffer[100];
            sprintf(buffer, "%d", z);
            push(stack, buffer);
        }
        else {
            push(stack, s[i]);
        }
    }
    printf("%s\n", pop(stack));
}

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { // 노드라는 구조체 정의
    char data[100]; // 하나의 문자열을 다룰수 있도록 함
    struct Node *next;
} Node;

typedef struct Stack {
    Node *top;
} Stack;

void push(Stack *stack, char *data) {
    Node *node = (Node*)malloc(sizeof(Node));
    strcpy(node->data, data);
    node->next = stack->top;
    stack->top = node;
}

char* getTop(Stack *stack) {
    Node *top = stack->top;
    return top->data;
}

char* pop(Stack *stack) {
    if (stack->top == NULL) {
        printf("스택 언더플로우 발생.\n");
        return NULL;
    }
    Node *node = stack->top;
    char *data = (char*)malloc(sizeof(char) * 100);
    strcpy(data, node->data);
    stack->top = node->next;
    free(node);
    return data;
}

int getPriority(char *i) {
    if (!strcmp(i, "(")) return 0;
    if (!strcmp(i, "+") || !strcmp(i, "-")) return 1;
    if (!strcmp(i, "*") || !strcmp(i, "/")) return 2;
    return 3;
}

// 나뉘어 들어오는 문자열들의 배열을 s라고 말함
char* transition(Stack *stack, char **s, int size) { // 들어오는 각 문자열의 갯수는 size
    char res[1000] = ""; // res 같은 경우는 후위 표기법으로 바뀐 결과가 담길 문자열
    for (int i = 0; i < size; i++) {
        
        // 해당 문자열이 연산자라면
        if (!strcmp(s[i], "+") || !strcmp(s[i], "*") || !strcmp(s[i], "/")) {
            
            // 자기보다 연산자 우선순위가 높은 것들을 다
            while (stack->top != NULL && getPriority(getTop(stack)) >= getPriority(s[i])) {
                
                // 스택에서 뽑은 뒤에
                strcat(res, pop(stack)); strcat(res, " ");
            }
            
            // 자신을 스택에 넣는것
            push(stack, s[i]);
        }
        // 기본 연산자가 아니라 여는 괄호라면 바로 스택에 넣을 수 있도록 하고
        else if (!strcmp(s[i], "(")) push(stack, s[i]);
        // 닫는 괄호인 경우 여는 괄호가 나올때까지 스택에서 뽑아서 바로 결과 배열에 담음
        else if (!strcmp(s[i], ")")) {
            while (strcmp(getTop(stack), "(")) {
                strcat(res, pop(stack)); strcat(res, " ");
            }
            // pop을 통해 여는 괄호도 나올수 있게 해줌
            pop(stack);
        }
        // 일반 숫자인 경우 바로 출력
        else {strcat(res, s[i]); strcat(res, " "); }
    }
    while (stack->top != NULL) {
        // 스택에 남아있는 원소가 있다면 다 꺼내주는게 기본적인 동작 과정
        strcat(res, pop(stack)); strcat(res, " ");
    }
    // result를 반환해서 후위 표기법을 뽑아낼 수 있음
    return res;
}

void calculate(Stack *stack, char **s, int size) {
    int x, y, z;
    for (int i = 0; i < size; i++) {
        if (!strcmp(s[i], "+") || !strcmp(s[i], "-") || !strcmp(s[i], "*") || !strcmp(s[i], "/")) {
            x = atoi(pop(stack));
            y = atoi(pop(stack));
            if (!strcmp(s[i], "+")) z = y + x;
            if (!strcmp(s[i], "-")) z = y - x;
            if (!strcmp(s[i], "*")) z = y * x;
            if (!strcmp(s[i], "/")) z = y / x;
            char buffer[100];
            sprintf(buffer, "%d", z);
            push(stack, buffer);
        }
        else {
            push(stack, s[i]);
        }
    }
    printf("%s\n", pop(stack));
}

int main(void) {
    Stack stack;
    stack.top = NULL;
    char a[100] = "( ( 3 + 4 ) * 5 ) - 5 * 7 * 5 - 5 * 10";
    int size = 1;
    for (int i = 0; i < strlen(a); i++) {
        if (a[i] == ' ')  size ++;
        }
    char *ptr = strtok(a, " ");
    char **input = (char**)malloc(sizeof(char*) * size);
    for (int i = 0; i < size; i++) {
        input[i] = (char*)malloc(sizeof(char) * 100);
    }
    for (int i = 0; i < size; i++) {
        strcpy(input[i], ptr);
        ptr = strtok(NULL, " ");
    }
    char b[1000] = "";
    strcpy(b, transition(&stack, input, size));
    printf("후위 표기법: %s\n", b);
    size = 1;
    for (int i = 0; i < strlen(b) - 1; i++) { // 마지막은 항상 공백이 들어감으로 1을 빼기
        if (b[i] == ' ') size++;
        }
    char *ptr2 = strtok(b, " ");
    for (int i = 0; i < size; i++) {
        strcpy(input[i], ptr2);
        ptr2 = strtok(NULL, " ");
    }
    calculate(&stack, input, size);
    system("pause");
    return 0;
}