[프로그래머스][LV.2] 전력망을 둘로 나누기 | python3

2025. 3. 16. 13:09프로그래머스/LV.2

문제링크:  전력망을 둘로 나누기


문제설명

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

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

 

제한조건

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


 

문제풀이

와 어려웠다. 다른 풀이를 참고해가며 이해하는데도 오래걸렸다. 

bfs로 푼 풀이를 참고했다.

 

더보기

BFS (Breadth-First Search, 너비 우선 탐색)

1. BFS란?

BFS는 그래프 탐색 알고리즘 중 하나로, 가까운 노드부터 탐색하는 방식이다. 즉, 특정 노드를 방문한 후, 그 노드와 직접 연결된 모든 노드를 먼저 방문한 다음, 점점 더 먼 노드를 탐색한다.

즉, BFS는 "넓게 탐색하는 방식"의 알고리즘이다.


2. BFS의 동작 원리

BFS는 큐(Queue) 자료구조를 이용하여 탐색을 진행한다.

BFS 탐색 과정

  1. 탐색을 시작할 노드를 큐에 넣고 방문 처리한다.
  2. 큐에서 노드를 하나 꺼내고, 그 노드와 연결된 방문하지 않은 노드들을 큐에 추가한다.
  3. 위 과정을 큐가 빌 때까지 반복하면 모든 연결된 노드를 탐색할 수 있다.

3. BFS 예제

예제 그래프

   1
  / \
 2   3
 |   |
 4   5

이 그래프는 다음과 같이 연결되어 있다:

  • 1번 노드는 2번, 3번과 연결됨
  • 2번 노드는 1번, 4번과 연결됨
  • 3번 노드는 1번, 5번과 연결됨
  • 4번 노드는 2번과 연결됨
  • 5번 노드는 3번과 연결됨

BFS 탐색 과정 (시작 노드 = 1)

  1. 시작 노드 1을 큐에 넣고 방문 처리
    큐: [1], 방문: {1}
  2. 큐에서 1을 꺼내고, 1과 연결된 2, 3을 큐에 추가
    큐: [2, 3], 방문: {1, 2, 3}
  3. 큐에서 2를 꺼내고, 2와 연결된 4를 큐에 추가
    큐: [3, 4], 방문: {1, 2, 3, 4}
  4. 큐에서 3을 꺼내고, 3과 연결된 5를 큐에 추가
    큐: [4, 5], 방문: {1, 2, 3, 4, 5}
  5. 큐에서 4를 꺼내고, 새로운 노드가 없으므로 그대로 유지
    큐: [5], 방문: {1, 2, 3, 4, 5}
  6. 큐에서 5를 꺼내고, 탐색 종료
    큐: [], 방문: {1, 2, 3, 4, 5}

최종 탐색 순서: 1 → 2 → 3 → 4 → 5


4. BFS 코드 구현 (파이썬)

from collections import deque

def bfs(graph, start):
    visited = set()  # 방문한 노드를 저장할 집합
    queue = deque([start])  # 큐 초기화 (시작 노드 삽입)
    visited.add(start)  # 시작 노드 방문 처리

    while queue:  # 큐가 빌 때까지 반복
        v = queue.popleft()  # 큐에서 노드를 꺼냄
        print(v, end=" ")  # 방문한 노드 출력

        for neighbor in graph[v]:  # 현재 노드와 연결된 모든 노드 탐색
            if neighbor not in visited:  # 방문하지 않은 노드라면
                queue.append(neighbor)  # 큐에 추가
                visited.add(neighbor)  # 방문 처리

# 예제 그래프 (인접 리스트 표현)
graph = {
    1: [2, 3],
    2: [1, 4],
    3: [1, 5],
    4: [2],
    5: [3]
}

# BFS 실행 (1번 노드부터 시작)
bfs(graph, 1)

출력 결과

1 2 3 4 5

가까운 노드부터 차례대로 탐색하는 것을 확인할 수 있다.


5. BFS vs DFS 비교

                         BFS (너비 우선 탐색)                                          DFS (깊이 우선 탐색)
탐색 방식 가까운 노드부터 탐색 한 방향으로 끝까지 탐색 후 백트래킹
자료구조 큐(Queue) 스택(Stack) 또는 재귀(Recursion)
탐색 순서 넓게 탐색 깊게 탐색
활용 예시 최단 거리 탐색, 네트워크 연결 찾기 백트래킹(예: 미로 찾기), 순열/조합 문제

6. BFS의 활용 예시

  1. 최단 경로 찾기
    • 예: 미로에서 출구까지 가장 빠른 길 찾기
    • BFS는 가장 먼저 도착한 경로가 최단 경로이므로, 최단 거리 문제에 적합함.
  2. 네트워크 연결 탐색
    • 예: 전력망 연결된 송전탑 찾기
    • 어떤 노드가 다른 노드와 연결되어 있는지를 찾을 때 유용함.
  3. 웹 크롤링
    • 예: 웹사이트의 링크를 따라가며 정보를 수집할 때
    • 가까운 페이지부터 차례로 방문하는 방식으로 BFS를 활용할 수 있음.
  4. 게임 AI
    • 예: 체스나 보드게임에서 최적의 움직임 찾기
    • BFS를 사용하여 적절한 수를 찾거나, 몬스터의 이동 경로를 계산할 수 있음.

7. 정리

✔ BFS는 가까운 노드부터 탐색하는 방식이며, 큐(Queue)를 사용한다.
최단 경로 찾기 같은 문제에서 효과적이다.
✔ 그래프를 탐색할 때 DFS와 비교하여 선택적으로 사용할 수 있다.

BFS는 그래프에서 최단 경로 탐색 및 연결된 요소를 찾는 데 강력한 알고리즘이다!

wires에서 차례대로 전선을 빼고 뺀 전선을 제외한 나머지 전선을 탐색하여 graph 이중 리스트에 넣는다.

이 이중리스트에서 연결된 값이 있는 원소를 찾자마자 그 값을 리턴한다. (한 묶음 당 하나의 값만 알면 전체 탐색을 할 수 잇음)

연결된 노드에 방문 할때마다 count를 해주고 전부 방문하면 종료한다.

다른 묶음의 갯수는 전체 노드에서 count한 노드를 빼주면 구할 수 있다.

 

나의코드

from collections import deque

def bfs(graph, start,visited):
    queue = deque([start]) # 큐에는 start와 직,간접적으로 연결되어 있는 원소가 들어가게 됨.
    
    visited[start]=True # 현재노드(시작 노드) 방문 처리
    cnt =0
    
    while queue: # 큐가 빌때까지 반복
        v=queue.popleft()
        cnt+=1
        # 해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
        for i in graph[v]: # v와 연결된 값 리스트
            if not visited[i]: # 방문하지 않은 원소 => False 이기 떼문에 앞에 not 처리
                queue.append(i)
                visited[i] = True
    return cnt # start와 직,간접적으로 연결된 노드의 수가 몇개인지
        
        

def solution(n, wires):
    answer = n-2 # 두 전력망이 갖게 되는 송전탑의 개수 차이의 절댓값 중 최댓값
    # 만약 n이 9라면 최대 차이는 1,8 일때로 8-1 = 7이 됨.
    
    for i in range(len(wires)):
        tmps=wires.copy()
        graph=[[] for i in range(n+1)] # 이중리스트
        print(graph)
        visited=[False] * (n+1)
        tmps.pop(i) # i번재 전선 제거 # 전선을 차례대로 제거
        for wire in tmps:
            x,y=wire
            graph[x].append(y) # x번재에 y값
            graph[y].append(x) # y번째에 x값 # 각 인덱스에 있는 리스트에 그 번호와 연결된 다른 값들이 들어있음
        for idx,g in enumerate(graph):
            if g != []: # n개의 송전탑 중 다른 송전탑과 연결된 송전탑을 시작점으로 지정
                start = idx # 연결된 노드가 있는 노드를 start 점으로 잡음
                break
                
        cnts=bfs(graph,start,visited) # bfs를 이용하여 시작점에서 다른 송전탑 탐색
        # 탐색 가능한 송전탑 갯수. => 즉 연결된 송전탑의 갯수
        other_cnts = n-cnts # 나머지 전력량 개수
        
        answer=min(answer,abs(other_cnts-cnts))
        
    return answer

 


 

더보기

참고하기

def solution(n, wires):
    ans = n
    for sub in (wires[i+1:] + wires[:i] for i in range(len(wires))):
        s = set(sub[0]) # 시작 노드 역할
        [s.update(v) for _ in sub for v in sub if set(v) & s] # s에 시작 노드와 연결된 노드 모두 들어감
        ans = min(ans, abs(2 * len(s) - n))
    return ans

집합자료형 사용, 이중 for문으로 모든 경우 탐색

 

 for sub in (wires[i+1:] + wires[:i] for i in range(len(wires))): 

이 코드가 전선을 끊는 역할을 함.

 

 [s.update(v) for _ in sub for v in sub if set(v) & s] 

  • sub 내의 모든 전선을 탐색 (for v in sub)
  • 현재 s에 포함된 노드와 연결된 전선이라면 (if set(v) & s)  # & :: 교집합 연산
    • A & B는 집합 A와 B의 교집합(intersection)이 존재하는지 확인하는 연산
    • set(v) & s의 결과가 비어 있지 않다면 (True) → v의 노드 중 하나 이상이 s에 포함되어 있음
    • set(v) & s의 결과가 비어 있다면 (False) → v의 노드가 s에 포함되지 않음
  • s에 해당 전선의 모든 노드를 추가 (s.update(v))