Algorithm/Theory

프림 알고리즘

삼쓰 웅쓰 2020. 3. 6. 17:13
728x90
- MST(Minimum Spanning Tree)를 만드는 방법 중 하나입니다.
- 현재 트리를 기준으로 가장 최소인 점을 찾아나가는 방식입니다.
- 사이클을 확인할 필요가 없습니다. (현재트리를 기준으로 하기 때문에 사이클이 생기지 않습니다.)

 


 

프림 알고리즘은 현재까지 생성된 트리를 기준으로 가장 최소 비용의 간선을 선택해서 트리를 확장시켜 나갑니다.
현재 트리를 기준으로 한다는 말 속에는 이미 방문한 점은 방문하지 않는다는 말이 포함되어 있습니다.
때문에 자연스럽게 사이클이 생기지 않습니다 :)

과정은 간단합니다. 

1. 현재 트리를 기준으로 최소 비용으로 방문 가능한 점을 방문합니다.

2. 새로운 점을 기준으로 최소 간선의 목록을 업데이트 합니다.

직접 문제를 하나 풀어보면 쉽게 이해하실 수 있으실거에요.
같이 예를 살펴보죠 :) 

 

저희의 목적은 방문 가능한 노드들 중에서 최소 비용이 드는 길을 선택하는거에요. (방문한 노드는 다시 방문하지 않습니다.)
길을 선택했으면 방문 가능한 노드들이 더 생겨나겠죠? 이걸 기준으로 최소 비용이 드는 길을 업데이트 해줍니다.

먼저 시작 노드를 정합니다. 0부터 시작해볼게요.
그럼 1, 2 번 노드가 방문이 가능해지고 각각 5, 4가 비용이 최소 비용이 됩니다.

그럼 5, 4중에서 최소 비용인 4를 선택해야겠죠? 2번을 방문합니다.
이미 방문한 0은 제외하고 새롭게 3, 4번 노드를 방문할 수 있게 되었습니다.

이제 최소 비용은 2가 되겠네요. 1번 노드를 방문합니다.
3번 노드를 방문할 수 있는 다른 길이 생겼지만 이미 최소 비용인 6보다 크기 때문에 업데이트 하지 않습니다.

이제 감 잡으셨죠? 최소 비용인 3번 노드를 방문합니다.
4번 노드로 갈 수 있는 새로운 길이 생겼네요. 최소 비용으로 업데이트 시켜줍니다.

이제 마지막 5번 노드만 남았습니다. 새로운 길이 생겼으나 최소비용과 동일합니다. 둘 다 선택이 가능하겠네요.

마지막 길까지 선택해주면 MST가 완성됩니다.
어렵지 않죠??
차근차근 따라오시면 쉽게 이해하셨으리라 생각해요 :)

구현 

문제는 구현이죠... 워낙 유명한 이론이기 때문에 이미 다른 언어로는 많은 코드가 있으니 참고하시면 될 것 같구요.

저는 Swift로 구현해보았습니다.

최소값을 구하는 부분을 Heap을 사용한다던지, 좀 더 좋은 방법으로 구할 수 있을 것 같은데,

추후에 업데이트 해보겠습니다.

/**
 * 현재 노드에 연결되어 있는 노드만 확인할 것이기 때문에
 * 간단하게 목적지와 비용만 저장하는 Edge 를 정의합니다.
 */
struct Edge {
    var destination: Int
    var cost: Int
}

func updateDist(_ tree: [Edge], dist: inout [Int?]) -> [Edge] {
    for edge in tree {
        if let d = dist[edge.destination] {
            if d > edge.cost {
                dist[edge.destination] = edge.cost
            }
        } else {
            dist[edge.destination] = edge.cost
        }
    }
    
    return tree
}

func prim(_ n: Int, _ costs: [[Int]]) -> Int {
    
    // 최소 비용을 저장할 배열
    var dist = [Int?](repeating: nil, count: n)
    
    // 방문 체크 배열
    var visit = [Bool](repeating: false, count: n)
    
    // 방문한 노드의 개수 ( 방문 체크 배열에서 확인해도 되지만.. 그냥 간단하게 변수를 하나 만들었습니다. )
    var visitCount = 0
    
    // 현재 방문 노드를 저장합니다. 0에서 시작합니다.
    var now = 0
    
    // 최소비용을 저장한다.
    var minimumCost = 0
    /**
     * 배열의 인덱스가 해당 노드가 되고 연결된
     * 연결된 선을 저장합니다.
     */
    var tree = [[Edge]](repeating: [], count: n)
    
    /**
     * Setup Tree
     * 선의 양 끝을 둘다 저장해줘야 합니다.
     */
    for cost in costs {
        tree[cost[0]].append(Edge(destination: cost[1], cost: cost[2]))
        tree[cost[1]].append(Edge(destination: cost[0], cost: cost[2]))
    }
    
    visit[now] = true
    visitCount += 1
    
    // 탐색
    while visitCount < n {
        let tree = updateDist(tree[now], dist: &dist)
        
        // 최소값 구하기
        var minCost: Int? = nil
        for i in 0..<n {
            guard !visit[i], let d = dist[i] else {continue}
            
            if let min = minCost {
                if min > d {
                    minCost = d
                    now = i
                }
            } else {
                minCost = d
                now = i
            }
        }
        
        visit[now] = true
        visitCount += 1
        minimumCost += minCost!
    }
    
    
    return minimumCost
}


// 입력: ( Int: 노드의 개수, [[Int]]: [[출발 노드, 끝 노드, 비용]] )
// 예상결과: 23
prim(6, [[0, 1, 5], [0, 2, 4], [1, 2, 2], [1, 3, 7], [2, 3, 6], [2, 4, 11], [3, 5, 8], [3, 4, 3], [4, 5, 8]])

 

감사합니다. :)