TIL/03_알고리즘

04 Stack (Algorithm_doit)

JJO.OYA 2022. 6. 18. 03:00

<Doit! 자료구조와 함께 배우는 알고리즘 입문 파이썬편> 공부중입니다.

04-1 스택이란?

스택 알아보기

  • 스택(stack)
    • 데이터를 임시 저장할 때 사용하는 구조
    • 데이터 입력과 출력 순서는 후입선출LIFO방식 (=선입후출FILO)
    • 푸시(push) : 스택에 데이터를 넣는 작업
    • 팝(pop) : 스택에서 데이터를 꺼내는 작업
    • 꼭대기(top) : 푸시하고 팝하는 윗부분
    • 바닥(bottom) : 푸시하고 팝하는 아랫부분

스택 구현하기1

  • 스택 배열 : stk
    • 푸시한 데이터를 저장하는 스택 본체인 list형 배열
    • 인덱스가 0인 원소를 스택의 바닥이라고 함
    • 가장 먼저 푸시하여 데이터를 저장하는 곳은 stk[0]
  • 스택 크기 : capacity
    • 스택의 최대 크기를 나타내는 int형 정수
    • 이 값은 배열 stk의 원소 수인 len(stk)와 일치
  • 스택 포인터 : ptr
    • 스택 포인터(stack pointer) : 스택에 쌓여 있는 데이터의 개수를 나타내는 정숫값
    • 스택이 비어 있으면 ptr의 값은 0이 되고, 가득 차 있으면 capacity와 같은 값

스택 구현하기2

# 고정 길이 스택 클래스 FixedStack 구현하기

from typing import Any

class FixedStack:
    '''고정 길이 스택 클래스'''

    class Empty(Exception):
        '''비어 있는 FixedStack에 팝 또는 피크할 때 내보내는 예외 처리'''
        pass

    clas Full(Exception):
        '''가득 찬 FixedStack에 푸시할 때 내보내는 예외 처리'''
        pass

    def __init__(self, capacity: int=256) -> None:
        '''스택 초기화'''
        self.stk = [None] * capacity   # 스택 본체 >> capacity 개수만큼 None이 있음
        self.capacity = capacity       # 스택의 (최대) 크기
        self.ptr = 0                   # 스택 포인터 (스텍에 쌓여 있는 데이터 개수)
  • `Empty` : 예외 처리 클래스
    • pop() 함수 또는 peek() 함수를 호출할 때 스택이 비어 있으면 내보내는 예외 처리
  • `Full` : 예외 처리 클래스
    • push() 함수를 호출할 때 스택이 가득 차 있으면 내보내는 예외 처리
  • `__init__()` : 초기화하는 함수
    • 스택 배열을 생성하는 등의 준비 작업을 수행
    • 매개변수 capacity로 전달 받아 원소 수가 capacity이고 모든 원소가 None인 리스트형 stk를 생성
    • 스택이 비어 있으므로 스택 포인터 ptr는 0
def __len__(self) -> int:
    '''스택에 쌓여 있는 데이터 개수를 반환'''
    return self.ptr

def is_empty(self) -> bool:
    '''스택이 비어 있는지 판단'''
    return self.ptr <= 0

def is_full(self) -> bool:
    '''스택이 가득 차 있는지 판단'''
    return self.ptr -> self.capacity
  • `__len__()` : 쌓여 있는 데이터 개수를 알아내는 함수
    • 스택에 쌓여있는 데이터 개수 반환(ptr값 반환)
  • `is_empty()` : 스택이 비어 있는지를 판단하는 함수
    • 데이터가 하나도 쌓여 있지 않은 상태, 즉 스택이 비어 있는지 판단
    • `self.ptr`이 0보다 작거나 같은지 판단
    • 스택이 비어 있으면 True, 아니면 False 반환
  • `is_full()` : 스택이 가득 차 있는지를 판단하는 함수
    • 더 이상 데이터를 푸시할 수 없는 상태, 즉 스택이 가득 차 있는지 판단
    • `self.ptr`이 `self.capacity`로 변환 되는지(같은 값인지) 판단
    • ptr == capacity 이면 full인 상태
    • 스택이 가득 차 있으면 True, 아니면 False 반환
def push(self, value: Any) -> None:  # 스택에 value를 푸시(데이터를 넣음)
    '''스택에 value를 푸시(데이터를 넣음)'''
    if self.is_full():               # 스택이 가득 차 있는 경우
        raise FixedStack.Full        # 예외 처리 발생
    self.stk[self.ptr] = value
    self.ptr += 1
  • `raise + 예외처리 이름` : 이미 파이썬에서 에러로 사용하고 있는 키워드를 넣을 수 있음
  • `push()` : 데이터를 푸시하는 함수
    • 스택에 데이터를 추가
    • 스택이 가득 차서 더 이상 푸시할 수 없는 경우에는 FixedStack.Full을 통하여 예외 처리를 내보냄
    • Class FixedStack class Full >> pass
    • `self.stk`의 `self.ptr` 위치에 value값 반환
    • `self.ptr`를 다음으로 이동 (+1)
def pop(self) -> Any:                # 스택에서 데이터를 팝(꼭대기 데이터를 꺼냄)
    '''스택에서 데이터를 팝(꼭대기 데이터를 꺼냄)'''
    if self.is_empty():              # 스택이 비어 있는 경우
        raise FixedStack.Empty       # 예외 처리 발생
    self.ptr -= 1
    return self.stk[self.ptr]
  • `pop()` : 데이터를 팝하는 함수
    • 스택의 꼭대기에서 데이터를 꺼내서 그 값을 반환
    • 스택이 비어서 팝할 수 없는 경우에는 FixedStack.Empty를 통하여 예외 처리를 내보냄
    • Class FixedStack class Empty >> pass
    • `self.ptr`를 한 칸 전으로 이동
    • `self.stk`의 `self.ptr`위치의 value값 반환
    • -> 마지막 값이 반환
def peek(self) -> Any:
    '''스택에서 데이터를 피크(꼭대기 데이터를 들여다봄)'''
    if self.is_empty():
        raise FixedStack.Empty
    return self.stk[self.ptr - 1]
  • `peek()` : 데이터를 들여다 보는 함수
    • 스택의 꼭대기 데이터를 들여다 봄
    • 스택이 비어 있는 경우에는 FixedStack.Empty를 통하여 예외처리를 내보냄
    • 데이터의 입출력이 없으므로 스택 포인터는 반환하지 않음
    • `self.stk`의 `sel.ptr`의 -1 값 반환
    • 밑의 사진에서 ptr이 4번 위치라면 그 전의 3번 위치를 봐야 하므로 ptr -1

# ALGORITHM_doit_04 스택과 큐

def clear(self) -> None:
    '''스택을 비움(모든 데이터를 삭제)'''
    self.ptr = 0
  • `clear()` : 스택의 모든 데이터를 삭제하는 함수
    • 스택에 쌓여 있는 데이터를 모두 삭제하여 빈 스택을 만듦
    • 스택 포인터 ptr값을 0으로 하면 끝!
def fine(self, value: Any) -> Any:
    '''스택에서 value를 찾아 인덱스를 반환(없으면 -1을 반환)'''
    for i in range(self.ptr -1, -1, -1):   # 꼭대기 쪽부터 선형 검색
        if self.stk[i] == value:
            return i                       # 검색 성공
    return -1                              # 검색 실패
  • `find()` : 데이터를 검색하는 함수
    • 스택 본체의 배열 stk 안에 value와 값이 같은 데이터가 포함되어 있는지 확인하고,
    • 포함되어 있다면 배열의 어디에 들어 있는지 검색
    • `self.ptr`-1 위치 부터 위치 0까지 거꾸로 탐색
    • `self.stk[i]` == value >> i 값 (배열 위치) 반환
    • 포함된 값이 없다면 -1 반환
def count(self, value: Any) -> bool:
    '''스택에 있는 value의 개수를 반환'''
    c = 0
    for i in range(self.ptr):      # 바닥 쪽부터 선형 검색
        if self.stk[i] == value:   # 검색 성공
            c += 1
    return c
  • `count()` : 데이터 개수를 세는 함수
    • 스택에 쌓여 있는 데이터(value)의 개수 반환
    • `self.stk[i]` == value >> c를 +1 해줌 >> 최종 c 값 반환
def __contains__(self, value: Any) -> bool:
    '''스택에 value가 있는지 판단'''
    return self.count(value)

def dump(self) -> None:
    '''덤프(스택 안의 모든 데이터를 바닥부터 꼭대기 순으로 출력)'''
    if self.is_empty():      # 스택이 비어 있음
        print('스택이 비어 있습니다.')
    else:
        print('self.stk[:self.ptr]')
  • `__contains__()` : 데이터가 포함되어 있는지 판단하는 함수
    • 스택에 데이터(value)가 있는지 판단
    • 있으면 True, 없으면 False를 반환
    • 예) 스택 s에 데이터 x가 포함되어 있는지 판단하기
    • 예) `s.__contains__(x)`
    • `self.count(value)`값 반환 >> `self.stk[i]`== x 를 만족하는 c 값 반환
  • `dump()` : 스택의 모든 데이터를 출력하는 함수
    • 스택에 쌓여 있는 ptr개의 모든 데이터를 바닥부터 꼭대기까지 순서대로 출력
    • 스택이 비어 있으면 '스택이 비어 있습니다.'를 출력
    • `self.is_empty`가 아니라면 `self.stk[:self.ptr]` 출력되는데,
    • 0부터 self.ptr-1 까지 (바닥부터 꼭대기 까지) 차례로 출력

스택 프로그램 만들기

from enum import Enum


Menu = Enum('Menu', ['push', 'pop', 'peek', 'find', 'dump', 'clear', 'exit'])#열거형 자료형으로 변환


def select_menu() -> Menu:
    s = [f'({m.value}){m.name}' for m in Menu]#['(1)push', '(2)pop', '(3)peek', '(4)find', '(5)dump', '(6)clear', '(7)exit']
    while True:
        print(*s, sep = '   ', end='') #s의 모든 값을 '  '띄우고 끝을 붙혀준다 
        n = int(input(': '))           # input을 받아준 후 int로 변환해 준다
        if 1 <= n <= len(Menu):        # 입력받은 값이 1~ 메뉴의 길이 (7) 사이의 값이라면 Menu(그숫자)를 리턴  
            return Menu(n)
        else:
            print()
            print('다시 입력하세요.(1~7사이)')

s = FixedStack(64)

while True:
    print()
    print('ㅡ'*20)
    print(f'현재 데이터 개수: {len(s)} / {s.capacity}') #s현재길이/s의 용량크기
    menu = select_menu() #menu를 받아온다


    if menu == Menu.push:
        x = int(input('데이터를 입력하세요.: '))
        try:
            s.push(x)
        except FixedStack.Full:
            print('스택이 가득 차 있습니다.')

    elif menu == Menu.pop:
        try:
            x = s.pop()
            print(f'팝한 데이터는 {x}입니다.')
        except FixedStack.Empty:
            print('스택이 비어있습니다.')

    elif menu == Menu.peek:# 스택의 꼭대기의 데이터를 출력
        try:
            x = s.peek()
            print(f'피크한 데이터는 {x}입니다.')
        except FixedStack.Empty:
            print('스택이 비어있습니다.')

    elif menu == Menu.find:
        x = int(input('검색할 데이터를 입력하세요.: '))
        if x in s:
            print(f'{s.count(x)}개 포함되고, 맨 앞의 위치는 {s.find(x)}입니다.')
        else:
            print('검색한 값을 찾을 수 없습니다.')

    elif menu == Menu.dump:#전부출력
        s.dump()

    elif menu == Menu.clear:#전부비우기
        s.clear()

    else:
        break

# 스택 구현 예제

stack = []

# 삽입(5) - 삽입(2) - 삽입(3) - 삽입(7) - 삭제() - 삽입(1) - 삽입(4) - 삭제()

stack.append(5)
stack.append(2)
stack.append(3)
stack.append(7)
stack.pop()
stack.append(1)
stack.append(4)
stack.pop()

print(stack[::-1])  # 최상단 원소부터 출력
print(stack)  # 최하단 원소부터 출력

[1, 3, 2, 5]
[5, 2, 3, 1]

동일한 값으로 나오는 것을 확인 할 수 있다.