Published 2023. 2. 20. 20:55

스택의 정의와 연산

  • 스택은 선입후출(FILO) 또는 후입선출(LIFO)의 특징을 가지는 자료구조로 나중에 들어온 데이터가 먼저 나가는 특징이 존재하는 자료구조이다.
  • 이를 다른 말로 하면 한 곳(Tail)에서만 삽입과 삭제 연산이 일어나는 자료구조이다.
  • 일반적으로 들어온 인풋의 역순으로 연산이 진행되어야 하는 경우 스택을 사용할 수 있다.
  • 스택의 연산은 일반적으로 다음과 같이 나열할 수 있다.
    • 데이터의 추가
    • 데이터의 제거
    • 데이터의 조회
    • 스택의 상태(비어있는지)

 

 

 

스택의 구현 방법

  • 스택은 배열과 리스트로 구현할 수 있다. 배열과 리스트의 장 단점은 각각 다른데 배열의 초기 길이를 변경하지 못한다는 크리티컬한 단점으로 인해 일반적으로 연결 리스트를 이용하여 구현하게 된다.
  • 이번에도 Node Pool을 이용한다. Node 클래스의 경우 연결 리스트 구현에서도 적은 적이 있는 만큼 생략하고 넘어가도록 한다.
  • 그리고 우리가 구현할 연산들을 메서드 형태로 구현해놓는다.
class Stack{
    private static final int MAX_NODE = 10001;
    int nodeCnt;
    Node[] nodePool;
    Node head;

    Stack(){
        nodePool = new Node[MAX_NODE];
        nodeCnt = 0;
        head = new Node(-1);
    }

    Node getNode(int data){
        nodePool[nodeCnt] = new Node(data);

        return nodePool[nodeCnt++];
    }

    void addLast(int data){
        
    }

    int pop(){
        
    }

    int peek(){
        
    }

    boolean isEmpty(){
        
    }

    int size(){
        
    }
}

 

 

 

스택의 구현 - 데이터 삽입

  • 스택에서 데이터의 삽입과 삭제는 연결 리스트의 끝 부분에서 일어난다. 즉 데이터의 삽입은 연결 리스트에서 맨 마지막 노드 뒤에 데이터를 삽입하는 연산과 완벽히 동일하다.
void addLast(int data){
    Node newNode = getNode(data);
    Node start = head;

    while(start.next != null){
        start = start.next;
    }

    start.next = newNode;
}
  • 동작 원리와 구현이 완벽히 동일하므로 이 이상의 설명은 하지 않겠다.

 

 

 

스택의 구현 - 데이터 삭제

  • 데이터의 삭제 역시 연결 리스트의 끝 부분에서만 일어난다.
  • 우리는 이전에 데이터를 입력받아 그 데이터와 동일한 데이터를 가진 노드를 삭제하는 연산을 구현했지만 이번에는 리스트의 맨 마지막 노드를 삭제하는 것만 구현하면 된다.
  • 이는 리스트의 순회를 이용해 마지막 노드 바로 이전의 노드를 가리키도록 위치를 잡고 이전 노드의 next를 null로 바꿔줌으로서 완료된다.
int pop(){
    if(isEmpty()){
        return -1;
    }

    Node start = head;
    Integer ret = null;

    while(start.next.next != null){
        start = start.next;
    }

    ret = start.next.data;
    start.next = null;
    nodeCnt--;

    return ret;
}
  • 이번에는 어떤 제약조건 없이 구현하는 것이기 때문에 스택이 비어있을 때 pop 연산이 실행될 상황도 고려해야 한다.

 

 

스택의 구현 - 데이터의 조회

  • 데이터의 조회 연산은 값만 가져오고 해당 데이터를 삭제하지 않는 것을 의미하며 리스트의 순회를 통해 마지막에 있는 값을 가져오는 것으로 구현이 가능하다.
int peek(){
    if (isEmpty()){
        return -1;
    }

    Node start = head;

    while(start.next != null){
        start = start.next;
    }

    return start.data;
}

 

 

스택의 구현 - 스택의 상태 조회

  • 스택이 비어있는지와 그 크기는 우리가 Stack 클래스에 포함시킨 nodeCnt를 사용하여 손쉽게 구현이 가능하다.
boolean isEmpty(){
    return this.nodeCnt == 0;
}

int size(){
    return this.nodeCnt;
}

 

 

 

정리

  • Stack의 구현은 연결 리스트의 구현만 제대로 마쳐놓는다면 매우 쉬운 일이 될 것이다. 지금은 단방향 연결 리스트를 사용하여 구현하였기 때문에 리스트의 맨 끝까지 순회하여 가는데 걸리는 시간만큼 손해를 보게 된다.
  • 이는 양방향 연결 리스트를 통해 해결이 가능하다. 사실 양방향을 구현하지 않더라도 맨 끝을 계속 가리키고 있는 포인터 peek만 잡아 주어도 순회와 관련된 연산이 제거될 것이다. 이것을 사용하여 구현하는 것은 여러분의 몫으로 남기도록 하겠다.
  • Java에서 Stack은 Stack클래스가 존재하며 이를 이용해 구현할 수 있지만 Stack 클래스는 Vector클래스를 상속받아 확장한 형태이며 Vector 클래스 자체가 문제가 많은 컬렉션 클래스이기 때문에 잘 사용되지 않는다. 이후에 우리가 배울 Deque클래스를 사용해 일반적으로 구현한다.
  • 전체적인 코드와 테스트 코드는 다음과 같다.
class Node{
    int data;
    Node next;

    Node(int data){
        this.data = data;
        this.next = null;
    }
}

class Stack{
    private static final int MAX_NODE = 10001;
    int nodeCnt;
    Node[] nodePool;
    Node head;

    Stack(){
        nodePool = new Node[MAX_NODE];
        nodeCnt = 0;
        head = new Node(-1);
    }

    Node getNode(int data){
        nodePool[nodeCnt] = new Node(data);

        return nodePool[nodeCnt++];
    }

    void addFirst(int data){
        Node newNode = getNode(data);

        newNode.next = head.next;
        head.next = newNode;
    }

    void addLast(int data){
        Node newNode = getNode(data);
        Node start = head;

        while(start.next != null){
            start = start.next;
        }

        start.next = newNode;
    }

    int pop(){
        if(isEmpty()){
            return -1;
        }

        Node start = head;
        Integer ret = null;

        while(start.next.next != null){
            start = start.next;
        }

        ret = start.next.data;
        start.next = null;
        nodeCnt--;

        return ret;
    }

    int peek(){
        if (isEmpty()){
            return -1;
        }

        Node start = head;

        while(start.next != null){
            start = start.next;
        }

        return start.data;
    }

    boolean isEmpty(){
        return this.nodeCnt == 0;
    }

    int size(){
        return this.nodeCnt;
    }
}


public class UserSolution{
    public static void main(String[] args){
        Stack stack = new Stack();

        stack.addLast(1);
        stack.addLast(2);
        stack.addLast(3);
        stack.addLast(4);
        stack.addLast(5);

        System.out.println(stack.peek());
        System.out.println(stack.size());
        System.out.println(stack.isEmpty());

        stack.pop();
        stack.pop();
        stack.pop();

        System.out.println(stack.size());
        System.out.println(stack.peek());

        stack.pop();
        stack.pop();

        System.out.println(stack.isEmpty());
    }
}

'DataStructure > List' 카테고리의 다른 글

덱(deque)의 정의와 구현  (0) 2023.02.23
큐(Queue)의 정의와 구현  (0) 2023.02.23
LinkedList의 구현 - Single Linked List  (0) 2023.02.20
LinkedList의 구현 - Single Linked List  (0) 2023.01.30
LinkedList의 정의와 구조  (0) 2023.01.30
복사했습니다!