18352 swift - 특정 거리의 도시 찾기
2024. 10. 2. 07:42ㆍ🐣/BOJ

처음 접근 - 벨만 포드
벨만 포드 풀이 시간초과 코드 보기.
더보기
이유. O(V * E)
벨만-포드 알고리즘의 시간 복잡도는 **O(V * E)**입니다. 여기서 V는 정점의 수, E는 간선의 수를 의미합니다.
주어진 문제에서:
- N (정점의 수) = 최대 300,000
- M (간선의 수) = 최대 1,000,000
따라서 벨만-포드를 사용했을 때 최악의 경우 시간 복잡도는:
O(V×E)=O(300,000×1,000,000)=300,000,000,000O(V \times E) = O(300,000 \times 1,000,000) = 300,000,000,000O(V×E)=O(300,000×1,000,000)=300,000,000,000
즉, 300조의 연산을 수행해야 합니다.
import Foundation let INF = 987654321 struct Edge { var s: Int, e: Int, t: Int } let nmkx = readLine()!.split(separator: " ").map{Int(String($0))!} let (n, m, k, x) = (nmkx[0], nmkx[1], nmkx[2], nmkx[3]) var edges = [Edge]() for _ in 0..<m { let se = readLine()!.split(separator: " ").map{Int(String($0))!} edges.append(Edge(s: se[0], e: se[1], t: 1)) } var dist = Array(repeating: INF, count: n + 1) dist[x] = 0 for _ in 0..<n-1 { for edge in edges { if dist[edge.e] > dist[edge.s] + edge.t { dist[edge.e] = dist[edge.s] + edge.t } } } var result = "" for (i, v) in dist.enumerated() { if v == k { result += "\(i)\n" } } print(result == "" ? "-1" : result)
BFS 풀이 코드. 링크드리스트로 Queue를 구현하여 풀이.
시간 복잡도: O(V + E)
- V: 그래프의 정점(Vertex) 개수
- E: 그래프의 간선(Edge) 개수
BFS 시간 복잡도 분석
- 정점 방문:
- 각 정점을 한 번만 방문합니다. BFS는 큐에 정점을 추가할 때 그 정점을 방문했다고 표시하므로, 각 정점은 최대 한 번만 큐에 들어가고 큐에서 꺼내집니다.
- 따라서 정점을 방문하는 시간 복잡도는 O(V)입니다.
- 간선 탐색:
- 각 정점에서 연결된 모든 간선을 확인합니다. 이 과정에서 간선을 따라 이동할 때, 그 간선은 최대 한 번만 사용됩니다.
- 따라서 간선을 탐색하는 시간 복잡도는 O(E)입니다.
BFS의 총 시간 복잡도
- BFS는 큐에 정점을 넣고, 큐에서 정점을 꺼내면서 그 정점에 연결된 모든 간선을 탐색합니다.
- 모든 정점을 한 번 방문하고, 모든 간선을 한 번씩 확인하므로 시간 복잡도는 O(V + E)가 됩니다.
BFS 시간 복잡도의 의미
- V와 E의 관계에 따라 그래프가 다르지만, 보통 E가 V에 비해 많을 수 있기 때문에 O(V + E)로 시간 복잡도를 표현합니다.
- 희소 그래프 (Sparse Graph): 간선의 수가 정점의 수와 비슷한 경우 (즉, E ≈ V) - 시간 복잡도는 O(V)에 가까워집니다.
- 밀집 그래프 (Dense Graph): 간선의 수가 정점의 수의 제곱에 가까운 경우 (즉, E ≈ V^2) - 시간 복잡도는 O(E)에 가까워집니다.
BFS와 시간 초과 문제
- 주어진 문제에서 정점의 수 N ≤ 300,000, 간선의 수 M ≤ 1,000,000으로 가정하면:
- 시간 복잡도는 O(V + E) = O(300,000 + 1,000,000) = O(1,300,000)입니다.
- 이는 일반적인 알고리즘 문제에서 제한 시간인 1초에 충분히 처리할 수 있는 범위 내에 있습니다. 보통 1초에 약 10^8번의 연산이 가능하다고 가정하기 때문에 BFS는 이 크기의 입력에서도 효율적으로 실행될 수 있습니다.
BFS와 우선순위 큐 사용 시 차이점
- BFS는 모든 간선의 가중치가 같을 때 최적의 시간 복잡도를 가지며 간단하게 구현할 수 있습니다.
- 다익스트라 알고리즘은 가중치가 다를 때 최소 비용 경로를 탐색하기 위해 우선순위 큐를 사용합니다. 이때 우선순위 큐의 삽입과 삭제에 O(log V)가 소요되기 때문에 BFS보다 다소 복잡하지만, 가중치가 다를 때 더 효율적으로 사용할 수 있습니다.
Swift 링크드리스트로 구현한 큐(Queue)
더보기
class LinkedListNode<T> { let value: T weak var previous: LinkedListNode? var next: LinkedListNode? init(value: T) { self.value = value } } struct LinkedList<T> { var isEmpty: Bool { return self.head == nil } mutating func appendValue(_ value: T) { let new = LinkedListNode<T>(value: value) if self.isEmpty { self.head = new } else { self.tail?.next = new new.previous = self.tail } self.tail = new } mutating func removeHead() -> T? { guard let head = self.head else { return nil } return self.removeNode(head) } mutating func removeTail() -> T? { guard let tail = self.tail else { return nil } return self.removeNode(tail) } mutating func removeNode(_ node: LinkedListNode<T>) -> T { if let prev = node.previous { prev.next = node.next } else { self.head = node.next } if let next = node.next { next.previous = node.previous } else { self.tail = node.previous } node.previous = nil node.next = nil return node.value } private(set) var head: LinkedListNode<T>? private var tail: LinkedListNode<T>? } struct QueueWithLinkedList<T> { var isEmpty: Bool { return self.linkedList.isEmpty } var front: T? { return self.linkedList.head?.value } mutating func enqueue(_ item: T) { self.linkedList.appendValue(item) } mutating func dequeue() -> T? { guard !self.isEmpty else { return nil } return self.linkedList.removeHead() } mutating func popLast() -> T? { guard !self.isEmpty else { return nil } return self.linkedList.removeTail() } private var linkedList = LinkedList<T>() }
import Foundation let nmkx = readLine()!.split(separator: " ").map { Int(String($0))! } let (n, m, k, x) = (nmkx[0], nmkx[1], nmkx[2], nmkx[3]) var graph = [[Int]](repeating: [], count: n + 1) for _ in 0..<m { let ab = readLine()!.split(separator: " ").map { Int(String($0))! } graph[ab[0]].append(ab[1]) } var distance = Array(repeating: -1, count: n + 1) distance[x] = 0 var queue = QueueWithLinkedList<Int>() queue.enqueue(x) while !queue.isEmpty { guard let current = queue.dequeue() else { continue } for next in graph[current] { if distance[next] == -1 { distance[next] = distance[current] + 1 queue.enqueue(next) } } } var result = [Int]() for i in 1...n { if distance[i] == k { result.append(i) } } if result.isEmpty { print("-1") } else { result.sort() for city in result { print(city) } }
다익스트라 알고리즘의 특징
- 시간 복잡도: O((V + E) * log V).
- V는 정점의 개수 (n), E는 간선의 개수 (m)입니다.
- 우선순위 큐에서 가장 작은 값을 꺼내고 삽입하는 데 소요되는 시간 복잡도는 log V이기 때문에, 그래프의 크기가 크더라도 효율적으로 처리할 수 있습니다.
Swift에서 우선순위큐를 활용한 다익스트라 알고리즘 코드
Swift에서 힙으로 구현한 우선순위 큐
더보기
struct Heap<T> { var elements: [T] = [] let priorityFunction: (T, T) -> Bool init(priorityFunction: @escaping (T, T) -> Bool) { self.priorityFunction = priorityFunction } var isEmpty: Bool { return elements.isEmpty } func peek() -> T? { return elements.first } mutating func insert(_ value: T) { elements.append(value) siftUp(elementAtIndex: elements.count - 1) } mutating func remove() -> T? { guard !elements.isEmpty else { return nil } if elements.count == 1 { return elements.removeFirst() } else { let value = elements.first elements[0] = elements.removeLast() siftDown(elementAtIndex: 0) return value } } private mutating func siftUp(elementAtIndex index: Int) { let parent = parentIndex(of: index) guard !isRoot(index), priorityFunction(elements[index], elements[parent]) else { return } elements.swapAt(index, parent) siftUp(elementAtIndex: parent) } private mutating func siftDown(elementAtIndex index: Int) { let childIndex = highestPriorityIndex(for: index) if index == childIndex { return } elements.swapAt(index, childIndex) siftDown(elementAtIndex: childIndex) } private func isRoot(_ index: Int) -> Bool { return index == 0 } private func leftChildIndex(of index: Int) -> Int { return 2 * index + 1 } private func rightChildIndex(of index: Int) -> Int { return 2 * index + 2 } private func parentIndex(of index: Int) -> Int { return (index - 1) / 2 } private func highestPriorityIndex(for parent: Int) -> Int { let leftChild = leftChildIndex(of: parent) let rightChild = rightChildIndex(of: parent) var highest = parent if leftChild < elements.count && priorityFunction(elements[leftChild], elements[highest]) { highest = leftChild } if rightChild < elements.count && priorityFunction(elements[rightChild], elements[highest]) { highest = rightChild } return highest } }
import Foundation let nmkx = readLine()!.split(separator: " ").map { Int(String($0))! } let (n, m, k, x) = (nmkx[0], nmkx[1], nmkx[2], nmkx[3]) var graph = [[(node: Int, cost: Int)]](repeating: [], count: n + 1) for _ in 0..<m { let ab = readLine()!.split(separator: " ").map { Int(String($0))! } graph[ab[0]].append((node: ab[1], cost: 1)) } var distance = Array(repeating: Int.max, count: n + 1) distance[x] = 0 var priorityQueue = Heap<(node: Int, cost: Int)> { (lhs: (node: Int, cost: Int), rhs: (node: Int, cost: Int)) in lhs.cost < rhs.cost } priorityQueue.insert((node: x, cost: 0)) while !priorityQueue.isEmpty { guard let current = priorityQueue.remove() else { break } let currentNode = current.node let currentCost = current.cost if currentCost > distance[currentNode] { continue } for neighbor in graph[currentNode] { let nextNode = neighbor.node let newCost = currentCost + neighbor.cost if newCost < distance[nextNode] { distance[nextNode] = newCost priorityQueue.insert((node: nextNode, cost: newCost)) } } } var result = [Int]() for i in 1...n { if distance[i] == k { result.append(i) } } if result.isEmpty { print("-1") } else { result.sort() for city in result { print(city) } }
'🐣 > BOJ' 카테고리의 다른 글
1299 c++ - 전쟁-탈출편2 (0) | 2024.10.05 |
---|---|
1446 swift / c++ - 지름길 (0) | 2024.10.04 |
12100 swift / c++ - 2048 (Easy) (0) | 2024.09.23 |
6603 swift - 로또 (0) | 2024.04.28 |
1946 swift - 신입 사원 / 시간 초과 해결 (0) | 2024.03.10 |