cf> 미로는 stack의 좋은 응용이 될 것이다.
5. Mazing Problem
1) 미로 표현 방법
1> 미로 구조
- 미로 전체는 Two-dimensional array로 표현한다. ( maze[R][C] )
- 그리고 이동 가능한 길을 0, 벽을 1로 표현한다.
- 미로 경계를 표현하기 위해 ( maze[R+2][C+2] )
- 행과 열을 2개씩 더 확보하고
- 경계 부분을 1로 표시한다.
- 미로의 입구는 [1][1]이며 미로의 출구는 [R][C]이다.
2> 이동 방향
- 기본적으로 8개 방향으로 이동 가능하다.
- x방향과 y방향을 가지는 구조체 생성
- 8개의 구조체(원소)를 가지는 구조체 array 생성
- 하지만 [row][col] 주변에 경계 혹은 벽이 있을 경우 이동 가능한 방향이 적어질 수 있다.
typedef struct {
short int vert;
short int horiz;
} offsets;
offsets move[8]; /*array of moves for each direction*/
3> 미로 내부 이동 구현 (index 이용)
- 현재 위치 : [row][col] / 현재 위치의 상태 : maze[Row][Col]
- 다음 위치 : [nextRow][nextCol] / 다음 위치의 상태 : maze[nextRow][nextCol]
- nextRow = row + move[dir].vert
- nextCol = col + move[dir].hori
4> 방문 여부 기록
- 방문 여부 : mark라는 Two-dimensional array에 기록
cf> stack의 적용 : 입장부터 현재까지의 위치를 stack에 기록한다.
- 미로를 이동할 때에는 여러 방향의 선택이 있을 수 있다. 이때 어떤 방향이 최상의 선택인지 알 수 없으므로,현재의 위치를 저장하고 선택할 수 있는 한 방향을 선택한다. 이렇게 현재의 위치를 저장하면 만약 잘못돈 길을 갔을 때 되돌아와서 다른 방향을 시도 할 수 있다.
5> stack의 구현
- 이동 경로를 element라는 단위별로 쪼개서 저장한다. (element array = stack = 전체 이동경로)
- element : 위치와 이동경로를 한 번에 저장하는 구조체
- row, col : 이동 경로 상에서의 위치 (행과 열)
- dir : 다음에 이동한 방향
- 그리고 element들의 array를 stack으로 정의한다.
- stack의 원소의 개수는 미로 탐색 중 방문하는 위치이면 충분
(미로 내의 0의 개수이면 충분 -> 최악의 경우를 고려해 mXp array의 경우 m*p개의 원소를 선언하면 충분하다.)
2) 구현 - 먼저 선언할 것
1> declare constant
- EXIT_ROW(EXIT_COL) : maze의 경계선을 제외한 행의 개수 = maze에서 출구의 행 index
- MAX_STACK_SIZE : 이동 경로를 기록할 stack(element array)의 최대 원소 개수
- TRUE, FALSE : 각각 1과 0
2> global variable
- top : 이동 경로를 기록할 stack의 top (가장 최근 이동경로가 저장된 index)
3> struct
- offset : 이동 방향 8개를 간단히 저장하기 위한 구조체 (북쪽 방향부터 시계방향으로 8개 방향)
- element : 위치와 이동경로를 한 번에 저장하는 구조체
- row, col : 이동 경로 상에서 위치(행과 열)
- dir : 다음에 이동한 방향
4> array
- stack : 이동 경로 전체를 저장 (element를 MAX_STACK_SIZE개의 array 형태로 저장)
- move : 이동 방향(offsets) 8개를 array로 저장
- maze : 미로의 경계선까지 포함한 미로의 구조
- 미로 자체의 크기는 maze[EXIT_ROW][EXIT_COL]이지만 경계선을 포함해서 maze[EXIT_ROW+2][EXIT_COL+2] 선언
- 0은 이동 가능한 공간, 1은 벽을 의미
- 경계 또한 1로 초기화한다.
- mark : 방문 여부를 기록할 array (maze와 크기가 같게 만든다.)
5> 추가적으로 필요한 함수
- element pop() : stack의 마지막 원소를 return하고 top 1감소(element 제거)
- void push(element cur) : top 1 증가 (새로운 원소를 추가할 공간 생성)하고 새로운 원소 추가
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK_SIZE 100 /* maximum queue size */
#define EXIT_ROW 3 // maze 경계선 제외 행의 개수 = maze 출구의 행 index
#define EXIT_COL 3 // maze 경계선 제외 열의 개수 = maze 출구의 열 index
#define FALSE 0;
#define TRUE 1;
typedef struct {
short int row;
short int col;
short int dir;
}element;
element stack[MAX_STACK_SIZE];
typedef struct {
short int vert;
short int horiz;
} offsets;
offsets move[8] = { {-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1} };
/*array of moves for each direction*/
int maze[EXIT_ROW + 2][EXIT_COL + 2] = { {1,1,1,1,1}, {1,0,0,0,1}, {1,0,1,1,1}, {1,0,0,0,1}, {1,1,1,1,1} };
int mark[EXIT_ROW + 2][EXIT_COL + 2]; // 모두 0으로 초기화
int top;
element pop() {
return stack[top--];
}
void push(element cur) {
stack[++top] = cur;
}
3) 구현 - 함수 내부
1> local variable
- row, col : 현재 maze에서 위치하는 공간
- nextRow, nextCol : 다음 순서에 이동할 공간
- dir : 다음에 이동할 방향
- position : (element 자료형) 현재 이동 경로 (현재 위치와 다음에 이동할 방향 정보 저장)
2> 초기화
- top : stack에서 삽입과 삭제가 일어날 곳 / 0번째 element부터 시작 (지금 몇 번째 경로를 탐색하고 있는지)
- found : 출구를 발견했는지 여부 / 맨 처음에는 출구를 발견한 게 아니니 FALSE로 초기화 (1번째 loop를 깨는데 사용)
- 첫 번째 위치에 방문 : mark에 표시 (mark[1][1]의 값을 1로 변경)
- 이동 경로 저장 : stack의 0번째 element
- row와 col은 각각 1이다.
- dir은 미정이지만 임의로 1을 넣는다. (어짜피 북쪽 방향은 이동할 수 없다.)
3> 첫 번째 loop : top이 -1 초과 && found가 0이면 계속 돌아간다 (출구를 찾아서 found가 1이면 멈춘다.)
pop() -> position에 저장 -> 2번째 loop
- stack에서 top에 있는 원소를 가지고 2번째 loop을 진행한다.
- pop으로 top에 있는 원소를 return하고 잠시 stack에서 제거한다.
- position에 저장 => position에 있어야 2번째 loop을 진행한다.
4> 두 번째 loop : 8개 경로가 모두 열려 있으면 계속 돌아간다. (8개 경로 모두 막혀있으면 중단)
- 다음 위치를 만들고 다음 위치가 가질 수 있는 3가지 경우 중 어느 곳에 해당하는지 확인
- 1. 출구에 도착
- 2. 이동할 수 있는지 = 다음 위치가 비어있고 && 방문한 적이 없는지 (maze가 0이고 mark도 0인지)
- 3. 이동 불가능한 경우로 방향 변경 (dir을 증가시킨다.
- if : 출구에 도착했는지 확인하고 -> found를 TRUE로 변경 -> 첫 번째 loop 해제
- else if : 이동할 수 있는지 확인하고
- 방문했다고 기록 (mark로 기록)
- stack에 저장할 position을 기록한다. (현재 row와 col 기록) (dir은 backtracking을 고려해서 나중에 돌아왔을 때 다른 direction으로 갈 수 있게 1을 증가시켜둔다.)
- position을 이동 경로로 저장 (stack에 push) (다른 곳으로 이동하니 경로가 된 것이고 stack에 저장한다.)
- 다시 row, col, dir 초기화 한다. (row와 col은 nextRow와 nextCol로 초기화) (dir은 0으로 초기화)
- else : 이동이 불가능한 경우이니 dir을 증가시켜서 방향을 변경해본다.
5> found가 TRUE로 변경되고 2개의 loop가 해제되며 종료된다.
void path(void)
{
/* output a path through the maze if such a path exists */
int row, col, nextRow, nextCol, dir, found = FALSE;
element position;
mark[1][1] = 1; top = 0;
stack[0].row = 1; stack[0].col = 1; stack[0].dir = 1;
while (top > -1 && !found) {
position = pop();
row = position.row; col = position.col, dir = position.dir;
while (dir < 8 && !found) {
/* move in direction dir*/
nextRow = row + move[dir].vert;
nextCol = col + move[dir].horiz;
if (nextRow == EXIT_ROW && nextCol == EXIT_COL) {
found = TRUE;
}
else if (!maze[nextRow][nextCol] && !mark[nextRow][nextCol]) {
mark[nextRow][nextCol] = 1;
position.row = row; position.col = col;
position.dir = ++dir;
push(position);
row = nextRow; col = nextCol; dir = 0;
}
else ++dir;
}
}
if (found) {
printf("The path is : \n");
printf("row col \n");
for (int i = 0; i <= top; i++)
printf("%2d %5d", stack[i].row, stack[i].col);
printf("%2d %5d \n", row, col);
printf("%2d %5d \n", EXIT_ROW, EXIT_COL);
}
else printf("The maze does not have a path \n");
}
int main() {
path();
return 0;
}
6> 결론
미로의 크기는 함수 path의 연산 시간을 결정한다. 미로의 각 위치는 한 번만 방문되므로 최악의 경우 연산 시간은 O(mp)이다. 여기서 m, p는 각각 미로의 행과 열이다
<전체 코드>
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK_SIZE 100 /* maximum queue size */
#define EXIT_ROW 3 // maze 경계선 제외 행의 개수 = maze 출구의 행 index
#define EXIT_COL 3 // maze 경계선 제외 열의 개수 = maze 출구의 열 index
#define FALSE 0;
#define TRUE 1;
typedef struct {
short int row;
short int col;
short int dir;
}element;
element stack[MAX_STACK_SIZE];
typedef struct {
short int vert;
short int horiz;
} offsets;
offsets move[8] = { {-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1} };
/*array of moves for each direction*/
int maze[EXIT_ROW + 2][EXIT_COL + 2] = { {1,1,1,1,1}, {1,0,0,0,1}, {1,0,1,1,1}, {1,0,0,0,1}, {1,1,1,1,1} };
int mark[EXIT_ROW + 2][EXIT_COL + 2]; // 모두 0으로 초기화
int top;
element pop() {
return stack[top--];
}
void push(element cur) {
stack[++top] = cur;
}
void path(void)
{
/* output a path through the maze if such a path exists */
int row, col, nextRow, nextCol, dir, found = FALSE;
element position;
mark[1][1] = 1; top = 0;
stack[0].row = 1; stack[0].col = 1; stack[0].dir = 1;
while (top > -1 && !found) {
position = pop();
row = position.row; col = position.col, dir = position.dir;
while (dir < 8 && !found) {
/* move in direction dir*/
nextRow = row + move[dir].vert;
nextCol = col + move[dir].horiz;
if (nextRow == EXIT_ROW && nextCol == EXIT_COL) {
found = TRUE;
}
else if (!maze[nextRow][nextCol] && !mark[nextRow][nextCol]) {
mark[nextRow][nextCol] = 1;
position.row = row; position.col = col;
position.dir = ++dir;
push(position);
row = nextRow; col = nextCol; dir = 0;
}
else ++dir;
}
}
if (found) {
printf("The path is : \n");
printf("row col \n");
for (int i = 0; i <= top; i++)
printf("%2d %5d", stack[i].row, stack[i].col);
printf("%2d %5d \n", row, col);
printf("%2d %5d \n", EXIT_ROW, EXIT_COL);
}
else printf("The maze does not have a path \n");
}
int main() {
path();
return 0;
}
'자료구조' 카테고리의 다른 글
3-5강 - Multiple Stack and Queue (다중 스택, 다중 큐) (0) | 2020.04.28 |
---|---|
3-4강 - Evaluation of Expressions (수식의 계산) (0) | 2020.04.23 |
3-2강 - Queue, Circular Queue using Dynamic Allocated Arrays(큐, 동적 할당로 만든 원형 큐) (0) | 2020.04.22 |
3-1강 - Stack, Stack using Dynamic Arrays (스택, 동적 배열로 만든 스택) (1) | 2020.04.21 |
2-5강 - Multidimensional Arrays, Strings (다차원 배열, 문자열) (0) | 2020.04.15 |