전공과목 정리/자료구조

[자료구조💾] 4장 스택과 큐

최연재 2024. 1. 16. 05:00

교재 : 파이썬으로 배우는 자료구조 프로그래밍 (유석종, 휴먼싸이언스)

 

4.1 스택 (stack)

1) 스택

- 선형 리스트의 특별한 형태

- 나중에 들어가는 원소가 가장 먼저 나오는 후입선출(LIFO; Last In First Out) 구조

- 함수 호출 관리, 문법 검사(syntax checking), 수식 평가(expression evalution) 등에 많이 사용한다.

- top : 마지막으로 추가된 원소를 가리키는 변수

- push : 스택에 원소를 추가하는 연산

- pop : 스택에서 원소를 삭제하는 연산

- 정적 배열로 스택을 구현할 경우, push 함수는 스택에 원소를 추가하기 전에 'stack full' 상태인지, pop 함수는 스택에서 원소를 꺼내기 전에 스택이 빈 상태(top이 -1)인지를 검사한다.

 

2) 코드로 구현

class Stack:
    def __init__(self, size):
        self.top = -1
        self.stack = []
        self.size =size

    def push(self, item):
        if self.top < (self.size -1):
            self.top += 1
            self.stack.append(item)
            self.show_stack()
        else:
            print("Stack Full")

    def pop(self):
        if self.top >-1:
            self.top -= 1
            return self.stack.pop()
        else:
            print("Stack Empty")
        
    def show_stack(self):
        print(self.stack)

 

 

4.2 선형 큐 (linear queue)

1) 선형 큐 

- 큐(queue) : 작업들이 처리되기 전에 대기하고 있는 선형 리스트 자료구조

- 선형 큐 : 큐의 시작과 끝이 분리되어 있는 구조

- 순환 큐 : 양 끝이 연결된 구조

 

2) 큐의 구현

- 파이썬의 리스트 이용

- front : 큐의 앞을 가리키는 변수

- rear : 큐의 뒤를 가리키는 변수

- push : 큐에서 원소를 추가하는 연산

- pop : 큐에서 원소를 삭제하는 연산

- 연산을 수행하기 전에 큐에 가득 차 있는지(push),비어 있는지(pop) 확인해야 한다.

 

3) 코드로 구현

class Queue:
    def __init__(self, size):
        self.front = -1
        self.rear = -1
        self.queue = []
        self.size =size

    def isEmpty(self):
        return len(self.queue) == 0
    
    def isFull(self):
        return len(self.queue) == self.size

    def push(self, item):
        if not self.isFull():
            self.rear += 1
            self.queue.append(item)
            self.show_queue()
        else:
            print("Queue Full")

    def pop(self):
        if not self.isEmpty():
            self.front+=1
            return self.queue.pop(0)
        else:
            print("Queue Empty")
        
    def show_queue(self):
        print(self.queue)

 

 

4.3 순환 큐 (circular queue)

1) 순환 큐

- 선형 큐의 경우 원소의 추가 삭제 시 rear, front 값이 계속 증가한다.

  • 원소들이 큐의 오른쪽에만 몰려 왼쪽에는 빈 공간이 생기게 된다.
  • rear 변수에 의해 큐에 가득 찼다는 신호를 받아도 실제 내부에는 빈 공간이 있을 수 있다.
  • 큐의 빈 공간을 재사용하기 위해 원소들을 왼쪽으로 이동시키는 작업을 해야 한다.

- 순환 큐 : 선형 큐의 단점을 개선한 자료구조

- 큐의 양쪽 끝을 서로 연결하여 원소의 이동 작업 없이 전체 공간을 모두 사용할 수 있다.

- front, rear 변수의 초기값을 0으로 설정하고, 최소 한 개의 빈 공간을 두거나 count 변수를 사용해 추가된 원소의 개수를 기억해야 한다.

- 고정 크기의 순환 큐를 구현할 때 None 값으로 미리 큐의 모든 공간을 채워둔다.

self.cqueue = [None] * self.size
self.rear = (self.rear + 1) % self.size
self.front = (self.front + 1) % self.size

 

2) 코드로 구현

class CQueue:
    def __init__(self, size):
        self.front = 0
        self.rear = 0
        self.count = 0
        self.cqueue = []
        self.size =size

    def empty(self):
        return self.count == 0

    def full(self):
        return self.count == self.size
    
    def view(self, size):
        print(self.cqueue, 'F=%d R=%d'  %(self.front, self.rear))

    def push(self, item):
        if not self.full():
            print("Push %2d" % item, end=' ')
            self.rear = (self.rear+1)%self.size
            self.cqueue[self.rear] = item
            self.view()
            self.count+=1
        else:
            print("Queue Full")

    def pop(self):
        if not self.empty():
            print("Pop ",  end=' ')
            self.front = (self.front+1)%self.size
            item = self.cqueue[self.front]
            self.cqueue[self.front] = None
            self.view()
            self.count-=1
            return item
        else:
            print("Queue Empty")

 

 

4.4 순환 데크 (circular deque)

1) 순환 데크

- 데크(Deque : double ended queue) : 큐의 앞(front), 뒤(rear) 모두에서 원소의 추가, 삭제가 발생한다.

- 순환 데크는 데크의 앞과 뒤가 서로 연결된 구조이다.

- 다음과 같은 함수가 필요하다.

데크 함수 작업
add_front() front 위치에 원소 저장 후 front 감소
del_front() front를 증가시키고 front의 원소 삭제
add_rear() rear를 증가시키고 원소 증가
del_rear() rear의 원소를 삭제하고 rear 감소

 

2) 코드로 구현

class CDeque:
    def __init__(self, size):
        self.front = 0
        self.rear = 0
        self.size = size
        self.cdeque = []
        self.count = 0

    def isEmpty(self):
        return self.count == 0
    
    def isFull(self):
        return self.count == self.size
    
    def show_deque(self):
        print(self.cdeque,'FR', self.frot, self.rear)

    def add_rear(self, item):
        print("Add Rear %2d" & item, end = ' ')
        if not self.isFull():
            self.rear = (self.rear+1)%self.size
            self.cdeque[self.rear] = item
            self.show_deque()
            self.count+=1
        else:
            print("=> Deque Full") 

    def add_front(self, item):
        print("Add Front %2d" & item, end = ' ')
        if not self.isFull():
            self.cdeque[self.front] = item
            self.front = (self.front-1+self.size)%self.size
            self.show_deque()
            self.count+=1
        else:
            print("=> Deque Full") 

    def del_front(self):
        print("Del Front  ", end = ' ')
        if not self.isEmpty():
            self.front = (self.front+1)%self.size
            item = self.cdeque[self.front]
            self.cdeque[self.front] = None
            self.count -= 1
            self.show_deque()
            return item
        else:
            print("=> Deque Empty")

    def del_rear(self):
        print("Del Rear  ", end = ' ')
        if not self.isEmpty():
            item = self.cdeque[self.rear]
            self.cdeque[self.rear] = None
            self.rear = (self.rear-1+self.size)%self.size
            self.count -= 1
            self.show_deque()
            return item
        else:
            print("=> Deque Empty")

 

 

4.5 수식의 표현과 평가

1) 수식 표기법

- 수식(expression)은 연산자(operator)와 피연산자(operand)의 조합으로 구성된다.

- 수식 표기법은 피연산자에 대한 연산자의 위치에 따라 중위 표기법(infix), 후위 표기법(postfix), 전위 표기법(prefix)으로 나뉜다.

- 중위 수식

  • 순서 : 피연산자 연산자 피연산자
  • 각 연산자별로 우선순위가 있으며, 괄호를 사용하여 우선 순위를 바꿀 수 있다.
  • 계산 순서가 복잡하다.
  • 사람이 이해하기 쉬우나, 연산 순서가 복잡하여 프로그래밍이 어렵다.
  • ex) 5 + 9 * 7

- 후위 수식

  • 순서 : 피연산자 피연산자 연산자
  • 연산자의 우선순위가 없고 괄호를 사용하지 않는다.
  • 왼쪽에서 오른쪽 방향으로 1회 스캔하여 계산하므로 프로그래밍이 쉽지만 사람이 직관적으로 이해하기 어렵다.
  • ex) 5 9 7 * +

 

2) 후위 수식 계산하기

- 알고리즘

  1. 후위 수식(expr)에서 읽은 토큰(token)이 피연산자이면 스택에 넣는다.
  2. 토큰이 연산자이면 스택에서 피연산자 2개를 꺼내서 계산한 후 그 결과 값을 다시 스택에 넣는다
  3. 모든 토큰이 입력되면 스택에 마지막으로 저장된 계산값을 꺼내 반환한다.

- 코드

class Sym:
  OPEN_B = 1
  CLOSE_B = 2
  PLUS = 3
  MINUS = 4
  TIMES = 5
  DIVIDE = 6
  MOD = 7
  OPERAND = 8

class Expression:
  def __init__(self,expr):
    self.stack = []
    self.size = 100
    self.expr= expr
    self.top=-1

  def push(self, item):
    self.top+=1
    self.stack.append(item)
    self.stack_show()

  def pop(self):
    if len(self.stack) > 0:
      self.top -=1
      return self.stack.pop()
    else:
      print("Stack Empty")

  def show_stack(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.OPEN_B
    elif sym == ')':
      sym_type = Sym.CLOSE_B
    elif 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. 중위 수식의 토큰(token)이 피연산자(operand)이면 그대로 후위 수식으로 출력한다.
  2. 토큰이 연산자인 경우 스택이 비어 있거나, 스택 연산자(stack_top)보다 우선 순위가 높으면 스택에 추가한다.
  3. 토큰이 스택 연산자보다 우선 순위가 낮거나 같으면, 스택에서 연산자를 먼저 제거한다.
  4. 토큰이 '('이면 그대로 스택에 추가한다.
  5. 토큰이 ')'이면 스택에서 '('이 나올 때까지 모든 연산자를 제거하여 출력하고 '('은 출력 없이 버린다.
  6. 토큰이 문자열 끝이면 스택에 남은 연산자를 모두 출력한다.

- 코드

class Sym:
  OPEN_B = 1
  CLOSE_B = 2
  PLUS = 3
  MINUS = 4
  TIMES = 5
  DIVIDE = 6
  MOD = 7
  OPERAND = 8

class Expression:
  def __init__(self,expr):
    self.stack = []
    self.size = 100
    self.expr= expr
    self.top=-1
    self.output = ""

  def isFull(self):
    return len(self.stack)==self.size
  
  def isEmpty(self):
    return len(self.stack)==0

  def push(self, item):
    if not self.isFull():
      self.top+=1
      self.stack.append(item)
      self.stack_show()
    else:
      print("Stack Full")

  def pop(self):
    if not self.isEmpty():
      self.top -=1
      return self.stack.pop()
    else:
      print("Stack Empty")

  def show_stack(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.OPEN_B
    elif sym == ')':
      sym_type = Sym.CLOSE_B
    elif 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
  
  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(token, end=' ')
      if token.isalum():
        self.output +=token
      elif token == '(':
        self.push(token)
      elif token == ')':
        sym = self.pop()
        while sym!='(':
          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)
    while not self.isEmpty():
      self.output += self.pop()
    print()