I. DFS(Depth First Search)란?
깊이 우선 탐색(DFS)은 연결된 Branch를 하나 정하여 막힐 때까지 탐색하는 알고리즘이다.
미로를 탐색할 때 갈림길에서 한 방향으로 먼저 쭉 가보는 방법이라고 생각하자.
BFS와 마찬가지로 DFS도 탐색이기 때문에 모든 점을 탐색하는 방법이다.
-> 공간 복잡도 O(N)
-> 시간 복잡도 O(N+B) (N : node, B : branch)
주로 Flood Fill, 조합 문제 등 모든 경우의 수를 다 확인해야 하는 문제로 출제된다.
응용이 많이되므로 연습이 필요하다. BFS보단 어렵다고 개인적으로 생각함!
II. 기본원리
한 노드와 인접한 점을 모두 확인하며 확산하는 BFS와는 달리,
동일한 패턴 및 조건을 가지고 연결된 노드를 끝까지 파고 들어간다.
따라서 "재귀함수" or "While()"을 이용한다. 나는 재귀 함수가 좀 편했다!
Ex) Flood Fill로 10x10 배열을 모두 0->1로 채운다고 할 때,
1. (0,0)을 기준으로 하여 4 방향을 탐색 (탐색 순서가 동남서북이라면)
2. (0,1)로 이동해 4방향 탐색 -> (0,2) 가서 4방향 탐색... 반복
3. 맨 끝인 (0,9)에서 동쪽은 막혔으니 남쪽(1,9) 이동
4. (2,9), (3,9).... 이동 , 이하 반복
!! 주의 - 방문했던 Node는 재방문 금지.
III. 구현 코드 - Flood Fill
!! 주의 - DFS를 재귀 함수로 구현할 때는 return 하면 한 단계 전으로 간다.
깊게 search 하면 할수록 빠져나오는데 시간도 더 오래 걸리니 이를 인지하고 구현해야 한다.
1. 모든 배열을 돌며 1의 개수를 찾는 코드 (2중 for 해도 되는데 예시로 ^^)
10x10의 배열을 DFS로 Search
<hide/>
/*
01100
00110
00111
11111
10000
답 : 13개
*/
#include <stdio.h>
#include <iostream>
#include <cstring>
using namespace std;
int map[5][5] = { {0,1,1,0,0},{0,0,1,1,0},{0,0,1,1,1},{1,1,1,1,1},{1,0,0,0,0} };
int cnt{ 0 };
/*
탈출조건으로 return 해도 좋고,
for문의 nexty, nextx를 탐색해서 continue;해도 괜찮다.
하지만 탈출조건 하면 1단계 더 깊이 들어가는 것 생각하자!
*/
void DFS(int y, int x) {
if (y == -1 or x == -1 or
y == 5 or x == 5) return; // 이것보단 for문 안에 continue가 좋음.
map[y][x] = 0; //방문처리
cnt++;
int dy[4] = { 0,0,-1,1 };
int dx[4] = { 1,-1,0,0 }; // 4방향 search
for (int d = 0; d < 4; d++) {
int nexty = y + dy[d];
int nextx = x + dx[d];
if (map[nexty][nextx]) DFS(nexty, nextx); // 다음이 1이면 확산
}
return;
}
int main() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (map[i][j]) DFS(i, j); // 1일 때만 DFS시작하여 확산
}
}
cout << cnt << endl; // 13
return 0;
}
2. 0과 1로 이루어진 배열에서 1의 그룹이 몇 개인지 찾는 코드
7x7 배열에서 1을 확인하면 그 위치부터 DFS시작
<hide/>
#include <stdio.h>
#include <iostream>
using namespace std;
int map[7][7] = { {0,1,1,0,1,0,0},{0,1,1,0,1,0,1},{1,1,1,0,1,0,1},{0,0,0,0,1,1,1},
{0,1,0,0,0,0,0},{0,1,1,1,1,1,0},{0,1,1,1,0,0,0} };
int groupcnt{ 0 }; // 1이 있는 그룹의 개수
int maxsum{ 0 }; // 그룹 내 1의 개수 중 max 값
int innersum{ 0 }; // 그룹 내 1의 개수
void DFS(int y, int x) {
map[y][x] = 0; //방문처리
innersum++;
int dy[4] = { 0,0,-1,1 };
int dx[4] = { 1,-1,0,0 }; // 4방향 search
for (int d = 0; d < 4; d++) {
int nexty = y + dy[d];
int nextx = x + dx[d];
if (nexty == -1 or nextx == -1 or
nexty == 7 or nextx == 7) continue; // 밖으로 나가면 무시
if (map[nexty][nextx]) DFS(nexty, nextx); // 다음이 1이면 확산
}
return;
}
int main() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (map[i][j]) {
innersum = 0;
DFS(i, j); // 1일 때만 DFS시작하여 확산
groupcnt++; // 그룹 1개 추가
if (maxsum < innersum) maxsum = innersum; //그룹 내 1의 수 최대값 업데이트
}
}
}
printf("그룹 수:%d, 최대 그룹:%d\n", groupcnt, maxsum); //3,9
return 0;
}
3. 가능한 합의 숫자 (조합)
5개의 수를 임의로 조합하여 N이라는 수를 만들 수 있을까?
<hide/>
#include <stdio.h>
#include <iostream>
using namespace std;
int numarr[5] = { 1,7,9,17,26 };
int target[3] = { 18,33,49 }; // Y,Y,N
bool istrue{ 0 }; // DFS도중에 찾았는가?
// 전역변수로 flag 두고 True / False 표시해도 된다.
void DFS(int idx, int sum, int targetnum) {
// DFS이므로 모든 경우를 탐색하게 된다.
// 따라서 istrue도 조건문에 넣어주어 가지치기해서 시간 절약
if (sum == targetnum or istrue==1) {
istrue = 1;
return;
}
if (idx == 5) return; // 인덱스 넘어가면 반환
DFS(idx + 1, sum + numarr[idx], targetnum); // 해당 idx의 숫자를 포함하여 더함
DFS(idx + 1, sum, targetnum); // 해당 idx의 숫자를 포함하지 않음
return;
}
int main() {
for (int i = 0; i < 3; i++) {
istrue = 0;
DFS(0, 0, target[i]);
if (istrue) cout << "True" << endl;
else cout << "False" << endl;
}
return 0;
}
4. 기타 응용
다양한 부분으로 응용이 많이 되어서... DFS문제다! 하고 한방에 알 수 있는 것은 Flood Fill정도이다.
다만, DFS는 한 단계씩 진행하며 모든 경우를 확인해야 하므로
다양한 경우 모두 파악해야 할 때, DFS방법으로 접근해보자.
중요한 것은 가지치기로 실행 시간을 많이 줄이도록 해야한다.
나는 DFS 응용문제가 정말 어려웠다. 문제를 많이 풀자ㅠㅠ
농장탈출/ 건물세우기/ 책꽂이/ 월드컵/ 스도쿠/ 벽장문의이동/ 매직스타/ 페그솔리테어 등등
'Algorithm > DFS' 카테고리의 다른 글
[Algorithm][DFS][정올] 1840 : 치즈 (0) | 2020.12.15 |
---|---|
[Algorithm][DFS][정올] 1457 : 영역 구하기 (0) | 2020.12.15 |
[Algorithm][DFS][정올] 2765 : 미술관람 대회 (0) | 2020.12.15 |
[Algorithm][DFS][USACO] 2013Feb. 둘레 (0) | 2020.12.15 |