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

[자료구조] 스택 - Stack

by sy.cho__ 2017. 11. 12.

스택에 대해 알아보도록 하겠습니다.

스택은 자료구조에서 아주 기초이자 중요한 개념입니다. 개념부터 설명하겠습니다.


스택 개념


stack은 사전적 의미는 "쌓다" 입니다. 상자 BOX를 생각하면 쉽게 이해할 수 있습니다.

박스에 책을 쌓는다고 가정하겠습니다. 책은 바닥부터 차례대로 쌓아지겠죠?? 책을 꺼내려면 어떻게 해야할까요!

아마 제일 최근에 쌓은 책을 처음 꺼낼 수 있고, 맨 처음에 넣은 책은 마지막에 꺼낼 수 있을것 같습니다.

그림으로 보면 아래와 같아요!



제일 먼저 넣은 데이터는 1번이고, 제일 마지막에 넣은 데이터는 4번입니다.

데이터를 꺼낼때는 반대로 4번이 제일 처음 나오고 1번이 마지막으로 나올 것입니다

그래서 스택을 "선입후출" , "후입선출" 의 특징을 가지고 있다고 말합니다!



어디에 쓰일까?


그럼 이 스택이라는 자료구조는 어디에 쓰일 수 있을까요??





지금 이 글을 보고 계시는 여러분은 웹사이트를 통해 보고 있을겁니다.

뒤로가기 버튼을 누르면 어떻게 되나요?! 아마 제일 최근에 방문했던 사이트로 이동될것입니다.


즉, 지금 보고 계신 웹사이트주소가 스택의 가장 상단!에 있다고 할 수 있고, 바로 이전에 봤던 사이트주소가 스택에 쌓여있다고 할 수 있죠.

가장 위에 있는 주소를 삭제하고 바로 그 아래 있는 주소를 확인하면, 전 페이지로 갈 수 있는 주소가 나오고 이렇게 뒤로가기를 구현할 수 있습니다!



스택 사용법


스택은 크게 삽입(Push) , 삭제(Pop) , 읽기(Peek) , 스택크기(Size) 로 구성되어 있습니다.


(1) 삽입 (Push)


상자의 맨 위에 데이터를 추가하는 것을 말합니다. 다음 그림을 통해 설명하겠습니다. 



현재 3까지 저장되어 있는 스택에 새로운 4를 삽입하는 과정입니다. 

구현은 어떻게 하면 될까요? 배열로 구현했다고 가정한다면, 맨 위를 가리키는 인덱스 top을 1증가하고, 그 자리에 새로운 데이터 4를 넣으면 될 것 같습니다.

자세한 구현설명은 아래에서 하겠습니다.


(2) 삭제 (Pop)


스택의 맨 위에 있는 데이터를 삭제하는 과정입니다. 아래 그림을 보면 쉽게 이해할 수 있습니다. 


이 역시 구현은 아래에서 설명하겠습니다.


(3) 읽기 (Peek)


맨 상단의 데이터를 확인하는 작업입니다. 만약 아래와 같은 스택이 있다면 Peek을 했을 때 return값은 4가 되겠죠??


(4) 스택크기 (Size)


현재 스택에 몇개의 데이터가 있는지 알고 싶은 경우가 있을 겁니다. 아래와 같은 경우 스택 사이즈는 4개가 되겠네요. 

스택은 맨 상단의 데이터만 읽을수 있기 때문에(Peek), 따로 size 변수를 만들어 놓거나 배열로 구현한 경우 맨 상단을 가리키는 top 변수를 이용해서 스택의 크기를 알 수 있습니다. 



스택의 구현


스택의 구현 방법은 배열과 연결리스트가 있습니다. 연결리스트에 대해 공부하고 싶은 분은 다음 링크를 통해 확인해주세요!

그럼 언제 배열을 쓰고, 언제 연결리스트를 사용할까요?

이것은 배열과 연결리스트의 장,단점을 확인한 후 정할 수 있습니다. 


배열과 연결리스트의 장,단점을 다시 한번 확인하겠습니다.



여러 장, 단점이 있지만 스택에 있어서 두 자료구조의 가장 차이점은 스택의 크기입니다. 


위에서 사용한 스택 그림을 다시 한번 확인해보시길 바랍니다. 

데이터를 4가지 쌓았는데, 상자가 거의 꽉! 차고 말았습니다. 이와 같이 배열로 구현을 하면 스택의 크기가 제한적이라고 할 수 있습니다. 

스택의 최대크기를 알고 있을 때, 최대크기가 너무 크지 않을 때! 배열을 통해 스택을 구현할 수 있습니다.


반대로 연결리스트를 사용하면 스택의 크기에 상관없이 구현할 수 있습니다!

그럼 단점은...? 배열에 비해 구현하기가 번거롭습니다..


어떤 자료구조를 이용해 구현할지는 여러분에게 맡기겠습니다.



(1) 배열을 이용한 스택의 구현


바로 코드를 통해 설명하겠습니다.


#include <iostream>

using namespace std;

#define MAX_SIZE 1000 // 배열의 최대 사이즈를 설정합니다. 


void Stack_push(int *stack,int val,int *top){

    stack[*top] = val;

    *top += 1;

}


int Stack_pop(int *stack, int *top){

    *top -= 1;

    return stack[*top];

}


int main(){

    

    //Array_stack

    int stack[MAX_SIZE];

    int top = 0;

    

    int n,val;

    

    cin>>n;

    

    for(int i=0;i<n;i++){

        cin>>val;

        if(top>=MAX_SIZE){

            cout<<"Out of range\n";

            cout<<"Cannot Push anymore\n";

        }

        else{

            //STACK_PUSH

            Stack_push(stack,val,&top);

            cout<<val<<" Push completed\n";

        }

    }

    

    cout"------------------\n"

    

    for(int i=0;i<n;i++){

        // Print current Stack Size

        cout<<"Stack Size is : "<<top<<'\n';

        

        if(top>0){

            int popvalue = Stack_pop(stack,&top);

            cout<<popvalue<<" is removed\n";

        }

    }

    

    return 0;

}


아마 쉽게 이해하실 수 있을겁니다.

Push할때는 현재 top에 값을 넣고, top의 크기를 1개 늘려주면 됩니다.

반대로 Pop은 top의 크기를 하나 줄여주면 끝입니다!


구현은 쉽지만, 미리 언급 했던것 처럼 다음과 같은 단점이 존재합니다.


- 스택의 사이즈가 제한적

- 스택의 사이즈가 정해져있기 때문에 사용하지 않을 경우 메모리 낭비


그러나 구현하기가 쉬워서 저는 자주 사용합니다!


(2) 연결리스트를 이용한 스택의 구현


단순 연결리스트 데이터 삽입 구현 방법 중 "리스트의 맨 앞에 삽입하기"가 있었습니다.

Push의 구현은 이와 똑같이 하면 됩니다.


다만 삭제(Pop)할 때 무조건 맨 앞에 있는 데이터를 삭제한다고 보면 됩니다.


코드를 통해 설명하겠습니다.


//

//  Stack_by_linkedlist.cpp

//  Algorithm

//

//  Created by seungyun cho on 2017. 11. 12..

//  Copyright © 2017 seungyun cho. All rights reserved.

//


#include <iostream>

using namespace std;


typedef struct Node{

    int data;

    Node *next;

}Node;


Node *head;


int size_stack = 0;


void Stack_push(int val){

    //새로운 노드를 만들고

    Node *newNode = new Node;

    newNode->data = val;

    

    //리스트의 (head) 연결합니다.

    newNode->next = head->next;

    head->next = newNode;

    size_stack++;

}


int Stack_pop(){

    

    

    int popvalue;

    

    if(size_stack<1){

        return -1;

    }

    //temp 배열에 맨앞에 노드의 주소를 저장합니다.

    Node *temp = head->next;

    popvalue = temp->data;

    

    //head 두번째 노드에 연결시키고

    head->next = temp->next;

    

    //첫번째 노드를 삭제합니다.

    free(temp);

    size_stack--;

    return popvalue;

}


int main(){

    

    //head 초기화

    head = new Node;

    

    int n;

    int val;

    cin>>n;

    

    for(int i=0;i<n;i++){

        cin>>val;

        Stack_push(val);

        cout<<val<<" is added\n";

    }

    

    cout<<"------------------------------\n";

    

    for(int i=0;i<n;i++){

        cout<<"Current stack size is : "<<size_stack<<'\n';

        int popvalue = Stack_pop();

        if(popvalue==-1){

            cout<<"Stack is empty\n";

        }

        else{

            cout<<popvalue<<" is removed\n";

        }

    }

    

    return 0;

}


위와 같이 구현할 수 있습니다. 

stack 사이즈의 경우 리스트를 모두 탐색하려면 시간이 오래 걸리기 때문에 size_stack 변수를 선언하여 push될때 1씩 증가하고,  pop될때 1씩 감소하도록 했습니다.



두 가지 구현방법에 대해 알아보았습니다.

설명이 부족했거나 이해가 안되는 부분 있으면 답변 달아주시길 바랍니다.

감사합니다!




반응형

'알고리즘 > 자료구조' 카테고리의 다른 글

자료구조 : 연결리스트 (Linked list)  (1) 2017.11.08
다익스트라 알고리즘(Dijkstra)  (0) 2017.10.06