자료구조

(C) 자료구조 - 스택의 이해

괴물좐 2023. 10. 17. 15:04

1. 스택의 개념과 구조

스택(Stack) 자료구조는 접시에 음식을 쌓아올리듯 데이터를 차곡차곡 쌓아올린 형태로 자료를 구성한다.

 

스택은 같은 구조와 같은 크기의 데이터를 정해진 방향으로만 쌓을 수 있고, top으로 정한  한 곳으로만 접근하도록 제한되어 있다. 따라서 top을 통해 들어온 데이터가 일정한 방향, 즉 아래(Bottom)에서 위로 차곡차곡 쌓이게 된다.

 

- top

스택에서 유일하게 액세스가 허용된 지점으로 삽입과 삭제가 일어나는 위치이며 현재 스택의 가장 위에 있는 데이터 위치가 된다.

새로 삽입되는 데이터가 스택에서 가장 위에 있는 데이터가 된다.

스택에서 자료를 삭제할 때 top을 통해서만 가능하기 때문에 top에 있는 데이터, 즉 가장 위에 있는 데이터가 삭제되고 삽입된다.

 

이러한 스택의 동작 구조를 후입선출(LIFO:Last-In-First-Out)이라고 한다.

 

 

- 스택의 구조

 

 

 

- 스텍의 데이터 삽입(push)과 삭제(pop) 과정

-> 스택에서 데이터가 삽입되고 삭제되는 순서를 결정하는 LIFO 구조는 반드시 지켜져야 한다.

-> 스택의 LIFO 동작 특성으로 인해 스택에서의 삭제 순서는 삽입 순서의 역순이 된다.


2. 스택의 구현

1
2
3
4
#define STACK_SIZE 100
 
typedef int element;                    // 스택 원소(element)의 자료형을 int로 정의
element stack[STACK_SIZE];        // 1차원 배열 스택 선언
cs

 

- 스택이 공백 상태인지 확인하는 연산

1
2
3
4
5
// 스택이 공백 상태인지 확인하는 연산
int isStackEmpty(void) {
    if (top == -1return 1;
    else return 0;
}
cs

 

- 스택이 포화 상태인지 확인하는 연산

1
2
3
4
5
// 스택이 포화 상태인지 확인하는 연산
int isStackFull(void) {
    if (top == STACK_SIZE - 1return 1;
    else return 0;
}
cs

 

2-1. 스택의 원소 삽입

1
2
3
4
5
6
7
8
// 스택의 top에 원소를 삽입하는 연산
void push(element item) {
    if (isStackFull()) {                        // 스택이 포화 상태인 경우
        printf("\n\n Stack is FULL! \n");
        return;
    }
    else stack[++top] = item;            // top을 증가시킨 후 현재 top에 원소 삽입
}
cs

 

2-2. 스택의 원소 삭제

1
2
3
4
5
6
7
8
// 스택의 top에서 원소를 삭제하는 연산
element pop(void) {
    if (isStackEmpty()) {                    // 스택이 공백 상태인 경우
        printf("\n\n Stack is Empty!!\n");
        return 0;
    }
    else return stack[top--];            // 현재 top의 원소를 삭제한 후 top 감소
}
cs

 

2-3. 스택의 원소 검색

1
2
3
4
5
6
7
8
// 스택의 top 원소를 검색하는 연산
element peek(void) {
    if (isStackEmpty()) {                    // 스택이 공백 상태인 경우
        printf("\n\n Stack is Empty !\n");
        return 0;
    }
    else return stack[top];                // 현재 top의 원소 확인
}
cs

 

2-4. 스택의 원소 출력

1
2
3
4
5
6
7
8
// 스택의 원소를 출력하는 연산
void printStack(void) {
    int i;
    printf("\n STACK [");
    for (i = 0; i <= top; i++)
        printf("%d "stack[i]);
    printf("] ");
}
cs

 

- 순차 자료구조를 이용해 순차스택 구현하기 -> 배열 연산을 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include "stackS.h"
 
int main(void) {
    element item;
    printf("\n** 순차 스택 연산 **\n");
    printStack();
    push(1); printStack();        // 1 삽입
    push(2); printStack();        // 2 삽입
    push(3); printStack();        // 3 삽입
 
    item = peek(); printStack();            // 현재 top의 원소 출력
    printf("peek => %d", item);
 
    item = pop(); printStack();            // 삭제
    printf("\t pop => %d", item);
 
    item = pop(); printStack();            // 삭제
    printf("\t pop => %d", item);
 
    item = pop(); printStack();            // 삭제
    printf("\t pop => %d\n", item);
 
    getchar(); return 0;
}
cs

 

 

 

 

 

 

 

 


3. 연결 자료구조를 이용한 스택의 구현

순차 자료구조를 이용한 스택은 1차원 배열 변수를 선언하여 쉽게 구현할 수 있다. 하지만, 물리적으로 고정된 배열을 사용하기 때문에 사용 중에 스택의 크기 변경이 어려우므로 배열 크기를 미리 크게 할당해 놓기 때문에 메모리 사용의 효율성이 떨어지는 문제가 생긴다. -> 해결방법 : 연결 자료구조 사용

 

- 단순 연결 리스트를 이용한 연결 스택 구현

-> 스택의 원소는 연결 리스트의 노드가 된다.

-> 스택에 원소를 삽입할 때마다 연결 리스트에 노드를 하나씩 연결한다.

-> 스택 원소의 순서는 연결 리스트 노드의 링크를 사용하여 표현한다.

-> 스택의 top을 표현하기 위해 포인터 top을 사용한다.

-> 스택의 초기 상태(공백 스택)는 포인터 top을 NULL 포인터로 설정하여 표현한다.

 

 

- 연결 자료구조를 이용해 연결 스택 구현하기

1
2
3
4
5
6
7
8
typedef int element;                // 스택 원소(element)의 자료형을 int로 정의
 
typedef struct stackNode {        // 스택의 노드를 구조체로 정의
    element data;
    struct stackNode* link;
} stackNode;
 
stackNode* top;                    // 스택의 top 노드를 지정하기 위해 포인터 top 선언
cs

 

- 스택이 공백 상태인지 확인하는 연산

1
2
3
4
5
// 스택이 공백 상태인지 확인하는 연산
int isStackEmpty(void) {
    if (top == NULLreturn 1;
    else return 0;
}
cs

 

3-1. 스택의 top에 원소를 삽입하는 연산

1
2
3
4
5
6
7
8
// 스택의 top에 원소를 삽입하는 연산
void push(element item) {
    stackNode* temp = (stackNode*)malloc(sizeof(stackNode));
 
    temp->data = item;
    temp->link = top;                // 삽입 노드를 top의 위에 연결
    top = temp;                        // top 위치를 삽입 노드로 이동
}
cs

 

3-2. 스택의 top에서 원소를 삭제하는 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 스택의 top에서 원소를 삭제하는 연산
element pop(void) {
    element item;
    stackNode* temp = top;
 
    if (isStackEmpty()) {                // 스택이 공백 리스트인 경우
        printf("\n\n Stack is empty !\n");
        return 0;
    }
    else {                                // 스택이 공백 리스트가 아닌 경우
        item = temp->data;
        top = temp->link;            // top 위치를 삭제 노드 아래로 이동
        free(temp);                    // 삭제된 노드의 메모리 반환
        return item;                    // 삭제된 원소 반환
    }
}
cs

 

3-3. 스택의 top에 원소를 검색하는 연산

1
2
3
4
5
6
7
8
9
10
// 스택의 top 원소를 검색하는 연산
element peek(void) {
    if (isStackEmpty()) {                // 스택이 공백 리스트인 경우
        printf("\n\n Stack is empty !\n");
        return 0;
    }
    else {                                // 스택이 공백 리스트가 아닌 경우
        return(top->data);            // 현재 top의 원소 반환
    }
}
cs

 

3-4. 스택의 원소를 top에서 bottom 순서로 출력하는 연산

1
2
3
4
5
6
7
8
9
10
// 스택의 원소를 top에서 bottom 순서로 출력하는 연산
void printStack(void) {
    stackNode* p = top;
    printf("\n STACK [ ");
    while (p) {
        printf("%d ", p->data);
        p = p->link;
    }
    printf("] ");
}
cs

 

- 연결 스택 연산 예제 -> 포인터 연산을 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include "stackL.h"
 
int main(void) {
    element item;
    top = NULL;
    printf("\n** 연결 스택 연산 **\n");
    printStack();
 
    push(1);    printStack();                    // 1 삽입
    push(2);    printStack();                    // 2 삽입
    push(3);    printStack();                    // 3 삽입
 
    item = peek(); printStack();                // 현재 top의 원소 출력
    printf("peek = > % d", item);
 
    item = pop(); printStack();                // 삭제
    printf("\t pop => %d", item);
 
    item = pop(); printStack();                // 삭제
    printf("\t pop => %d", item);
 
    item = pop(); printStack();                // 삭제
    printf("\t pop => %d", item);
 
    getchar();        return 0;
}
cs