-
그래프(DFS, BFS) 문제알고리즘 2025. 3. 11. 22:31
Clone Grpah
dfs를 이용해서 deepCopy를 수행하는 문제다.
/** * Definition for a Node. * class Node(var `val`: Int) { * var neighbors: ArrayList<Node?> = ArrayList<Node?>() * } */ class Solution { fun cloneGraph(node: Node?): Node? { if(node == null) { return null } val map = mutableMapOf<Node, Node>() return dfs(node, map) } private fun dfs(node: Node, map: MutableMap<Node, Node>): Node? { if(map.containsKey(node)) { return map[node] } val copy = Node(node.`val`) map.put(node, copy) for (n in node.neighbors) { n?.let{copy.neighbors.add(dfs(it, map))} } return copy } }
Course Schedule
사전에 방문해야 하는 노드가 정해져 있는 경우 방향이 있는 연결 리스트를 이용해서 사이클 발생 여부를 조회하는 문제다.
DFS로 풀면 다음과 같이 풀 수 있다.
class Solution { fun canFinish(numCourses: Int, prerequisites: Array<IntArray>): Boolean { // 1. DFS 활용 방안 val graph = HashMap<Int, MutableList<Int>>() val visited = IntArray(numCourses) {0} for((a, b) in prerequisites) { // b에서 a를 갈 수 있음 (b를 들어야 a를 갈 수 있음) graph.computeIfAbsent(b) {mutableListOf()}.add(a) } fun dfs(course: Int): Boolean { if(visited[course] == 1) return false // 탐색 중인 노드 (해당 노드를 마주치게 되면 사이클 발생) if(visited[course] == 2) return true // 이미 탐색 완료 됨 (이미 탐색 완료된 노드는 안전한 노드) visited[course] = 1 // 방문 시작 표시 for(next in graph[course] ?: emptyList()){ // 사이클 발생시 예외 전달 if(dfs(next).not()) { return false } } visited[course] = 2 // 방문 완료 표시 return true } for(course in 0..numCourses-1) { if(visited[course] == 0 && dfs(course).not()) { return false } } return true } }
Kahn's algorithm을 이용해서 BFS를 사용하면 다음과 같이 쓸 수 있다.
class Solution { fun canFinish(numCourses: Int, prerequisites: Array<IntArray>): Boolean { // 2. BFS 활용 방안 (Kahn's algorithm) val graph = HashMap<Int, MutableList<Int>>() val indegree = IntArray(numCourses) {0} for((a, b) in prerequisites) { // b에서 a를 갈 수 있음 (b를 들어야 a를 갈 수 있음) graph.computeIfAbsent(b) {mutableListOf()}.add(a) // a에서 사전에 들어야 하는 갯수 증가 indegree[a] ++ } val queue: Queue<Int> = LinkedList() for(i in 0 until numCourses) { // 사전에 들어야 하는 진입 차수가 0인 노드를 큐에 추가 if(indegree[i] == 0) queue.add(i) } var count = 0 while (queue.isNotEmpty()) { val course = queue.poll() count ++ // 코스 완료 표시 for (next in graph[course] ?: emptyList()) { indegree[next]-- // 사전에 들어야 하는 갯수를 제거 시킴 (코스 완료) if(indegree[next]==0) { queue.add(next) // 선행 과목 모두 방문된 노드를 큐에 넣음 } } } return count == numCourses } }
DFS를 이용한 방안과 BFS를 이용한 방안 모두 시간 복잡도 모두 O(V + E) (V : 노드 수, E: 간선 수) 이다.
Number of Islands
좌우상하 네방향으로 돌아다니며 섬의 갯수를 찾는 기본적인 탐색의 문제다.
먼저 DFS를 이용하여 문제를 풀었다.
시간 복잡도 = O(MxN)
공간 복잡도 = O(MxN)
여기서 dx, dy를 사용하지 않고 네방향으로 dfs()를 실행하게 하면 참조 연산이 줄어들 수 있다.
class Solution { val dx = listOf(0, 0, 1, -1) val dy = listOf(1, -1, 0, 0) fun numIslands(grid: Array<CharArray>): Int { val m = grid.size val n = grid[0].size var answer = 0 fun dfs(x: Int, y: Int) { for (i in 0..3) { val newX = x + dx[i] val newY = y + dy[i] if(0<=newX && newX<m && 0<= newY && newY<n && grid[newX][newY] == '1') { grid[newX][newY] = '0' dfs(newX, newY) } } } for(i in 0..m-1) { for (j in 0..n-1) { if(grid[i][j] == '1') { answer++ grid[i][j] = '0' dfs(i,j) } } } return answer } }
두번째로 BFS를 이용해서도 풀 수 있다.
시간복잡도 : O(MxN)
공간복잡도 : O(min(M, N))
val dx = listOf(0, 0, 1, -1) val dy = listOf(1, -1, 0, 0) fun numIslands(grid: Array<CharArray>): Int { val m = grid.size val n = grid[0].size var answer = 0 fun bfs(x: Int, y: Int) { val queue: Queue<Pair<Int, Int>> = LinkedList() queue.add(x to y) grid[x][y] = '0' while(queue.isNotEmpty()) { val (x, y) = queue.poll() for (i in 0..3) { val newX = x + dx[i] val newY = y + dy[i] if(0<=newX && newX<m && 0<= newY && newY<n && grid[newX][newY] == '1') { grid[newX][newY] = '0' queue.add(newX to newY) } } } } for(i in 0..m-1) { for (j in 0..n-1) { if(grid[i][j] == '1') { answer++ bfs(i,j) } } } return answer }
UnionFind(Disjoint Set)을 이용해서도 풀 수 있다.
시간복잡도 : O(MxNXα(MxN))
공간복잡도: O(MxN)
=> 이건 다음 시간에...
Longest Consecutive Sequence
이건 그래프라기 보다, 값 비교로 풀었다.
class Solution { fun longestConsecutive(nums: IntArray): Int { if(nums.size == 0) { return 0 } val sortedNums = nums.sorted() var temp = sortedNums[0] var answer = 1 var length = 1 for (i in 1..nums.size-1) { if(sortedNums[i] == temp) { continue }else if(temp+1 == sortedNums[i]) { length += 1 answer = max(answer, length) }else { length = 1 } temp = sortedNums[i] } return answer } }
Alien Dictionary 유사 문제
NeetCode
neetcode.io
BFS 위상 정렬 (Kahn's Algorithm)을 이용해서 우선순위를 이용한 그래프 탐색을 이용할 수 있다.
시간복잡도 : O(MxN)
공간복잡도 : O(V+E)
fun alienOrder(words: Array<String>): String { val graph = HashMap<Char, MutableSet<Char>> // 문자 간의 관계 저장 val indegree = HashMap<Char, Int>() // 각 문자에 대한 진입 차수 // 모든 문자 노드를 그래프에 추가 for (word in words) { for ( c in word) { graph.putIfAbsent(c, mutableSetOf()) indegree.putIfAbsent(c, 0) } } // 인접 리스트(그래프) 만들기 for ( i in 0 until words.size-1) { val first = words[i] val second = words[i+1] val minLength = min(first.length, second.length) if (first.length > second.length && first.startsWith(second)) { return "" // 잘못된 사전 순서가 있는 경우 } for (j in 0 until minLength) { val c1 = first[j] val c2 = second[j] if(c1 != c2) { // 처음으로 다른 문자 등장 if(!graph[c1]!!.contains(c2)) { graph[c1]!!.add(c2) indegree[c2] = indegree[c2]!! + 1 // 진입 차수 증가 } break // 순위가 정해졌기 떄문에 그 뒤는 볼 필요가 없음 } } } // BFS를 위한 Queue (진입 차수가 0인 문자부터 처리) val queue :Queue<Char> = LinkedList() for((c, degree) in indegree) { if(degree == 0 ) { queue.add(c) } } val order = StringBuilder() while(queue.isNotEmpty()) { val char = queue.poll() order.append(char) for(next in graph[char]!!) { indgree[next] = indegree[next]!! -1 if(indegree[next] == 0) { queue.add(next) } } } // 모든 문자를 방문 가능하면 응답 아니라면 불가 응답 return if (order.length == graph.size) order.toString() else "" }
Graph Valid Tree 유사 문제
NeetCode
neetcode.io
Union-Find 방법으로 찾으면 다음과 같다.
시간복잡도 : O(N*α(N))
공간복잡도 : O(N)
class Solution { fun validTree(n: Int, edges: Array<IntArray>): Boolean { if (edges.size != n - 1) return false // ❗ 간선 수 조건 불충족 val parent = IntArray(n) { it } // 각 노드의 부모 초기화 // Find with path compression fun find(x: Int): Int { if (parent[x] != x) { parent[x] = find(parent[x]) } return parent[x] } // Union - returns false if cycle detected fun union(x: Int, y: Int): Boolean { val rootX = find(x) val rootY = find(y) if (rootX == rootY) return false // 🔥 사이클 발생 parent[rootY] = rootX return true } for ((a, b) in edges) { if (!union(a, b)) return false // 사이클이 있다면 false } return true // 연결 + 무사이클이면 트리 } }
DFS로 찾으면 다음과 같다.
시간복잡도 : O(N)
공간복잡도 : O(N)
class Solution { fun validTree(n: Int, edges: Array<IntArray>): Boolean { if (edges.size != n - 1) return false // 트리 조건 위반 val graph = HashMap<Int, MutableList<Int>>() for (i in 0 until n) { graph[i] = mutableListOf() } for ((a, b) in edges) { graph[a]!!.add(b) graph[b]!!.add(a) } val visited = BooleanArray(n) fun dfs(node: Int, parent: Int): Boolean { if (visited[node]) return false // 🔥 사이클 발생 visited[node] = true for (neighbor in graph[node]!!) { if (neighbor == parent) continue // 부모는 무시 if (!dfs(neighbor, node)) return false } return true } if (!dfs(0, -1)) return false // DFS 중 사이클 발견 // 모든 노드가 방문되었는지 확인 (연결 여부 확인) return visited.all { it } } }
BFS로도 풀 수 있다.
시간복잡도 : O(N+E)
공간복잡도 : O(N+E)
import java.util.* class Solution { fun validTree(n: Int, edges: Array<IntArray>): Boolean { if (edges.size != n - 1) return false // 트리 조건 위반 // 인접 리스트로 그래프 구성 val graph = HashMap<Int, MutableList<Int>>() for (i in 0 until n) { graph[i] = mutableListOf() } for ((a, b) in edges) { graph[a]!!.add(b) graph[b]!!.add(a) } val visited = BooleanArray(n) val queue: Queue<Pair<Int, Int>> = LinkedList() // Pair(currentNode, parentNode) queue.add(0 to -1) // 시작 노드 0, 부모는 -1 while (queue.isNotEmpty()) { val (node, parent) = queue.poll() if (visited[node]) return false // 🔥 사이클 발생 visited[node] = true for (neighbor in graph[node]!!) { if (neighbor == parent) continue // 부모 노드는 무시 if (visited[neighbor]) return false // 🔥 사이클 발생 queue.add(neighbor to node) } } // 모든 노드가 방문되었는지 확인 (연결성) return visited.all { it } } }
Number of Connected Components in an Undirected Graph 유사 문제
NeetCode
neetcode.io
상단의 문제와 비슷한 문제로 DFS로 풀었다 .
class Solution { fun countComponents(n: Int, edges: Array<IntArray>): Int { val graph = Array(n) { mutableListOf<Int>() } for ((a, b) in edges) { graph[a].add(b) graph[b].add(a) } val visited = BooleanArray(n) var answer = 0 fun dfs(node: Int, parent: Int) { if (visited[node]) return visited[node] = true for (neighbor in graph[node]) { if (neighbor == parent) continue dfs(neighbor, node) } } for (i in 0 until n) { if (!visited[i]) { answer++ dfs(i, -1) } } return answer } }
'알고리즘' 카테고리의 다른 글
백준 14891) 톱니바퀴 (골드.5) (0) 2022.08.02 백준 14503) 로봇청소기 (골드.5) (0) 2022.08.02 백준 16234) 인구이동 (골드.5) (0) 2022.08.02 백준 15686) 치킨배달 (골드.5) (0) 2022.08.02 백준 17140) 이차원 배열과 연산 (골드.4) (0) 2022.08.02