N
개라면 신장 트리는 정확히 N-1
개의 간선을 가집니다.Prim
, Kruskal
알고리즘이 있습니다.
Kruskal(크루스칼) Algorithm
: 모든 간선을 가중치에 따라 오름차순으로 정렬한 후, 가장 가중치가 낮은 간선부터 선택하여 트리를 구성합니다. 이 과정에서 사이클이 형성되지 않도록 주의합니다.Prim (프림) Algorithm
: 한 노드에서 시작하여, 연결된 노드를 중 최소 가중치를 가진 간선을 선택하며 트리를 확장합니다.
O(ElogE)
의 시간이 소요됩니다. E: 간선의 수 find
와 union
연산은 거의 상수 시간에 가깝게 수행될 수 있기 때문에 무시됩니다. O(E + V)
입니다. 여기서 V
는 노드의 수이며, 이는 서로소 집합과 간선 리스트를 저장하는데 필요한 공간입니다. import Foundation
// 간선에 대한 정보를 담는 구조체
struct Edge {
var node1: Int
var node2: Int
var weight: Int
}
// 서로소 집합 자료구조
struct DisjointSet {
var parent: [Int]
var rank: [Int]
init(count: Int) {
parent = Array(0...count)
rank = Array(repeating: 0, count: count + 1)
}
mutating func find(_ node: Int) -> Int {
if parent[node] != node {
parent[node] = find(parent[node])
}
return parent[node]
}
mutating func union(_ node1: Int, _ node2: Int) {
let root1 = find(node1)
let root2 = find(node2)
if root1 != root2 {
if rank[root1] < rank[root2] {
parent[root1] = root2
} else if rank[root1] > rank[root2] {
parent[root2] = root1
} else {
parent[root2] = root1
rank[root1] += 1
}
}
}
}
// 크루스칼 알고리즘 구현
func kruskal(edges: [Edge], nodeCount: Int) -> Int {
var disjointSet = DisjointSet(count: nodeCount)
// 가중치 오름차순으로 정렬
let sortedEdges = edges.sorted { $0.weight < $1.weight}
var totalWeight = 0
for edge in sortedEdges {
// 두 노드에 대해서 부모 노드가 같다면 무시
if disjointSet.find(edge.node1) != disjointSet.find(edge.node2) {
disjointSet.union(edge.node1, edge.node2)
totalWeight += edge.weight
}
}
return totalWeight
}
let nodeCount = 7
let edge1 = Edge(node1: 1, node2: 2, weight: 29)
let edge2 = Edge(node1: 1, node2: 5, weight: 75)
let edge3 = Edge(node1: 2, node2: 3, weight: 35)
let edge4 = Edge(node1: 2, node2: 6, weight: 34)
let edge5 = Edge(node1: 3, node2: 4, weight: 7)
let edge6 = Edge(node1: 4, node2: 6, weight: 23)
let edge7 = Edge(node1: 4, node2: 7, weight: 13)
let edge8 = Edge(node1: 5, node2: 6, weight: 53)
let edge9 = Edge(node1: 6, node2: 7, weight: 25)
let edges = [edge1, edge2, edge3, edge4, edge5, edge6, edge7, edge8, edge9]
kruskal(edges: edges, nodeCount: nodeCount)
159
O(ElogV)
입니다. O(V)
입니다. 우선순위 큐에 모든 노드를 저장할 수 있어야 하기 때문입니다. import Foundation
struct Edge: Comparable {
var node: Int
var weight: Int
static func < (lhs: Edge, rhs: Edge) -> Bool {
return lhs.weight < rhs.weight
}
}
struct Heap {
var elements: [Edge] = []
let sort: (Edge, Edge) -> Bool
init(sort: @escaping (Edge, Edge) -> Bool, elements: [Edge] = []) {
self.sort = sort
self.elements = elements
if !elements.isEmpty {
for i in stride(from: count / 2 - 1, through: 0, by: -1) {
siftDown(from: i)
}
}
}
var isEmpty: Bool {
elements.isEmpty
}
var count: Int {
elements.count
}
func peek() -> Edge? {
elements.first
}
private func leftChildIndex(ofParentAt index: Int) -> Int {
(index * 2) + 1
}
private func rightChildIndex(ofParentAt index: Int) -> Int {
(index * 2) + 2
}
private func parentIndex(ofChildAt index: Int) -> Int {
(index - 1) / 2
}
private mutating func siftDown(from index: Int) {
var parent = index
while true {
let left = leftChildIndex(ofParentAt: parent)
let right = rightChildIndex(ofParentAt: parent)
var candidate = parent
if left < count && sort(elements[left], elements[candidate]) {
candidate = left
}
if right < count && sort(elements[right], elements[candidate]) {
candidate = right
}
if candidate == parent {
return
}
elements.swapAt(parent, candidate)
parent = candidate
}
}
private mutating func siftUp(from index: Int) {
var child = index
var parent = parentIndex(ofChildAt: child)
while child > 0 && sort(elements[child], elements[parent]) {
elements.swapAt(child, parent)
child = parent
parent = parentIndex(ofChildAt: child)
}
}
mutating func remove() -> Edge? {
guard !isEmpty else { return nil }
elements.swapAt(0, count - 1)
defer {
siftDown(from: 0)
}
return elements.removeLast()
}
mutating func insert(_ element: Edge) {
elements.append(element)
siftUp(from: elements.count - 1)
}
}
struct PriorityQueue {
private var heap: Heap
init(sort: @escaping (Edge, Edge) -> Bool, elements: [Edge] = []) {
heap = Heap(sort: sort, elements: elements)
}
var isEmpty: Bool {
heap.isEmpty
}
var peek: Edge? {
heap.peek()
}
@discardableResult
mutating func enqueue(_ element: Edge) -> Bool {
heap.insert(element)
return true
}
mutating func dequeue() -> Edge? {
heap.remove()
}
}
func prim(graph: [[(node: Int, weight: Int)]], startNode: Int) -> Int {
var totalWeight = 0
var visited = [Bool](repeating: false, count: graph.count)
let startEdge = Edge(node: startNode, weight: 0)
var pq: PriorityQueue = PriorityQueue(sort: <, elements: [startEdge])
while !pq.isEmpty {
let edge = pq.dequeue()!
// 해당 노드가 방문되었을때 분기처리
if visited[edge.node] { continue }
visited[edge.node] = true
totalWeight += edge.weight
for neighbor in graph[edge.node] {
// 이웃 노드가 visited 되지 않았다면 enqueue
// 이 부분은 효율성을 위한 분기 처리
if !visited[neighbor.node] {
let edge = Edge(node: neighbor.node, weight: neighbor.weight)
pq.enqueue(edge)
}
}
}
return totalWeight
}
let nodeCount = 7
var graph = [[(node: Int, weight: Int)]](repeating: [(node: Int, weight: Int)](), count: nodeCount + 1)
let edges = [
(1,2,29),
(1,5,75),
(2,3,35),
(2,6,34),
(3,4,7),
(4,6,23),
(4,7,13),
(5,6,53),
(6,7,25)
]
// 양방향 인접 리스트로 저장
for edge in edges {
graph[edge.0].append((node: edge.1, weight: edge.2))
graph[edge.1].append((node: edge.0, weight: edge.2))
}
prim(graph: graph, startNode: 6)
159
- 크루스칼 알고리즘은 그래프의 모든 간선을 가중치에 따라 오름차순으로 정렬한 뒤, 가장 가중치가 낮은 간선부터 선택하여 작동합니다.
- 가중치가 가장 낮은 간선부터 선택하되, 사이클이 형성하지 않는 간선만을 선택합니다.
Disjoint Set
자료구조를 사용하며, 사이클 형성 여부를 판단합니다.
- 그래프가 희소일 때(즉, 간선의 수가 노드의 수에 비해 상대적으로 적을 때) 더 효율적 입니다.
- 프림 알고리즘은 그래프의 임의 노드에서 시작하며, 점진적으로 최소 신장 트리를 확장해 나갑니다.
- 이미 선택된 노드들에 인접한 간선들 중에서 가장 가중치가 낮은 간선을 선택합니다.
Priority Queue
를 사용하여 다음에 추가할 간선을 효율적으로 선택합니다.
- 그래프가 밀집되어 있을 때(즉, 간선의 수가 노드의 수에 비해 많을 때) 더 효율적입니다.
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.