Disjoint Set
과 DFS
입니다.key point
는 union
연산에 있습니다. union
연산을 통해 그래프에서의 간선으로 표현될 수 있기 때문입니다. union
연산을 수행합니다.cycle
이 발생한 것 입니다.import Foundation
// Disjoint Set을 활용한 무방향 그래프 사이클 찾기
struct DisjointSet {
var parent: [Int]
var rank: [Int]
init(count: Int) {
parent = Array(0..<count)
rank = Array(repeating: 0, count: count)
}
mutating func find(_ x: Int) -> Int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
mutating func union(_ x: Int, _ y: Int) {
let xRoot = find(x)
let yRoot = find(y)
if xRoot != yRoot {
if rank[xRoot] < rank[yRoot] {
parent[xRoot] = yRoot
} else if rank[xRoot] > rank[yRoot] {
parent[yRoot] = xRoot
} else {
parent[yRoot] = xRoot
rank[xRoot] += 1
}
}
}
}
func hasCycle(edges: [(Int, Int)], nodeCount: Int) -> Bool {
var disjointSet = DisjointSet(count: nodeCount)
for edge in edges {
let xRoot = disjointSet.find(edge.0)
let yRoot = disjointSet.find(edge.1)
if xRoot == yRoot {
return true
}
disjointSet.union(edge.0, edge.1)
}
return false
}
let test = hasCycle(edges: [(0,1), (0,2), (1,2)], nodeCount: 3)
true
key point
는 부모 노드를 다시 방문하는 것은 사이클로 간주하지 않는다는 것 입니다. 즉, 현재 노드의 인접 노드를 방문할 때, 해당 인접 노드가 이전에 방문한 노드이면서 현재 노드의 부모 노드가 아니라면 사이클이 있는 것으로 판단합니다. 부모 노드
일 경우는 예외 입니다. func isCycle(
_ graph: [[Int]],
_ v: Int,
_ visited: inout [Bool],
_ parent: Int
) -> Bool {
// visited 처리
visited[v] = true
// 2차원 배열 요소 접근
for i in graph[v] {
// 해당 요소에 대해서 접근이 안 되어 있을 때 발동
if !visited[i] {
// 해당 함수 재귀처리하면서 부모 노드로 현재 노드를 부여
if isCycle(graph, i, &visited, v) {
return true
}
// 방문했지만, i가 parent일때는 무시
} else if i != parent {
return true
}
}
return false
}
func checkCycle(_ graph: [[Int]], _ n: Int) -> Bool {
var visited = [Bool](repeating: false, count: n)
for v in 0..<n {
if !visited[v] {
// 사이클이 있는지 확인,
// 최초값을 최소값의 -1 값을 부여
if isCycle(graph, v, &visited, -1) {
return true
}
}
}
return false
}
print(checkCycle([[0,1,1], [1,0,1], [1,1,0]], 3))
Visited
를 표시합니다.Recursive Stack
를 사용해서 표시합니다. recStack
에 있는 노드)를 다시 만나면 사이클이 존재한다고 판단합니다. 즉, recStack
배열에 이미 표시된 노드를 방문하면 사이클이 존재하는 것 입니다. recStack
에서 제거합니다. recStack
은 현재 DFS 탐색 경로에 있는 노드들을 추적합니다. DFS가 특정 노드를 방문할 때, 해당 노드는 recStack
에 추가됩니다. recStack
에 있는 노드를 다시 방문하면 사이클이 있는 것으로 간주합니다. visited
배열에 기록되고 recStack
에 추가됩니다. visited
되었고, recStack
에도 존재한다면, 사이클이라고 판단합니다. recStack
에서 제거됩니다. Visited
만으로 circle
을 판단했을 때recStack
을 활용하여 circle
을 판단했을 때// 인접 리스트 방식
func isCycle(
_ graph: inout [[Int]],
_ v: Int,
_ visited: inout [Bool],
_ recStack: inout [Bool]
) -> Bool {
if !visited[v] {
visited[v] = true
recStack[v] = true
for neighbor in graph[v] {
if !visited[neighbor] && isCycle(&graph, neighbor, &visited, &recStack) {
return true
} else if recStack[neighbor] {
return true
}
}
}
recStack[v] = false
return false
}
func dfs(_ graph: inout [[Int]], _ n: Int) -> Bool {
var visited = [Bool](repeating: false, count: n)
var recStack = [Bool](repeating: false, count: n)
for v in 0..<n {
if isCycle(&graph, v, &visited, &recStack) {
return true
}
}
return false
}
var graph = [[1,2],[],[3],[1]]
let n = 4 //
let hasCycle = dfs(&graph, n)
print(hasCycle ? "YES" : "NO")
NO
C에 속한 임의의 선분의 한 끝점에서 출발하여 모든 선분을 한 번씩만 지나서 출발점으로 되돌아올 수 있다.
6 5
0 1
1 2
2 3
5 4
0 4
0
6 5
0 1
1 2
1 3
0 3
4 5
4
import sys
input = sys.stdin.readline
class DisjointSet:
def __init__(self, count):
self.parent = list(range(count))
self.rank = [0] * count
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
x_root = self.find(x)
y_root = self.find(y)
if x_root != y_root:
if self.rank[x_root] < self.rank[y_root]:
self.parent[x_root] = y_root
elif self.rank[x_root] > self.rank[y_root]:
self.parent[y_root] = x_root
else:
self.parent[y_root] = x_root
self.rank[x_root] += 1
def has_cycle(edge, disjoint_set):
x_root = disjoint_set.find(edge[0])
y_root = disjoint_set.find(edge[1])
if x_root == y_root:
return true
disjoint_set.union(edge[0], edge[1])
return false
def start(N,M):
disjoint_set = DisjointSet(N)
result = 0
for i in range(M):
a, b = map(int, input().split())
result = i + 1
if has_cycle((a,b), disjoint_set):
return result
return 0
N, M = map(int, input().split())
print(start(N, M))
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
3 | 1 | 3 | 7 | 3 | 4 | 6 |
2
7
3 1 3 7 3 4 6
8
1 2 3 4 5 6 7 8
3
0
visited
를 확인하기 위한 배열 뿐만 아니라 사이클 확인 전용 배열(recStack
)도 만들어줘야 한다.if !visited[n]
를 하고 else if !recStack[n]
분기에서 방문을 안했을 때 count
를 진행해주고, dfs
가 종료되는 시점에서 recStack
을 true처리 해주면된다. key point
는 visited
는 dfs
를 시작할때 방문처리, recStack
은 모든 조건문 (내부 재귀 함수)가 종료될 때 방문처리를 해주면 된다.import Foundation
let testCase = Int(readLine()!)!
var results = [Int]()
for _ in 0..<testCase {
let r = Int(readLine()!)!
let choices = [0] + readLine()!.split(separator: " ").map { Int($0)! }
var visited = Array(repeating: false, count: r + 1)
var recSatck = Array(repeating: false, count: r + 1)
var count = 0
func dfs(start: Int) {
visited[start] = true
var n = choices[start]
if !visited[n] {
dfs(start: n)
// 방문했고 해당 사이클이 아직 종료되지 않았다면 해당 조건 활성화
} else if !recSatck[n] {
// 해당 사이클에서 시작점이 맞을때까지 시작점으로 이동하는 반복문
while n != start {
count += 1
n = choices[n]
}
// 해당 사이클이 시작점과 같다면 count만 하고 해당 사이클 종료
count += 1
}
// 해당 노드에서 dfs가 종료될 때 recStack에 사이클 종료를 알림을 알린다.
recSatck[start] = true
}
for i in 1...r {
if !visited[i] {
dfs(start: i)
}
}
results.append(r - count)
}
results.forEach {
print($0)
}
visted
는 true이니 해당 분기는 넘어가며, 아직 사이클이 종료되지 않아서 else if문이 실행된다. 여기서 n == start이므로 count가 증가된다.recStack[start]
이 true처리되고 해당 사이클이 종료된다. recStack[6]
= true가 된다.recStack[7], [4]
= true가 되며 한 사이클이 종료된다. 3 4
DLLL
DRLU
RRRU
2
import Foundation
struct DisjointSet {
var parent: [Int]
var rank: [Int]
init(count: Int) {
parent = Array(0..<count)
rank = Array(repeating: 0, count: count)
}
mutating func find(_ x: Int) -> Int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
mutating func union(_ x: Int, _ y: Int) {
let xRoot = find(x)
let yRoot = find(y)
if xRoot != yRoot {
if rank[xRoot] < rank[yRoot] {
parent[xRoot] = yRoot
} else if rank[xRoot] > rank[yRoot] {
parent[yRoot] = xRoot
} else {
parent[yRoot] = xRoot
rank[xRoot] += 1
}
}
}
}
let testCase = readLine()!.components(separatedBy: " ").map { Int($0)! }
let N = testCase[0]
let M = testCase[1]
var graph = [[String]]()
for _ in 0..<N {
graph.append(readLine()!.map { String($0) })
}
var disjointSet = DisjointSet(count: N * M)
let dx = [-1, 1, 0, 0]
let dy = [0, 0, -1, 1]
let directions: [String: Int] = ["U": 0, "D": 1, "L": 2, "R": 3]
for i in 0..<N {
for j in 0..<M {
let direction = directions[graph[i][j]]!
let nx = i + dx[direction]
let ny = j + dy[direction]
if nx >= 0 && nx < N && ny >= 0 && ny < M {
// 해당 위치에서 주어진 값에 따라 이동 후 해당 값과 이동 후 값에 대해서 union을 해준다.
// 2차원 배열을 1차원으로 플릿화
let current = i * M + j
let next = nx * M + ny
disjointSet.union(current, next)
}
}
}
var safeZones = Set<Int>()
for i in 0..<N {
for j in 0..<M {
// 2차원 배열을 1차원으로 플릿 후
let index = i * M + j
// find 메서드를 활용해서 부모 노드를 safeZone에 넣어준다.
safeZones.insert(disjointSet.find(index))
}
}
print(safeZones.count)
import Foundation
let testCase = readLine()!.components(separatedBy: " ").map { Int($0)! }
let N = testCase[0]
let M = testCase[1]
var graph = [[String]](repeating: [String](), count: N)
// Visted을 3단계로 나눠서 해결
// 0 -> 방문 x
// 1 -> 방문 중
// 2 -> 방문 끝
// 즉, 방문 중인걸 다시 방문할 경우 circle이 생김
var visited = [[Int]](repeating: [Int](repeating: 0, count: M), count: N)
for i in 0..<N {
let line = readLine()!.map { String($0) }
graph[i] = line
}
let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
let directions: [String: Int] = ["U": 0, "D": 1, "L": 2, "R": 3]
var result = 0
func dfs(x: Int, y: Int) {
// 방문 처리
visited[x][y] = 1
let direction = directions[graph[x][y]]!
let nx = x + dx[direction]
let ny = y + dy[direction]
if nx >= 0 && nx < N && ny >= 0 && ny < M {
if visited[nx][ny] == 1 {
result += 1
} else if visited[nx][ny] == 0 {
dfs(x: nx, y: ny)
}
}
visited[x][y] = 2
}
for i in 0..<N {
for j in 0..<M {
if visited[i][j] == 0 {
dfs(x: i, y: j)
}
}
}
print(result)
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.