코딩테스트에 통과하려면 무조건 풀어야 하는 문제유형은 DFS,BFS인것 같다😂😂😂
그래서 참 많은 유튜브와 블로그의 글들을 보고 개념은 이해했지만,
문제에 적용해서 코딩하는거까지는 쉽지않은과정이라고 생각한다.
(개인적으로 해당 유튜브가 개념을 이해하는데 가장 쉬움 -> 유튜브 링크)
그래서 일단 차근차근 개념부터 제대로 이해해보자!
우선 DFS와 BFS를 이해하기전에 간단하게 그래프를 알아야한다.
정점과 간선으로 이루어진 자료구조
하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한번씩 방문하는 것이다.
곧 DFS와 BFS는 그래프탐색의 일종이다.
- DFS : 이동한 정점의 값을 가지고 계속 연산을 하는 경우(재귀적으로 호출되는경우)
- BFS : 최단거리 문제
ex1) 지구 상에 존재하는 모든 친구 관계를 그래프로 표현한 후 Ash 와 Vanessa 사이에 존재하는 경로를 찾는 경우
💡 이제 개념을 알았으니 코딩으로 DFS와 BFS를 이해해보자!
문제예제는 기본적인 코딩을 공부하기위해 백준 126번 문제 DFS와 BFS로 선택했다.
DFS, BFS탐색순으로 결과를 출력하면 된다.
// 입력1
4 5 1
1 2
1 3
1 4
2 4
3 4
// 출력1
3 1 2 5 4 (DFS)
3 1 4 2 5 (BFS)
map[][]==1
인 경우이면서, 아직 방문하지 않은 경우(visited[]==false
)for
안에서 끝까지 탐색한다.DFS의 경우 , 인접행렬, 인접리스트 , 스택 총 3가지 방법을 이용해 구현해보았다.
1. 인접행렬 : 정점의 개수가 적은 경우에만 사용하는 것을 권장한다. 왜냐하면, 정점n개에 대해 행렬의 크기가 n*n만큼 차지하기 때문에 n의 수가 시간복잡도 O(n^2)가 된다.
2. 인접리스트 : 모든 정점을 탐색하는 최악의 경우를 제외한 나머지 경우 인접행렬보다는 빠른 시간복잡도인 O(n+E)이다. (E는 간선의 개수)
3. 스택 : 1,2번과 달리 재귀호출을 하지 않는 방식으로 구현가능하다.
BFS는 큐를 이용하여 구현해보았다.
static int map[][]; // 정점과 간선을 표현하는 행렬
// 입력1로 예를 들면 map[1][2] == 1
// 사이즈는 n+1로 지정
static ArrayList<TreeMap<Integer, Integer>> arrayList; // 인접리스트 표현
static LinkedList<Integer>[] adjList; // 인접리스트 표현(LinkedList([]))
static boolean[] visited;
static int n,m,v; // 정점의 개수, 간선의 개수, 시작 정점
static String answer = "";
// 인접행렬로 구현한 DFS (정점의 개수가 작은 경우에만 사용하는걸 권장)
public static void dfs_adjacency_matrix(int v) {
visited[v] = true;
answer += v+" ";
System.out.println(answer);
for(int i=1;i<m;i++) {
if(map[v][i] == 1 && !visited[i]) {
dfs_adjacency_matrix(i);
}
}
}
// 인접리스트로 구현한 DFS
public static void dfs_adjacency_list(int v) {
visited[v] = true;
answer += v+" ";
TreeMap<Integer, Integer> tmap = arrayList.get(v);
for(int i : tmap.keySet()) {
if(!visited[i]) {
dfs_adjacency_list(i);
}
}
}
// 스택으로 구현한 DFS
public static void dfs_stack(int v) {
Stack<Integer> stack = new Stack<Integer>();
stack.push(v);
while(!stack.isEmpty()) {
int vv= stack.pop();
visited[vv] = true;
answer += vv+" ";
for(int i=1; i<n+1;i++) {
if(map[vv][i] == 1 && !visited[i]) {
stack.push(i); // stack에 담고 빠져나온다.
break;
}
}
}
}
// 큐로 구현한 BFS (인접행렬)
public static void bfs_queue_adjacency_matrix(int v) {
Queue<Integer> q = new LinkedList<Integer>();
q.offer(v);
visited[v] = true;
while(!q.isEmpty()) {
int vv = q.poll();
answer += vv+" ";
for(int i=1; i<n+1 ; i++) {
if(map[vv][i] == 1 && !visited[i]) {
q.offer(i); // queu에 담고 계속 진행 map[vv][i~n] 끝까지 탐색
visited[i] = true;
}
}
}
}
// 큐로 구현하는 BFS (인접리스트)
public static void bfs_queue_adjacency_list(int v){
Queue<Integer> q = new LinkedList<Integer>();
visited[v] = true;
q.add(v);
while(!q.isEmpty()){
v = q.poll();
answer += v +" ";
Iterator<Integer> it = adjList[v].listIterator();
while(it.hasNext()){
int w = it.next();
if(!visited[w]){
visited[w]=true;
q.add(w);
}
}
}
}
for
문을 돌면서 탐색해야하는 정점이면서 방문하지 않은 경우,
break으로 빠져나오면 DFS, 그렇지 않고 계속해서 정점을 탐색한다면 BFS
[참조블로그]
그래프 이미지
DFS vs BFS gif
깊이 우선 탐색(DFS) 과 너비 우선 탐색(BFS)
DFS와 BFS - JAVA 정리 및 해설
BFS 너비 우선 탐색 - 인접리스트 / 인접행렬로 구현
DFS가 빠르냐, BFS 빠르냐는 일반적으로 정의할 수 없습니다.
풀고자 하는 문제의 목표가 무엇이냐에 따라 속도면에서 유리한 것이 달라요.
Metrix가 주어지는 문제를 예시로 들어볼까요?
주어진 위치 (m, n)에서 가장자리로 이동하기 위한 경로 찾기 (최단 거리일 필요 없음)
-> DFS가 빠릅니다. 가장자리는 반드시 도달하기 때문에 굳이 다른 방향을 탐색할 필요가 없죠.
주어진 위치 (m, n) 에서 주변에 폭탄이 단 하나라도 있는지 확인 (폭탄은 바로 옆에 있음이 보장)
-> BFS가 빠릅니다. DFS를 사용하면 처음 출발 방향을 잘못 선택하면 가장자리 끝까지 탐색하다가 다시 돌아와야합니다. 비효율적이죠. 이런 경우는 BFS가 빠릅니다.
그냥 모든 위치를 방문해야하는 경우
-> 이미 방문한 위치는 다시 방문하지 않도록 한다면 DFS, BFS는 속도차가 나지 않습니다.
속도는 DFS가 더 빠릅니다. 비교가 잘못된 것 같습니다.
일례로: https://www.acmicpc.net/problem/17070 문제의 경우 python에서 dfs로 풀면 성공, bfs로 풀면 시간초과입니다.