본문 바로가기
알고리즘/자료구조_알고리즘

[자료구조] 06. 스택 (Stack)

by ㅣlㅣl 2022. 8. 25.

 

사담 공간

 

드디어 고오급 자료구조에 입문하게 된다..!! 그래도 스택이랑 큐까지는 할만했던 것 같은데 트리부터가...허허

알고리즘 전까지는 최대한 개강 전에 끝내보자 ㅠㅠ 

 


키워드

 

#스택 #후입선출 #LIFO

 


1. 스택 개념

 

스택(Stack)은 한쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형구조(LIFO) 로 되어있다.

 

 

스택의 핵심이자 큐 같은 자료구조와 구별되는 가장 큰 차이점은 바로 이 "LIFO"라는 개념이다.

 

 

LIFO (Last In First Out), 후입선출

영어와 한자 해석 그대로, "제일 마지막에 들어온 데이터가 제일 먼저 나간다" 라는 뜻이다.

사실 글로 읽기보다는 그림으로 보면 이해가 빠르다.

 

[그림 1] 스택 개념 그림

 

 

그림과 함께 보면 당연하게 느껴진다.

 

한쪽이 막혀있으니, 한쪽으로만 자료를 꺼낼 수 있고, 꺼낼 수 있는 자료는 맨 위에 쌓여있던 (가장 최근에 입력된) 데이터일 것이다!

 

따라서 스택의 삽입과 삭제는 스택의 맨 윗 지점에서만 일어나며, 이를 top 이라고 지칭한다.

 

위 내용은 후에 나올 큐(queue) 자료구조와 스택을 구분짓는 가장 큰 차이점이므로 꼭 기억해두자!

 

2. 스택 ADT & 구현

스택 메서드 ADT

일반 메서드

  • void initialize() : 초기 스택 생성

 

주요 스택 메서드

  • void push(e) : 스택에 원소를 삽입
  • element pop() : 스택에서 원소 삭제 후 반환

 

보조 스택 메서드

  • element top() : 가장 최근에 삽입된 원소 반환
  • integer size() : 저장된 원소의 개수를 반환
  • boolean isEmpty() : 아무 원소도 저장되어 있지 않고 비어있는지 여부를 반환

 

예외

  • emptyStackException() : 비어 있는 스택에서 삭제나 top 시도할 경우
  • fullStackException() : 꽉 차 있는 스택에서 삽입 시도할 경우 발령

 

 

스택 구현

배열을 이용한 구현

  • 크기 N의 배열을 사용해 원소들을 배열의 왼쪽 -> 오른쪽으로 추가
  • 변수 t = top 원소의 인덱스

 

 

일반 메서드

  • void initialize() : 초기 스택 생성
Alg initialize()
    // input : size N, top t
    // output : empty stack S
    
    S = array()
    t = -1
    return S

 

[그림 2] 배열 기반 스택 - initialize()

 

 

주요 스택 메서드

  • void push(e) : 스택에 원소를 삽입
Alg push(e)
    // input : stack S, size N, top t, element e
    // output : none

    if (t = N - 1)
        fullStackException()
    
    t = t + 1
    S[t] = e
    return

 

[그림 3] 배열 기반 스택 - push()

 

 

  • element pop() : 스택에서 원소 삭제 후 반환
Alg pop()
    // input : stack S, size N, top t
    // output : element

    if (isEmpty())
        emptyStackException()
    
    t = t - 1
    return S[t+1]

 

[그림 4] 배열 기반 스택 - pop()

 

 

보조 스택 메서드

  • element top() : 가장 최근에 삽입된 원소 반환
Alg top()
    if (isEmpty())
        emptyStackException()
    return S[t]

 

  • integer size() : 저장된 원소의 개수를 반환
Alg size()
    return t + 1

 

  • boolean isEmpty() : 아무 원소도 저장되어 있지 않고 비어있는지 여부를 반환
Alg isEmpty()
    if (t = -1)
        return true

    else
        return false

 

 

 

<파이썬 구현>

 

더보기

 

# stack_array.py

class stack_array:
    def __init__(self, N):
        self.arr = ['NULL'] * N
        print(len(self.arr))
        self.n = N
        self.t = -1
    
    def push(self, e):
        if self.t == self.n - 1:
            print("fullStackException")
        
        self.t += 1
        self.arr[self.t] = e
        return
    
    def pop(self):
        if self.isEmpty():
            print("emptyStackException")
        self.t -= 1
        return self.arr[self.t+1]
    
    def isEmpty(self):
        if self.t == -1:
            return True
        else:
            return False
    
    def size(self):
        return self.n
    
    def top(self):
        if self.isEmpty():
            print("emptyStackException")
        return self.arr[self.t]

 

# stack.py

from stack_array import stack_array

stack = stack_array(10)

stack.push('a')
stack.push('b')
stack.push('d')
stack.push('e')
print('top :', stack.top(), 'size :', stack.size())
print('remove', stack.pop())
print('top :', stack.top(), 'size :', stack.size())
stack.push('g')
print('top :', stack.top(), 'size :', stack.size())
stack.pop()
stack.pop()
stack.pop()
stack.pop()
stack.pop()

 

<실행 결과>

 

[그림 5] 실행 결과

 

 

 

성능

 

스택의 원소 개수가 n일 때,

  • 기억장소 사용 : O(n)
  • 각 작업의 실행 시간 : O(1)

 

제약

  • 스택의 최대 크기를 예측해야 하며 크기를 실행 중에 변경할 수 없다
  • 만원인 스택에 새로운 원소를 push 시도할 경우 구현상의 오류를 일으킨다

 

 


 

 

연결리스트를 이용한 구현

단일연결리스트를 사용해서 스택을 구현할 수 있다

-> 삽입 / 삭제 기능이 top에서만 수행되므로 헤더노드는 불필요

 

top 원소를 연결리스트의 첫 노드에 저장하고, 그곳을 t로 가리키게 한다

 

 

일반 메서드

  • void initialize() : 초기 스택 생성
Alg initialize()
    // input top t
    // output an empty stack with top t

    // 초기에는 아무 노드도 없다
    t = NULL
    return

 

[그림 6] 노드 연결리스트 기반 스택 - initialize()

 

 

주요 스택 메서드

  • void push(e) : 스택에 원소를 삽입
Alg push(e)
    // input top t, element e
    // output none
    p <- getnode()
    p.elem <- e
    p.next <- t
    t <- p
    return

 

 

[그림 7] 노드 연결리스트 기반 스택 - push()

 

  • element pop() : 스택에서 원소 삭제 후 반환
Alg pop()
    // input top t
    // output element
    if (isEmpty())
        emptyStackException()
    e <- t.elem
    p <- t
    t <- t.next
    putnode(p)
    return e

 

[그림 8] 노드 연결리스트 기반 스택 - pop()

 

 

보조 스택 메서드

  • element top() : 가장 최근에 삽입된 원소 반환
Alg top()
    // input top t
    // output element
    return t.elem

 

  • integer size() : 저장된 원소의 개수를 반환
Alg size()
    // input None
    // output None
    p = t
    n = 0
    while (p != NULL)
        n += 1
    return n

 

  • boolean isEmpty() : 아무 원소도 저장되어 있지 않고 비어있는지 여부를 반환
Alg isEmpty()
    // input top t
    // output boolean

    if t = NULL
        return true
    else
        return false

 

 

<파이썬 구현>

더보기

 

# singly_linked_list.py

from asyncio.windows_events import NULL


class node():
    def __init__(self, data, next=NULL):
        self.data = data
        self.next = next

class singly_linked_list():
    
    # 동적 할당 받으면 되니까 처음부터 공간 얼마 필요한지 필요 X
    def __init__(self):
        self.head = node(NULL)
        self.n = 0
    
    # 일반 메서드
    def isEmpty(self):
        if self.n == 0:
            return True
        else:
            return False
    
    def size(self):
        return self.n
    
    def traverse(self):
        p = self.head
        while p != NULL:
            print(p.data, end=' ')
            p = p.next
        print()
        return
    
    # 접근 메서드
    def get(self, r):
        if r<1 or r>self.n:
            print("invalidRankException")
            return NULL
        else:
            p = self.head
            for i in range(1, r+1):
                p = p.next
            return p.data
    
    # 갱신 메서드
    def set(self, r, e):
        if r<1 or r>self.n:
            print("invalidRankException")
            return NULL
        else:
            p = self.head
            for i in range(1, r+1):
                p = p.next
            p.data = e
            return e
        
    def addFirst(self, e):
        p = self.head

        q = node(e, p.next)
        p.next = q
        self.n += 1
        return
    
    
    def removeFirst(self):
        if self.n == 0:
            print("emptyListException")
            return NULL
        else:
            p = self.head

            e = p.data
            p.next = (p.next).next

            self.n -= 1
            return e

 

# stack_linked_list.py
from asyncio.windows_events import NULL
from single_linked_list import singly_linked_list, node

class stack_linked_list:
    def __init__(self):
        self.linked_list = singly_linked_list()
        self.t = self.linked_list.head
    
    def push(self, e):
        p = node(e, self.t.next)
        self.t.next = p
        return

    def pop(self):
        if self.isEmpty():
            print("emptyStackException")
            return NULL
        e = self.t.next.data
        self.t.next = (self.t.next).next

        return e


    def isEmpty(self):
        if self.t.next == NULL:
            return True
        else:
            return False

    def size(self):
        p = self.t.next
        n = 0

        while p != NULL:
            n += 1
            p = p.next

        return n
    
    def top(self):
        return self.t.next.data

 

# stack.py

from stack_linked_list import stack_linked_list

stack = stack_linked_list()

stack.push('a')
stack.push('b')
stack.push('d')
stack.push('e')
print('top :', stack.top(), 'size :', stack.size())
print('remove', stack.pop())
print('top :', stack.top(), 'size :', stack.size())
stack.push('g')
print('top :', stack.top(), 'size :', stack.size())
stack.pop()
stack.pop()
stack.pop()
stack.pop()
stack.pop()

 

<실행 결과>

[그림 9] 실행 결과

 

 

 

 

성능

 

스택의 원소 개수가 n일 때,

  • 기억장소 사용 : O(n)
  • 각 작업의 실행 시간 : O(1)

 

3. 스택 응용

 

추후 포스팅에서 다루도록 하겠다.