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 시간 복잡도 분석

  1. 정점 방문:
    • 각 정점을 한 번만 방문합니다. BFS는 큐에 정점을 추가할 때 그 정점을 방문했다고 표시하므로, 각 정점은 최대 한 번만 큐에 들어가고 큐에서 꺼내집니다.
    • 따라서 정점을 방문하는 시간 복잡도는 O(V)입니다.
  2. 간선 탐색:
    • 각 정점에서 연결된 모든 간선을 확인합니다. 이 과정에서 간선을 따라 이동할 때, 그 간선은 최대 한 번만 사용됩니다.
    • 따라서 간선을 탐색하는 시간 복잡도는 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