6 분 소요

1) 스택(Stack)

  • 들어온 시간 순으로 데이터를 쌓아갈 때 가장 위(가장 최근에 삽입)에 있는 데이터를 삭제하거나 새로운 데이터를 삽입할 수 있도록 하는 추상 자료형
  • 후입선출
  • 접근 삽입 및 삭제
    • 스택 탑만 접근 가능
    • 한쪽 끝에서 삽입, 삭제
    • 삽입, 삭제 위치를 스택 탑 부근으로 제한
    • 구현 자료구조에서 탑이 아닌 다른 곳을 접근할 수 있어도 하지 않기로 약속

Push 연산

  • 스택에서의 삽입 연산
    1. 스택 S에서 top이 마지막 자료를 가리키고 있으므로 그 위에 자료를 삽입하려면 먼저 top의 위치를 하나 증가
    2. 만약 이때 top의 위치가 스택의 크기(stack_SIZE)보다 크다면 오버플로우(Overflow) 상태가 되므로 삽입 연산을 수행하지 못하고 연산 종료
    3. 오버플로우 상태가 아니라면 스택의 top이 가리키는 위치에 x 삽입
push(S, x)
{
    top = top + 1;  // 1 
    if (top > stack_SIZE)   // 2
        overflow;
    else
        S(top) = x; // 3
}

pop 연산

  • 스택에서의 삭제 연산
    • 스택이 공백 스택이 아니라면 top이 가리키는 원소를 먼저 반환
    • 스택의 top 원소를 반환하였으므로 top의 위치는 그 아래의 원소로 변경하기 위해 top의 위치를 하나 감소
pop(S)
{
    if (top == 0)
        underflow;
    else    // 1
    {
        return S(top--);    // 2
    }
}

2) 배열로 구현한 스택

  • 스택의 크기 : 배열의 크기
  • 스택에 저장된 원소의 순서 : 배열 원소의 인덱스
    • index = 0 : 스택의 첫번째 원소
    • index = n-1 : 스택의 n번째 원소
  • 변수 top : 스택에 저장된 마지막 원소에 대한 인덱스 저장
    • top = -1 : 공백 상태(초기값)
    • top = n-1 : 포화 상태

image

장단점

  • 장점 : 순차 자료구조인 1차원 배열을 사용하여 쉽게 구현
  • 단점 : 물리적으로 크기가 고정된 배열을 사용하므로 스택의 크기 변경 어려움
#include<stdio.h>
#include<stdlib.h>
#define STACK_SIZE 100

typedef int element; // int를 스택 element의 자료형으로 정의
element stack[STACK_SIZE];
int top = -1;

void push(element item)	// 요소 삽입
{
	if (top >= STACK_SIZE - 1)
	{
		printf("\n\n Stack is FULL \n");
		return;
	}
	else stack[++top] = item;
}

element pop()	// 요소 반환
{
	if (top == -1)
	{
		printf("\n\n Stack is Empty \n");
		return 0;
	}
	else return stack[top--];
}	

void del()	// 리턴 없이 요소 제거
{
	if (top == -1)
	{
		printf("\n\n Stack is Empty \n");
		exit(1);
	}
	else top--;
}

element peek()	// 원소 검색
{
	if (top == -1)
	{
		printf("\n\n Stack is Empty \n");
		return 0;
	}
	else return stack[top];
}

void printStack()
{
	int i;
	printf("\n STACK [ ");
	for (i = 0; i <= top; i++)
		printf("%d ", stack[i]);
	printf("] ");
}

void main()
{
	element item;
	printStack();
	push(1);
	printStack();
	push(2);
	printStack();
	push(3);
	printStack();

	item = peek();
	printStack();
	printf("peek top => %d", item);

	del();
	printStack();

	item = pop();
	printStack();
	printf("\t pop top => %d", item);
	return;
}

image

3) 단순 연결 리스트로 구현한 스택

  • 스택의 원소 : 단순 연결 리스트의 노드
  • 스택 원소의 순서 : 노드의 링크 포인터로 연결
    • push : 리스트의 마지막에 노드 삽입
    • pop : 리스트의 마지막 노드 삭제
  • 변수 top : 단순 연결 리스트의 마지막 노드를 가리키는 포인터 변수
    • 초기 상태 : top = null

image

장단점

  • 장점 : 동적 메모리로 메모리를 효율적으로 사용
  • 단점 : 구현과 이해가 어려움
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef int element;

typedef struct stackNode
{
	element data;
	stackNode* link;
}stackNode;

stackNode* top;

void push(element item)	// 요소 삽입
{
	stackNode* temp = (stackNode*)malloc(sizeof(stackNode));
	temp->data = item;
	temp->link = top;
	top = temp;
}

element pop()	// 요소 반환
{
	element item;
	stackNode* temp = top;
	if (top == NULL)
	{
		printf("\n\n Stack is empty \n");
		return 0;
	}
	else
	{
		item = temp->data;
		top = temp->link;
		free(temp);
		return item;
	}
}

void del()	// 리턴 없이 요소 제거
{
	stackNode* temp;
	if (top == NULL)
	{
		printf("\n\n Stack is empty \n");
	}
	else
	{
		temp = top;
		top = top->link;
		free(temp);
	}
}

element peek()	// 원소 검색
{
	element item;
	if (top == NULL)
	{
		printf("\n\n Stack is Empty \n");
		return 0;
	}
	else
	{
		item = top->data;
		return item;
	}
}

void printStack()
{
	stackNode* p = top;
	printf("\n STACK [ ");
	while (p)
	{
		printf("%d ", p->data);
		p = p->link;
	}
	printf("] ");
}

void main()
{
	element item;
	top = NULL;
	printStack();
	push(1);
	printStack();
	push(2);
	printStack();
	push(3);
	printStack();

	item = peek();
	printStack();
	printf("peek top => %d", item);

	del();
	printStack();

	item = pop();
	printStack();
	printf("\t pop top => %d", item);
	return;
}

image

4) 스택의 응용

괄호 검사

  • 수식에 포함되어 있는 괄호는 가장 마지막에 열린 괄호를 가장 먼저 닫아 주어야 하는 후입선출 구조로 구성되어 있음
  • 후입선출 구조의 스택을 이용하여 괄호를 검사함
  • 수식을 왼쪽에서 오른쪽으로 하나씩 읽으면서 괄호 검사
    1. 왼쪽 괄호를 만나면 스택에 Push
    2. 오른쪽 괄호를 만나면 스택을 Pop하여 마지막에 저장한 괄호와 같은 종류인지를 확인
      • 같은 종류의 괄호가 아닌 경우 괄호의 짝이 잘못 사용된 수식임
  • 수식에 대한 검사가 모두 끝났을 때 스택은 공백 스택이 됨
    • 수식이 끝났어도 스택이 공백이 되지 않으면 괄호의 개수가 틀린 수식임
int testPair(const char* exp)
{
	char symbol, open_pair;
	int i, length = strlen(exp);
	top = NULL;
	for (i = 0; i < length; i++)
	{
		symbol = exp[i];
		switch (symbol)
		{
		case '(':
		case '{':
		case '[':
			push(symbol);	break;
		case ')':
		case '}':
		case ']':
			if (top == NULL)	return 0;
			else
			{
				open_pair = pop();
				if ((open_pair == '(' && symbol != ')') ||
					(open_pair == '{' && symbol != '}') ||
					(open_pair == '[' && symbol != ']'))
					return 0;
				else break;
			}
		}
	}
	if (top == NULL) return 1;
	else return 0;
}

void main()
{
	const char* express = "{(A + B) - 3}*5 + [{cos(x + y) + 7} - 1] * 4";
	printf("%s", express);
	if (testPair(express) == 1)
		printf("\n\n 수식의 괄호가 맞게 사용되었습니다.");
	else
		printf("\n\n 수식의 괄호가 틀렸습니다.");
	return;
}

image

수식의 표기법 변환

  • 전위 표기법
    • 연산자를 피연산자를 앞에 표기하는 방법
    • 예) +AB
  • 중위 표기법
    • 연산자를 피연산자의 가운데 표기하는 방법
    • 예) A+B
  • 후위 표기법
    • 연산자를 피연산자의 뒤에 표기하는 방법
    • 예) AB+
  • 전위 표기식으로 변환하기
    1. 수식의 각 연산자에 대해서 우선 순위에 따라 괄호를 사용하여 다시 표현함
    2. 각 연산자를 그에 대응하는 왼쪽 괄호의 앞으로 이동시킴
    3. 수식의 괄호를 제거함
      • A*B-C/D
      • 1단계 : ( (A*B) - (C/D) )
      • 2단계 : -( *(AB) / (CD) )
      • 3단계 : -*AB / CD
  • 후위 표기식으로 변환하기
    1. 수식의 각 연산자에 대해서 우선 순위에 따라 괄호를 사용하여 다시 표현함
    2. 각 연산자를 그에 대응하는 오른쪽 괄호의 뒤로 이동시킴
    3. 수식의 괄호를 제거함
  • 알고리즘
    1. 왼쪽 괄호를 만나면 무시하고 다음 문자를 읽음
    2. 피연산자(숫자)를 만나면 출력함
    3. 연산자를 만나면 스택에 Push함
    4. 오른쪽 괄호를 만나면 스택을 Pop하여 출력함
    5. 수식이 끝나면, 스택이 공백이 될 때까지 Pop하여 출력함
Infix_to_postfix(exp)
{
    while(true) 
    {
        symbol = getSymbol(exp);
        if(symbol == operand)   // 2
            print(symbol);
        if(symbol == operator)  // 3
            push(stack, symbol);
        if(symbol == ')')    // 4
            print(pop(stack));
        if(symbol == null)  // 5
            while(top > -1)
                print(pop(stack));
    }
}

후위 표기식 연산

  • 피연산자(숫자)를 만나면 스택에 Push함
  • 연산자를 만나면 필요한 만큼의 피연산자를 스택에서 Pop하여 연산하고, 연산 결과를 다시 스택에 Push함
  • 수식이 끝나면, 마지막으로 스택을 Pop하여 출력함
element evalPostfix(const char* exp)
{
	int opr1, opr2, value, i = 0;
	int length = strlen(exp);
	char symbol;
	top = NULL;
	for (i = 0;i < length;i++)
	{
		symbol = exp[i];
		if (symbol != '+' && symbol != '-' && symbol != '*' && symbol != '/')
		{
			value = symbol - '0';
			push(value);
		}
		else
		{
			opr2 = pop();
			opr1 = pop();
			switch (symbol)
			{
			case '+': push(opr1 + opr2); break;
			case '-': push(opr1 - opr2); break;
			case '*': push(opr1 * opr2); break;
			case '/': push(opr1 / opr2); break;
			}
		}
	}
	return pop();
}

void main()
{
	int result;
	const char* express = "35*62/-";
	printf("후위표기식 : %s", express);
	result = evalPostfix(express);
	printf("\n\n 연산결과 => %d", result);
	return;
}

image

업데이트: