매번 '가장 비용이 적은 노드'를 선택해서 임의의 과정을 반복하기 때문입니다. 하지만 종종 일부 단계가 비용이 더 들더라도 전체 비용이 더 낮은 솔루션을 놓치기도 합니다. 그럼에도 불구하고 매우 빠르고 꽤 좋은 결과를 도출합니다.
O(E logV)
입니다. 여기서 V
는 노드의 개수, E
는 간선의 개수입니다.struct PrioirtyQueueArray<T: Equatable> {
private var elements:[T] = []
let sort: (T,T) -> Bool
init(sort: @escaping (T,T) -> Bool, elements: [T] = []) {
self.sort = sort
self.elements = elements
self.elements.sort(by: sort)
}
public var isEmpty: Bool {
elements.isEmpty
}
public var peek: T? {
elements.first
}
public mutating func enqueue(_ element: T) -> Bool {
for (index, otherElement) in elements.enumerated() {
if sort(element, otherElement) {
elements.insert(element, at: index)
return true
}
}
elements.append(element)
return true
}
public mutating func dequeue() -> T? {
isEmpty ? nil : elements.removeFirst()
}
}
public enum EdgeType {
case directed
case undirected
}
public struct Vertex<T> {
public let index: Int
public let data: T
}
extension Vertex: Hashable where T: Hashable {}
extension Vertex: Equatable where T: Equatable {}
public struct Edge<T> {
public let source: Vertex<T>
public let destination: Vertex<T>
public let weight: Double?
}
public class AdjacencyList<T: Hashable> {
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 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)
}
}
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 enum Visit<T: Hashable> {
case start
case edge(Edge<T>)
}
start
는 시작 정점을 의미합니다. edge(Edge<T>)
는 시작 정점으로 되돌아가는 경로로 이어지는 관련 간선을 가지고 있습니다. public class Dijkstra<T: Hashable> {
public typealias Graph = AdjacencyList<T>
let graph: Graph
public init(graph: Graph) {
self.graph = graph
}
// 현재 vertex에서 시작 vertex까지의 총 Weight를 추적할 수 있는 메커니즘
private func route(to destination: Vertex<T>, with paths: [Vertex<T>: Visit<T>]) -> [Edge<T>] {
var vertex = destination // 1
var path: [Edge<T>] = [] // 2
while let visit = paths[vertex], case .edge(let edge) = visit { // 3
path = [edge] + path // 4
vertex = edge.source // 5
}
return path // 6
}
destination
정점과 기존 경로를 담은 dictionary
을 받아들이고, destination
정점으로 이어지는 경로를 생성합니다. destination
정점에서 시작합니다. private func distance(to destination: Vertex<T>, with paths: [Vertex<T>: Visit<T>]) -> Double {
let path = route(to: destination, with: paths) // 1
let distances = path.compactMap { $0.weight } // 2
return distances.reduce(0.0, +) // 3
}
destination
정점과 기존 경로들을 담은 dictionary
를 받아들여 총 가중치를 반환합니다. desination
으로 가는 경로를 구성합니다. compactMap
을 사용하여 경로에서 모든 nil
가중치 값을 제거합니다. reduce
를 사용하여 모든 간선의 가중치를 합산합니다. public func shortestPath(from start: Vertex<T>) -> [Vertex<T>: Visit<T>] {
var paths: [Vertex<T>: Visit<T>] = [start: .start] // 1
// 2
var priorityQueue = PrioirtyQueueArray<Vertex<T>>(sort: {
self.distance(to: $0, with: paths) < self.distance(to: $1, with: paths)
})
priorityQueue.enqueue(start) // 3
dictionary
을 return 합니다. path
를 정의하고 시작 정점으로 초기화합니다.distance
메서드를 사용하여 시작 정점으로부터의 거리에 따라 정점들을 정렬합니다. while let vertex = priorityQueue.dequeue() { // 1
for edge in graph.edges(from: vertex) { // 2
guard let weight = edge.weight else { // 3
continue
}
if paths[edge.destination] == nil || distance(to: vertex, with: paths) + weight < distance(to: edge.destination, with: paths) { // 4
paths[edge.destination] = .edge(edge)
priorityQueue.enqueue(edge.destination)
}
}
}
return paths
}
destination
정점이 이전에 방문된 적이 없거나, 더 저렴한 경로를 발견했다면, 경로를 업데이트하고 이웃하는 정점을 우선순위 큐에 추가합니다. public func shortestPath(to destination: Vertex<T>, paths: [Vertex<T>: Visit<T>]) -> [Edge<T>] {
return route(to: destination, with: paths)
}
destination
정점과 최단 경로 dictionary
을 인자로 받아 destination
정점으로의 경로를 반환합니다. public func getAllShortestPath(from source: Vertex<T>) -> [Vertex<T>: [Edge<T>]] {
var pathsDict = [Vertex<T>: [Edge<T>]]()
let pathsFromSource = shortestPath(from: source)
for vertex in graph.vertices {
let path = shortestPath(to: vertex, paths: pathsFromSource)
pathsDict[vertex] = path
}
return pathsDict
}
}
let dijkstraList = DijkstraList(graph: graph1)
let pathsFromA = dijkstraList.shortestPath(from: tokyo)
let path = dijkstraList.shortestPath(to: seattle, paths: pathsFromA)
for edge in path {
print("\(edge.source) --|\(edge.weight ?? 0.0)|--> \(edge.destination)")
}
let allPath = dijkstraList.getAllShortestPath(from: tokyo)
for path in allPath {
for edges in path.value {
print("\(edges.source) --|\(edges.weight ?? 0.0)|--> \(edges.destination)")
}
print()
}
1: Tokyo --|300.0|--> 5: Washington DC
5: Washington DC --|277.0|--> 7: Seattle
1: Tokyo --|300.0|--> 5: Washington DC
1: Tokyo --|450.0|--> 3: Detroit
3: Detroit --|50.0|--> 6: Austin Texas
6: Austin Texas --|297.0|--> 4: San Francisco
1: Tokyo --|450.0|--> 3: Detroit
1: Tokyo --|450.0|--> 3: Detroit
3: Detroit --|50.0|--> 6: Austin Texas
import Foundation
// 최댓값
let INF = Int.max
// 노드의 개수
let nodeCount: Int = 6
// 간선의 개수
let link: Int = 11
// 시작 노드 번호
let start: Int = 1
// 각 노드에 연결되어 있는 노드에 대한 정보를 담는 배열
var graph = [[[Int]]](repeating: [[Int]](), count: nodeCount + 1)
// 방문한 적이 있는지 체크하는 목적의 배열
var visited = [Bool](repeating: false, count: nodeCount + 1)
// 최단 거리 테이블을 모두 무한으로 초기화
var distance = [Int](repeating: INF, count: nodeCount + 1)
// 모든 간선 정보 입력받기
// a번 노드에서 b번 노드로 가는 비용이 c라는 의미
for _ in 1...link {
let array = readLine()!.components(separatedBy: " ").map { Int($0)!}
let a = array[0]
let b = array[1]
let c = array[2]
graph[a].append([b,c])
}
// 방문하지 않은 노드 중에서, 가장 최단 거리가 짧은 노드의 번호를 반환
func get_smallest_node() -> Int {
var min_value = INF
// 가장 최단 거리가 짧은 노드(인덱스)
var index = 0
for i in 1...nodeCount {
if distance[i] < min_value && !visited[i] {
min_value = distance[i]
index = i
}
}
return index
}
func dijkstra(_ start: Int) {
distance[start] = 0
visited[start] = true
for i in graph[start] {
distance[i[0]] = i[1]
}
// 시작 노드를 제외한 전체 n-1개의 노드에 대해 반복
for _ in 0..<nodeCount {
// 현재 최단 거리가 가장 짧은 노드를 꺼내서, 방문 처리
let now = get_smallest_node()
visited[now] = true
// 현재 노드와 연결된 다른 노드를 확인
for j in graph[now] {
let cost = distance[now] + j[1]
if cost < distance[j[0]] {
distance[j[0]] = cost
}
}
}
}
dijkstra(start)
for i in 1...nodeCount {
if distance[i] == INF {
print("INFINITY")
} else {
print("\(i): ", distance[i])
}
}
struct Heap {
var elements = [(Int,Int)]()
let sort: (Int,Int) -> Bool
init(elements: [(Int,Int)] = [(Int,Int)](), sort: @escaping (Int, Int) -> Bool) {
self.elements = elements
self.sort = sort
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() -> (Int,Int)? {
elements.first
}
private func leftChildIndex(ofParentAt index: Int) -> Int {
(2 * index) + 1
}
private func rightChildIndex(ofParentAt index: Int) -> Int {
(2 * index) + 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].1, elements[candidate].1) {
candidate = left
}
if right < count && sort(elements[right].1, elements[candidate].1) {
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(orParentAt: child)
while child > 0 && sort(elements[child].1, elements[parent].1) {
elements.swapAt(child, parent)
child = parent
parent = parentIndex(orParentAt: child)
}
}
mutating func remove() -> (Int,Int)? {
guard !isEmpty else {
return nil
}
elements.swapAt(0, count - 1)
defer {
siftDown(from: 0)
}
return elements.removeLast()
}
mutating func insert(_ element: (Int,Int)) {
elements.append(element)
siftUp(from: elements.count - 1)
}
}
struct PriorityQueue {
private var heap: Heap
init(sort: @escaping (Int,Int) -> Bool, elements:[(Int,Int)] = []) {
heap = Heap(elements: elements,sort: sort)
}
var isEmpty: Bool {
heap.isEmpty
}
var peek: (Int,Int)? {
heap.peek()
}
@discardableResult mutating func enqueue(_ element: (Int,Int)) -> Bool {
heap.insert(element)
return true
}
mutating func dequeue() -> (Int,Int)? {
heap.remove()
}
}
let INF = Int.max
let line = readLine()!.components(separatedBy: " ").map { Int($0)!}
// 노드
var n = line[0]
// 간선
var m = line[1]
// 시작 노드 번호를 입력받기
let start = Int(readLine()!)!
// 각 노드에 연결되어 있는 노드에 대한 정보를 담는 리스트 만들기
var graph = [[(Int,Int)]](repeating:[(Int,Int)](), count: n + 1)
// 최단 거리 테이블을 모두 무한으로 초기화
var distance = [Int](repeating: INF, count: n + 1)
// 모든 간선 정보 입력받기
for _ in 0..<m {
let line2 = readLine()!.components(separatedBy: " ").map { Int($0)!}
let a = line2[0]
let b = line2[1]
let c = line2[2]
// a번 노드에서 b번 노드로 가는 비용이 c
graph[a].append((b,c))
}
func dijkstra(_ start: Int) {
var queue = PriorityQueue(sort: <, elements: [(start,0)])
distance[start] = 0
while !queue.isEmpty {
// 가장 최단 거리가 짧은 노드에 대한 정보 꺼내기
let queueValue = queue.dequeue()!
let dist = queueValue.1
let nowNode = queueValue.0
// 현재 노드가 이미 처리된 적이 있다면 무시
if distance[nowNode] < dist {
continue
}
// 현재 노드와 연결된 다른 인접한 노드들을 확인
for i in graph[nowNode] {
let cost = dist + i.1
if cost < distance[i.0] {
distance[i.0] = cost
queue.enqueue((i.0,cost))
}
}
}
}
3 2 1
1 2 4
1 3 2
2 4
import Foundation
struct Heap {
var elements = [(Int, Int)]()
let sort: (Int, Int) -> Bool
init(elements: [(Int, Int)] = [(Int, Int)](), sort: @escaping (Int, Int) -> Bool) {
self.elements = elements
self.sort = sort
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() -> (Int, Int)? {
elements.first
}
private func leftChildIndex(ofParentAt index: Int) -> Int {
(2 * index) + 1
}
private func rightChildIndex(ofParentAt index: Int) -> Int {
(2 * index) + 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].1, elements[candidate].1) {
candidate = left
}
if right < count && sort(elements[right].1, elements[candidate].1) {
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].1, elements[parent].1) {
elements.swapAt(child, parent)
child = parent
parent = parentIndex(ofchildAt: child)
}
}
mutating func remove() -> (Int, Int)? {
guard !isEmpty else {
return nil
}
elements.swapAt(0, count - 1)
defer {
siftDown(from: 0)
}
return elements.removeLast()
}
mutating func insert(_ element: (Int, Int)) {
elements.append(element)
siftUp(from: elements.count - 1)
}
}
struct PriorityQueue {
private var heap: Heap
init(sort: @escaping (Int,Int) -> Bool, elements:[(Int,Int)] = []) {
heap = Heap(elements: elements,sort: sort)
}
var isEmpty: Bool {
heap.isEmpty
}
var peek: (Int,Int)? {
heap.peek()
}
@discardableResult mutating func enqueue(_ element: (Int,Int)) -> Bool {
heap.insert(element)
return true
}
mutating func dequeue() -> (Int,Int)? {
heap.remove()
}
}
let INF = Int.max
let testCase = readLine()!.components(separatedBy: " ").map { Int($0)! }
// 도시의 총 개수
let n = testCase[0]
// 통로의 총 개수
let m = testCase[1]
// 메시지 보내는 도시
let start = testCase[2]
// destination / weight
var graph = [[(Int, Int)]](repeating: [(Int, Int)](), count: n + 1)
var distance = [Int](repeating: INF, count: n + 1)
var visited = [Bool](repeating: false, count: n + 1)
for _ in 0..<m {
let test = readLine()!.components(separatedBy: " ").map { Int($0)! }
// to destination weight
graph[test[0]].append((test[1], test[2]))
}
func dijkstra(_ start: Int) {
// queue에 값을 넣고 시작합니다. 이때 시작값의 weight 값 추가, 방문처리를 진행합니다.
var queue = PriorityQueue(sort: <, elements: [(start, 0)])
distance[start] = 0
visited[start] = true
// queue가 비워질때까지 진행합니다.
while !queue.isEmpty {
// queue의 최신값을 뽑아내며, 그 값을 활용합니다.
let value = queue.dequeue()!
let vertex = value.0
let weight = value.1
// distance에 있는 값이 weight보다 작다면 즉, 기존값이 더 작다면 넘어갑니다.
if distance[vertex] < weight {
continue
}
// 현재 Vertex의 Destination들을 확인합니다.
for i in graph[vertex] {
// 현재 vertex에서 destination으로 가는 값을 현재 vertex의 최단거리와 더해줍니다.
let newWeight = weight + i.1
// 더해준 값이 작다면, 해당값으로 값을 변경해주고 enqueue 해줍니다.
if newWeight < distance[i.0] {
distance[i.0] = newWeight
visited[i.0] = true
queue.enqueue((i.0, newWeight))
}
}
}
}
dijkstra(start)
var result1 = 0
var result2 = Int.min
for i in 1...n {
if i > 1 && visited[i] == true {
result1 += 1
}
result2 = max(distance[i], result2)
}
print("\(result1) \(result2)")
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.