[프로그래머스] 전력망을 둘로 나누기 - python 본문

코테 문제 풀이

[프로그래머스] 전력망을 둘로 나누기 - python

미니모아 2022. 4. 23. 15:21
반응형

전력망을 둘로 나누기

문제

n개의 송전탑이 전선을 통해 하나의 트리 형태로 연결되어 있습니다. 당신은 이 전선들 중 하나를 끊어서 현재의 전력망 네트워크를 2개로 분할하려고 합니다. 이때, 두 전력망이 갖게 되는 송전탑의 개수를 최대한 비슷하게 맞추고자 합니다.

송전탑의 개수 n, 그리고 전선 정보 wires가 매개변수로 주어집니다. 전선들 중 하나를 끊어서 송전탑 개수가 가능한 비슷하도록 두 전력망으로 나누었을 때, 두 전력망이 가지고 있는 송전탑 개수의 차이(절대값)를 return 하도록 solution 함수를 완성해주세요.

제한사항

  • n은 2 이상 100 이하인 자연수입니다.
  • wires는 길이가 n-1인 정수형 2차원 배열입니다.
    • wires의 각 원소는 [v1, v2] 2개의 자연수로 이루어져 있으며, 이는 전력망의 v1번 송전탑과 v2번 송전탑이 전선으로 연결되어 있다는 것을 의미합니다.
    • 1 ≤ v1 < v2 ≤ n 입니다.
    • 전력망 네트워크가 하나의 트리 형태가 아닌 경우는 입력으로 주어지지 않습니다.

풀이

처음에는 1개씩 끊어보면서 union-find해서 각각 집합의 개수를 구한 후 개수 차가 작은 것을 찾으면 어떨까 했다.

그런데 집합이 2개보다 더 크게 나눠지는 경우 처리를 할 수가 없어서 포기했다. Union-find으로도 풀 수 있는 방법이 있을 것 같은데.. 애초에 왜 2개로 안 나눠졌던 건지 모르겠다.

다음으로는 간선을 하나씩 제거해서 그래프를 만들어 보면서 dfs를 돌아서 각 개수 차의 최소를 구했다. dfs를 돌면서 노드의 수를 어떻게 세야할지 모르겠어서 좀 헤맸는데 전역 변수를 써서 해결했다.

cnt = 1
​
def dfs(graph, visited, start):
    global cnt
    for next_node in graph[start]:
        if not visited[next_node]:
            visited[next_node] = True
            cnt += 1
            dfs(graph, visited, next_node)
    
​
def solution(n, wires):
    global cnt
    answer = float('inf')
    
    
    for w in wires:
        tmp = wires[:]
        tmp.remove(w)
        graph = [[] for _ in range(n + 1)]
        
        for a, b in tmp:
            graph[a].append(b)
            graph[b].append(a)
        
        visited = [False] * (n + 1)
        networks = []
        for node in range(1, n + 1):
            if not visited[node]:
                visited[node] = True
                cnt = 1
                dfs(graph, visited, node)
                networks.append(cnt)
        
        answer = min(answer, abs(networks[0] - networks[1]))
        
    return answer

다른 사람 풀이

다른 사람 풀이 중에 union-find로 푼 풀이

uf에 root 노드에는 자식 노드의 개수가 저장되고 자식 노드는 루트 노드의 번호를 가지게 된다.

uf = []
​
def find(a):
    global uf
    if uf[a] < 0: return a
    uf[a] = find(uf[a])
    return uf[a]
​
def merge(a, b):
    global uf
    pa = find(a)
    pb = find(b)
    if pa == pb: return
    uf[pa] += uf[pb]
    uf[pb] = pa
​
def solution(n, wires):
    global uf
    answer = int(1e9)
    k = len(wires)
    for i in range(k):
        uf = [-1 for _ in range(n+1)]
        tmp = [wires[x] for x in range(k) if x != i]
        for a, b in tmp: merge(a, b)
        v = [x for x in uf[1:] if x < 0]
        answer = min(answer, abs(v[0]-v[1]))
​
    return answer

 

+)  문제를 찾았다 !!!!!

이전 코드의 경우 find를 할 때 경로 압축을 하지 않았다. 그리고 부모를 찾을 때 경로 압축을 했더라도 간선이 순서대로 주어지지 않으면 부모 배열을 참조했을 때  바로 이전 부모로 나오기 때문에 부모 배열을 봤을 때는 최소 신장 트리가 2개로 나눠지지 않은 것처럼 보였던 것이다.

 

예) [[1, 4], [2, 5], [5, 1], [6, 3]]
parnets) [0, 1, 1, 3, 1, 2, 3]

따라서 union 연산을 한 후 각 노드에 대하여 find 연산을 하면서 루트 노드를 확인해서 카운트 해준다. 

from collections import Counter
def find(x, p):
    if p[x] != x:
        p[x] = find(p[x], p)
    return p[x]

def union(a, b, p):
    a = find(a, p)
    b = find(b, p)
    
    if a < b:
        p[b] = a
    else:
        p[a] = b
        
def solution(n, wires):
    answer = float('inf')
    
    for w in wires:
        tmp = wires[:]
        tmp.remove(w)
        p = [i for i in range(n + 1)]
        
        for a, b in tmp:
            if find(a, p) == find(b, p):
                continue
            union(a, b, p)
        
        uf = []
        for i in range(1, n + 1):
            uf.append(find(i, p))
            
        uf = Counter(uf)
        v = list(uf.values())
        answer = min(answer, abs( v[0]- v[1]))
    return answer

 

반응형
Comments