루트 노드(혹은 다른 임의의 노드)에서 시작해서 다음 분기(branch)로 넘어가기 전에 해당 분기를 완벽하게 탐색하는 방법이다.
특징
자기 자신을 호출하는 순환 알고리즘의 형태를 지님
이 알고리즘을 구현할 때 가장 큰 차이점은 그래프 탐색의 경우 어떤 노드를 방문했었는지 여부를 반드시 검사해야한다는 것 (이를 검사하지 않을 경우 무한루프에 빠질 위험이 있음)
구현
fun dfs(index: Int) {
visited[index] = true
println(index)
// 구현
for (next in graphs[index]) {
if (!visited[next]) {
dfs(next)
}
}
}
장단점
루트 노드(혹은 다른 임의의 노드)에서 시작해서 인접한 노드를 먼저 탐색하는 방법이다.
특징
BFS 는 재귀적으로 동작하지 않는다.
이 알고리즘을 구현할 때 가장 큰 차이점은 그래프 탐색의 경우 어떤 노드를 방문했었는지 여부를 반드시 검사해야한다는 것 (이를 검사하지 않을 경우 무한루프에 빠질 위험이 있음)
BFS 는 방문한 노드들을 차례로 저장한 후 꺼낼 수 있는 자료 구조인 큐(Queue)를 사용함
구현
val queue = LinkedList<Int>()
fun bfs(start: Int) {
queue.add(start)
visited[start] = true
while (queue.isNotEmpty()) {
val head = queue.poll() // 첫 원소 반환 후 remove
println(head)
for (next in graphs[head]) {
if (!visited[next]) {
visited[next] = true
queue.add(next)
}
}
}
}
장단점
DFS의 시간복잡도
인접 리스트로 표현된 그래프 : O(N+E)
인접 행렬로 표현된 그래프: O(N^2)
👉 적은 수의 간선을 사용할 때는 인접 리스트가 더 유리하다.
BFS의 시간복잡도
인접 리스트로 표현된 그래프 : O(N+E)
인접 행렬로 표현된 그래프: O(N^2)
👉 DFS와 마찬가지