그래프의 기본 요소로, 위치를 나타냅니다. 노드라고도 불립니다.
두 정점을 연결하는 선으로, 두 정점 사이의 관계를 나타냅니다.
다양한 알고리즘(다익스트라, 벨만-포드, 플로이드-워셜 등)을 사용하여 최단 경로를 찾는데 사용됩니다.
최대 흐름 문제와 같은 네트워크 흐름 문제를 해결하는 데 사용됩니다.
그래프에서 사이클을 감지하는데 사용됩니다.
그래프 내의 모든 정점이 서로 연결되어 있는지 확인하는 문제입니다.
그래프의 정점을 두 그룹으로 나누어 각 그룹 내의 정점끼리는 서로 인접하지 않도록 할 수 있는지 확인하는 문제입니다.
그래프에 가중치나 방향이 있을 경우, 이를 고려하여 구현해야 합니다.
특정 문제에 적합한 자료구조 (인접 리스트 또는 인접 행렬)를 선택하여 효율적인 그래프 구현이 가능합니다.
public enum EdgeType {
case directed
case undirected
}
public protocol Graph {
associatedtype Element
func createVertex(data: Element) -> Vertex<Element>
func addDirectedEdge(from source: Vertex<Element>, to destination: Vertex<Element>, weight: Double?)
func addUndirectedEdge(between source: Vertex<Element>, and destination: Vertex<Element>, weight: Double?)
func add(_ edge: EdgeType, from source: Vertex<Element>, to destination: Vertex<Element>, weight: Double?)
func edges(from source: Vertex<Element>) -> [Edge<Element>]
func weight(from source: Vertex<Element>, to destination: Vertex<Element>) -> Double?
}
public struct Vertex<T> {
public let index: Int
public let data: T
}
extension Vertex: Hashable where T: Hashable {}
extension Vertex: Equatable where T: Equatable {}
extension Vertex: CustomStringConvertible {
public var description: String {
"\(index): \(data)"
}
}
Hashable
프로토콜은 Equatable
을 상속하므로 이 프로토콜의 요구 사항도 충족해야 합니다.public struct Edge<T> {
public let source: Vertex<T>
public let destination: Vertex<T>
public let weight: Double?
}
public class AdjacencyList<T: Hashable>: Graph {
private var adjacencies: [Vertex<T>: [Edge<T>]] = [:]
public init() { }
public func createVertex(data: T) -> Vertex<T> {
let vertex = Vertex(index: adjacencies.count, data: data)
adjacencies[vertex] = []
return vertex
}
public func edges(from source: Vertex<T>) -> [Edge<T>] {
adjacencies[source] ?? []
}
public func weight(from source: Vertex<T>, to destination: Vertex<T>) -> Double? {
edges(from: source)
.first { $0.destination == destination }?
.weight
}
public func addDirectedEdge(from source: Vertex<T>, to destination: Vertex<T>, weight: Double?) {
let edge = Edge(source: source, destination: destination, weight: weight)
adjacencies[source]?.append(edge)
}
public func addUndirectedEdge(between source: Vertex<T>, and destination: Vertex<T>, weight: Double?) {
addDirectedEdge(from: source, to: destination, weight: weight)
addDirectedEdge(from: destination, to: source, weight: weight)
}
public func add(_ edge: EdgeType, from source: Vertex<T>, to destination: Vertex<T>, weight: Double?) {
switch edge {
case .directed:
addDirectedEdge(from: source, to: destination, weight: weight)
case .undirected:
addUndirectedEdge(between: source, and: destination, weight: weight)
}
}
}
extension AdjacencyList: CustomStringConvertible {
public var description: String {
var result = ""
for (vertex, edges) in adjacencies { // 1
var edgeString = ""
for (index, edge) in edges.enumerated() { // 2
if index != edges.count - 1 {
edgeString.append("\(edge.destination), ")
} else {
edgeString.append("\(edge.destination)")
}
}
result.append("\(vertex) ---> [ \(edgeString) ]\n") // 3
}
return result
}
}
let graph = AdjacencyList<String>()
let singapore = graph.createVertex(data: "Singapore")
let tokyo = graph.createVertex(data: "Tokyo")
let hongKong = graph.createVertex(data: "Hong Kong")
let detroit = graph.createVertex(data: "Detroit")
let sanFrancisco = graph.createVertex(data: "San Francisco")
let washingtonDC = graph.createVertex(data: "Washington DC")
let austinTexas = graph.createVertex(data: "Austin Texas")
let seattle = graph.createVertex(data: "Seattle")
graph.add(.undirected, from: singapore, to: hongKong, weight: 300)
graph.add(.undirected, from: singapore, to: tokyo, weight: 500)
graph.add(.undirected, from: hongKong, to: tokyo, weight: 250)
graph.add(.undirected, from: tokyo, to: detroit, weight: 450)
graph.add(.undirected, from: tokyo, to: washingtonDC, weight: 300)
graph.add(.undirected, from: hongKong, to: sanFrancisco, weight: 600)
graph.add(.undirected, from: detroit, to: austinTexas, weight: 50)
graph.add(.undirected, from: austinTexas, to: washingtonDC, weight: 292)
graph.add(.undirected, from: sanFrancisco, to: washingtonDC, weight: 337)
graph.add(.undirected, from: washingtonDC, to: seattle, weight: 277)
graph.add(.undirected, from: sanFrancisco, to: seattle, weight: 218)
graph.add(.undirected, from: austinTexas, to: sanFrancisco, weight: 297)
print(graph)
2: Hong Kong ---> [ 0: Singapore, 1: Tokyo, 4: San Francisco ]
4: San Francisco ---> [ 2: Hong Kong, 5: Washington DC, 7: Seattle, 6: Austin Texas ]
5: Washington DC ---> [ 1: Tokyo, 6: Austin Texas, 4: San Francisco, 7: Seattle ]
6: Austin Texas ---> [ 3: Detroit, 5: Washington DC, 4: San Francisco ]
7: Seattle ---> [ 5: Washington DC, 4: San Francisco ]
0: Singapore ---> [ 2: Hong Kong, 1: Tokyo ]
1: Tokyo ---> [ 0: Singapore, 2: Hong Kong, 3: Detroit, 5: Washington DC ]
3: Detroit ---> [ 1: Tokyo, 6: Austin Texas ]
public class AdjacencyMatrix<T>: Graph {
private var vertices: [Vertex<T>] = []
private var weights: [[Double?]] = []
public init() { }
public func createVertex(data: T) -> Vertex<T> {
let vertex = Vertex(index: vertices.count, data: data)
vertices.append(vertex)
// 기존에 있던 값에 추사된 vertex 가중치를 위한 자리 만들기 (Up down)
for i in 0..<weights.count {
weights[i].append(nil)
}
// 추가된 vertex의 가중치를 위한 자리 만들기
let row = [Double?](repeating: nil, count: vertices.count)
weights.append(row)
return vertex
}
public func addDirectedEdge(from source: Vertex<T>, to destination: Vertex<T>, weight: Double?) {
weights[source.index][destination.index] = weight
}
public func addUndirectedEdge(between source: Vertex<T>, and destination: Vertex<T>, weight: Double?) {
addDirectedEdge(from: source, to: destination, weight: weight)
addDirectedEdge(from: destination, to: source, weight: weight)
}
public func add(_ edge: EdgeType, from source: Vertex<T>, to destination: Vertex<T>, weight: Double?) {
switch edge {
case .directed:
addDirectedEdge(from: source, to: destination, weight: weight)
case .undirected:
addUndirectedEdge(between: source, and: destination, weight: weight)
}
}
public func edges(from source: Vertex<T>) -> [Edge<T>] {
var edges: [Edge<T>] = []
for column in 0..<weights.count {
if let weight = weights[source.index][column] {
edges.append(Edge(source: source, destination: vertices[column], weight: weight))
}
}
return edges
}
public func weight(from source: Vertex<T>, to destination: Vertex<T>) -> Double? {
weights[source.index][destination.index]
}
}
extension AdjacencyMatrix: CustomStringConvertible {
public var description: String {
// 1
let verticesDescription = vertices.map { "\($0)" }
.joined(separator: "\n")
// 2
var grid: [String] = []
for i in 0..<weights.count {
var row = ""
for j in 0..<weights.count {
if let value = weights[i][j] {
row += "\(value)\t"
} else {
row += "ø\t\t"
}
}
grid.append(row)
}
let edgesDescription = grid.joined(separator: "\n")
// 3
return "\(verticesDescription)\n\n\(edgesDescription)"
}
}
let graph2 = AdjacencyMatrix<String>()
let singapore2 = graph2.createVertex(data: "Singapore")
let tokyo2 = graph2.createVertex(data: "Tokyo")
let hongKong2 = graph2.createVertex(data: "Hong Kong")
let detroit2 = graph2.createVertex(data: "Detroit")
let sanFrancisco2 = graph2.createVertex(data: "San Francisco")
let washingtonDC2 = graph2.createVertex(data: "Washington DC")
let austinTexas2 = graph2.createVertex(data: "Austin Texas")
let seattle2 = graph2.createVertex(data: "Seattle")
graph2.add(.directed, from: singapore2, to: hongKong2, weight: 300)
graph2.add(.directed, from: singapore2, to: tokyo2, weight: 500)
graph2.add(.directed, from: hongKong2, to: tokyo2, weight: 250)
graph2.add(.directed, from: tokyo2, to: detroit2, weight: 450)
graph2.add(.directed, from: tokyo2, to: washingtonDC2, weight: 300)
graph2.add(.directed, from: hongKong2, to: sanFrancisco2, weight: 600)
graph2.add(.directed, from: detroit2, to: austinTexas2, weight: 50)
graph2.add(.directed, from: austinTexas2, to: washingtonDC2, weight: 292)
graph2.add(.directed, from: sanFrancisco2, to: washingtonDC2, weight: 337)
graph2.add(.directed, from: washingtonDC2, to: seattle2, weight: 277)
graph2.add(.directed, from: sanFrancisco2, to: seattle2, weight: 218)
graph2.add(.directed, from: austinTexas2, to: sanFrancisco2, weight: 297)
print(graph2)
0: Singapore
1: Tokyo
2: Hong Kong
3: Detroit
4: San Francisco
5: Washington DC
6: Austin Texas
7: Seattle
ø 500.0 300.0 ø ø ø ø ø
ø ø ø 450.0 ø 300.0 ø ø
ø 250.0 ø ø 600.0 ø ø ø
ø ø ø ø ø ø 50.0 ø
ø ø ø ø ø 337.0 ø 218.0
ø ø ø ø ø ø ø 277.0
ø ø ø ø 297.0 292.0 ø ø
ø ø ø ø ø ø ø ø
희소 그래프에서는 인접 리스트가, 밀집 그래프에서는 인접 행렬이 메모리 효율적입니다.
간선의 존재 여부를 자주 확인해야 한다면 인접 행렬, 특정 정점의 인접 정점을 자주 확인한다면 인접 리스트가 유리합니다.
메모리 사용량을 줄이고자 한다면 인접 리스트, 연산 속도를 높이고자 한다면 인접 행렬을 선택합니다.
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.