이 포스팅은 윤성우 님의 열혈 자료구조를 기반으로 합니다.

스택의 이해와 ADT 정의

스택은 자료구조중에 하나로서 흔히 박스를 쌓는 구조로 설명을 한다. 이를 그림으로 표현하면 다음과 같다.

스택의 그림


위의 그림을 보면 2, 4, 6, 8, 10 의 순서로 삽입을 한 형태이다. 먼저 들어온 2가 바닥에 있고 나중에 넣은 10이 위에 있음을 알 수 있다. 이를 상자를 쌓았다고 가정해보자. 상자의 밑 부분 부터 뺄 수 없으므로 당연히 맨 위에 부분부터 뺄 수 있을 것이다. 스택 역시 마찬가지이며 10 부터 뺼 수 있음을 알 수 있다. 이를 후입선출이라 하며 영어로 LIFO(Last In First Out)이다. 그렇다면 스택의 ADT를 살펴보자. 다음과 같다.

void StackInit(Stack * pstack);
// 스택의 초기화를 진행한다.
// 스택 생성 후 제일 먼저 호출되어야 하는 함수이다.

int SIsEmpty(Stack * pstack);
// 스택이 빈 경우 TRUE(1)을, 그렇지 않은 경우 FALSE(0)을 반환한다.

void SPush(Stack * pstack, Data data);
// 스택에 데이터를 저장한다. 매개변수 data로 전달된 값을 저장한다.

Data SPop(Stack * pstack);
// 마지막에 저장된 요소를 삭제한다.
// 삭제된 데이터는 반환이 된다.
// 본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다.

Data SPeek(Stack * pstack);
// 마지막에 저장된 요소를 반환하되 삭제하지 않는다.
// 본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다.

이제 이 ADT를 기반으로 배열 기반, 연결 리스트 기반으로 구현해보자.


배열 기반 스택 구현

스택에서 구현해야 하는 함수는 push, pop이 있고 이를 top이라는 스택의 가장 윗 부분을 담당하는 변수를 이용하여 구현한다.
가장 중요한 함수는 push, pop으로 push의 경우 top을 한칸 올리고 top이 가리키는 위치에 데이터를 저장하고
pop의 경우 top이 가리키는 데이터를 반환하고, top을 아래로 한칸 내린다. push연산을 그림으로 표현하면 다음과 같다.

push 연산

확인할 수 있는 점은 top의 시작은 -1 이며 하나의 데이터가 들어올 때 0이 된다는 것이다. 처음에 A를 추가할 때
top이 하나 증가한 뒤 해당 top의 위치에 원소가 추가되는것을 확인할 수 있으며 이후로도 마찬가지 이다.

pop연산역시 그림으로 표현하면 다음과 같다.

pop 연산

소스코드는 다음과 같다.

<ArrayBaseStack.h>

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
#ifndef __AB_STACK_H__
#define __AB_STACK_H__
 
#define TRUE    1
#define FALSE    0
#define STACK_LEN    100
 
typedef int Data;
 
typedef struct _arrayStack
{
    Data stackArr[STACK_LEN];
    int topIndex;
} ArrayStack;
 
typedef ArrayStack Stack;
 
void StackInit(Stack * pstack);
int SIsEmpty(Stack * pstack);
 
void SPush(Stack * pstack, Data data);
Data SPop(Stack * pstack);
Data SPeek(Stack * pstack);
 
#endif
cs

<ArrayBaseStack.h>

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
28
29
30
31
32
33
34
35
36
37
38
39
#include "ArrayBaseStack.h"
#include <stdio.h>
 
void StackInit(Stack* pstack) {
    pstack->topIndex = -1;
}
 
int SIsEmpty(Stack* pstack) {
    if (pstack->topIndex == -1)
        return TRUE;
    else
        return FALSE;
}
 
void SPush(Stack* pstack, Data data) {
    pstack->topIndex += 1;
    pstack->stackArr[pstack->topIndex] = data;
}
 
Data SPop(Stack* pstack) {
    if (SIsEmpty(pstack)) {
        printf("Stack memory error!");
        return -1;
    }
 
    Data rdata = pstack->stackArr[pstack->topIndex];
    pstack->topIndex -= 1;
 
    return rdata;
}
 
Data SPeek(Stack* pstack) {
    if (SIsEmpty(pstack)) {
        printf("Stack memory error!");
        return -1;
    }
 
    return pstack->stackArr[pstack->topIndex];
}
cs

<ArrayBaseStackMain.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include "ArrayBaseStack.h"
 
int main(void)
{
    // Stack의 생성 및 초기화 ///////
    Stack stack;
    StackInit(&stack);
 
    // 데이터 넣기 ///////
    SPush(&stack1);  SPush(&stack2);
    SPush(&stack3);  SPush(&stack4);
    SPush(&stack5);
 
    // 데이터 꺼내기 ///////
    while(!SIsEmpty(&stack))
        printf("%d ", SPop(&stack)); 
 
    return 0;
}
cs

스택의 연결 리스트 기반 구현

앞서 스택을 배열로 구현하였는데 연결 리스트로도 구현할 수 있다. 단순하게 자료구조가 바뀐것 이외에는 개념적 단계에서의 구현에는 큰 차이가 없으므로 바로 헤더파일과 이를 통한 구현을 보일것이다.

<ListBaseStack.h>

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
28
29
30
#ifndef __LB_STACK_H__
#define __LB_STACK_H__
 
#define TRUE    1
#define FALSE    0
 
typedef int Data;
 
typedef struct _node
{
    Data data;
    struct _node * next;
} Node;
 
typedef struct _listStack
{
    Node * head;
} ListStack;
 
 
typedef ListStack Stack;
 
void StackInit(Stack * pstack);
int SIsEmpty(Stack * pstack);
 
void SPush(Stack * pstack, Data data);
Data SPop(Stack * pstack);
Data SPeek(Stack * pstack);
 
#endif
cs

<ListBaseStack.c>

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "ListBaseStack.h"
#include <stdio.h>
#include <stdlib.h>
 
void StackInit(Stack* pstack) {
    pstack->head = NULL;
}
 
int SIsEmpty(Stack* pstack) {
    if (pstack->head == NULL)
        return TRUE;
    else
        return FALSE;
}
 
void SPush(Stack* pstack, Data data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
 
    newNode->next = pstack->head;
    pstack->head = newNode;
}
 
Data SPop(Stack* pstack) {
    if (SIsEmpty(pstack)) {
        printf("stack memory error!");
        exit(-1);
    }
 
    Data rdata;
    Node * rNode;
 
    rNode = pstack->head;
    rdata = pstack->head->data;
    
    pstack->head = pstack->head->next;
 
    free(rNode);
 
    return rdata;
}
 
Data SPeek(Stack* pstack) {
    if (SIsEmpty(pstack)) {
        printf("stack memory error!");
        exit(-1);
    }
 
    return pstack->head->data;
}
cs

<ListStackBaseMain.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include "ListBaseStack.h"
 
int main(void)
{
    // Stack의 생성 및 초기화 ///////
    Stack stack;
    StackInit(&stack);
 
    // 데이터 넣기 ///////
    SPush(&stack1);  SPush(&stack2);
    SPush(&stack3);  SPush(&stack4);
    SPush(&stack5);
 
    // 데이터 꺼내기 ///////
    while(!SIsEmpty(&stack))
        printf("%d ", SPop(&stack)); 
 
    return 0;
}
cs

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

4. 연결 리스트  (0) 2021.07.19
3. 배열 기반 리스트  (0) 2021.07.19
2. 재귀 (Recursion)  (0) 2021.06.27
1. 자료구조와 알고리즘의 이해  (0) 2021.06.27

DFS란?

DFS 즉 Depth-Frist-Search는 영어를 해석한 그대로 깊이를 우선으로 탐색하는 즉 깊이 우선 탐색입니다.

깊이를 우선 탐색한다는 것은 그래프에서 한 노드에서 연결된 다른 노드들 중 한 방향의 노드에 대해서 계속 파고 들어간다는 뜻 입니다. 그림 1을 보시면 이해가 더욱 쉽습니다. 

 

그림 1

 

그림 1에서 노드 1을 시작점으로 DFS를 수행해보겠습니다. (위 예시에서 설명을 용이하게 하기 위해 낮은 숫자부터 방문한다고 가정하겠습니다.)

1번을 탐색하고 난 뒤 갈 수 있는곳은 2, 3, 7 이 있지만 낮은 숫자인 2를 탐색합니다.(탐색:  1, 2)

2번을 탐색하고 난 뒤 갈 수 있는곳은 9 가 있습니다. 9를 탐색합니다. (탐색: 1, 2, 9)

9번을 탐색하고 난 뒤 갈 수 있는곳은 3, 4가 있지만 낮은 숫자인 3을 탐색합니다. (탐색: 1, 2, 9, 3)

3번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 9로 돌아온 후 4를 탐색합니다. (탐색: 1, 2, 9, 3, 4)

4번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 1로 돌아온 후 7을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7)

7번을 탐색하고 난 뒤 갈 수 있는곳이 6, 8, 이 있지만 낮은 숫자인 6을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6)

6번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 7로 돌아온 후 8을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6, 8)

8번을 탐색하고 난 뒤 갈 수 있는곳은 5가 있습니다. 5를 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6, 8, 5)

모든 곳을 탐색하였으므로 탐색을 종료합니다.

탐색이 되는 순서는 위와 같이 1, 2, 9, 3, 4, 7, 6, 8, 5입니다. DFS는 구현을 스택을 이용하여 합니다만 제가 공부를 했던 나동빈 님의 이것이 취업을 위한 코딩 테스트다 with Python에서는 재귀의 형태로 구현을 하였고 저도 해당 코드를 인용하기 때문에 재귀를 이용하여 구현하겠습니다.

 


소스코드

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def dfs(graph, visited, start):
  visited[start] = True # 방문처리
  print(start, end=" "
  for i in graph[start]: # start와 연결되어 있는 노드 탐색 중
    if not visited[i]: # 방문하지 않은 노드가 있다면
      dfs(graph, visited, i) # 재귀적으로 호출
 
graph = [
  [],
  [237],
  [19],
  [19],
  [9],
  [8],
  [7],
  [168],
  [57],
  [234]
]
visited = [False* 10
 
dfs(graph, visited, 1)
cs

+ Recent posts