🍋 ⚾️ 💻 🎬 🎮

coding_test/항해99 코테스터디 TIL

99클럽 코테 스터디 _ 8일차 TIL (BFS, DFS, Union-Find, Flood Fill)

aeightchill 2025. 1. 22. 16:04
728x90

 

< 문제 >

[백준] 2667. 단지번호붙이기 / 실버1


< 풀이 >

코드 변수 정의

  • graph : 아파트(1)가 표시된 지도
  • cnt : 현재 단지 내 아파트 수
  • dx, dy : 상하좌우를 탐색할 변수. 상(-1, 0), 하(1,0), 좌(0,-1), 우(0,1)
  • nx, ny : 현재 위치에서 상하좌우로 이동했을 때의 좌표
  • apartment : 각 단지의 아파트 수를 저장할 리스트

BFS
시간복잡도 : O(N^2)
from collections import deque

# BFS를 이용하여 연결된 아파트 단지의 크기를 계산하는 함수
def bfs(graph, i, j, visited):
    q = deque([(i, j)]) # 큐에 시작 좌표(i, j)를 추가
    cnt = 1 # 현재 단지 내 아파트 수 (초기값=1, 시작점 포함)
    while q:
        x, y = q.popleft()
        visited[x][y] = True # 현재 위치 방문 처리
        # 현재 위치에서 상하좌우 탐색
        for dx, dy in [(-1,0), (0,1), (1,0), (0,-1)]:
            nx, ny = x + dx, y + dy
            # 새로운 위치가 유효한 범위 내에 있고, 방문하지 않았으며 아파트가 있는 경우
            if 0 <= nx < N and 0 <= ny < N and not visited[nx][ny] and graph[nx][ny] == '1':
                visited[nx][ny] = True # 방문 처리
                q.append((nx, ny)) # 큐에 새로운 위치 추가
                cnt += 1 # 단지 내 아파트 수 + 1
    return cnt # 같은 단지 내 아파트 수 반환
                
def f():
    global N # 전역변수 N (그래프의 크기)
    N = int(input())
    graph = [input() for _ in range(N)]
    
    visited = [[False] * N for _ in range(N)] # 방문 여부 확인 배열
    apartment = [] # 각 단지의 아파트 수를 저장할 리스트
    for i in range(N):
        for j in range(N):
        	# 방문하지 않은 아파트를 단지 내 아파트 탐색 시작점으로
            if graph[i][j] == '1' and not visited[i][j]:
                apartment.append(bfs(graph, i, j, visited))
    apartment.sort() # 단지 크기 오름차순 정렬
    print(len(apartment)) # 단지 총 개수 출력
    print(*apartment, sep='\n') # 각 단지의 크기를 줄바꿈으로 출력

f()

 

풀이 방식

  1. 그래프 탐색
    • 2중 for문으로 그래프의 모든 위치를 순회한다.
    • 현재 위치가 아파트('1')면서, 방문하지 않은 경우  →  BFS를 수행하여 단지를 탐색한다.
  2. BFS로 단지 탐색
    • BFS 시작 위치 : (i, j)
    • 시작 위치를 기준으로 연결된 모든 '1'을 탐색한다.
    • 작동 방식
      • 시작 위치를 큐에 추가하고, 방문 처리(visited=True)한다.
      • 큐에서 현재 위치를 꺼내고, 상하좌우로 인접한 좌표를 확인한다.
      • 인접한 좌표가 그래프 내에 있으며 방문하지 않은 아파트('1')인 경우 큐에 추가하고 방문 처리한다.
      • 탐색이 끝날 때까지 연결된 모든 '1'을 확인하고, 단지의 크기(cnt)를 계산한다.
  3. 단지 크기 저장
    • BFS 함수가 반환한 단지 크기를 apartment 리스트에 추가한다.
    • apartment를 오름차순으로 정렬한다.

< 회고 >

🤔 💭_

오름차순 정렬(문제 조건 잘 읽기..)

인접한 아파트를 탐색하는 문제여서 bfs로 구현했다.


BFS 말고 다른 풀이 방식이 있을까?

 

DFS(깊이 우선 탐색)

-   재귀 호출로 연결된 모든 노드를 탐색
def dfs(graph, x, y, visited):
    visited[x][y] = True
    cnt = 1  # 현재 위치 포함
    for dx, dy in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
        nx, ny = x + dx, y + dy
        if 0 <= nx < N and 0 <= ny < N and not visited[nx][ny] and graph[nx][ny] == '1':
            cnt += dfs(graph, nx, ny, visited)  # 연결된 노드 탐색
    return cnt

def f():
    global N
    N = int(input())
    graph = [input() for _ in range(N)]
    visited = [[False] * N for _ in range(N)]
    apartment = []

    for i in range(N):
        for j in range(N):
            if graph[i][j] == '1' and not visited[i][j]:
                apartment.append(dfs(graph, i, j, visited))

    apartment.sort()
    print(len(apartment))
    print(*apartment, sep='\n')

f()
  • 재귀 호출로 인한 스택 사용량 증가
  • python 기본 재귀 깊이 제한에 주의 (sys.setrecursionlimit)

Union-Find (Disjoint Set Union)

-   그래프의 각 노드를 하나의 집합으로 간주하고, 연결된 노드를 하나의 집합으로 합친다.
-   모든 노드를 순회하며 집합을 합친 후, 각 집합의 크기를 계산한다.
# 부모 노드를 찾는 함수 (경로 압축)
def find(parent, x):
	# x의 부모가 자기 자신이 아니라면, 재귀적으로 루트 노드를 찾음.
    if parent[x] != x:
        parent[x] = find(parent, parent[x]) # 루트 노드로 바로 연결
    return parent[x]

# 두 집합을 병합하는 함수 (Union by Rank)
def union(parent, rank, a, b):
	# 두 노드 a, b의 루트 노드를 찾음.
    rootA = find(parent, a)
    rootB = find(parent, b)
    if rootA != rootB: # 두 노드가 속한 집합이 다르면 합침.
    	# rank가 더 큰 쪽이 부모가 됨.
        if rank[rootA] > rank[rootB]:
            parent[rootB] = rootA
        elif rank[rootA] < rank[rootB]:
            parent[rootA] = rootB
        else: # rank가 같다면 한쪽을 부모로 설정하고 rank + 1
            parent[rootB] = rootA
            rank[rootA] += 1

def f():
    N = int(input())
    graph = [input() for _ in range(N)]
	
    # union-find 구조 초기화
    parent = {} # 각 노드의 부모 노드
    rank = {} # 각 노드의 rank (트리 높이)
    node_mapping = {} # 좌표를 노드 번호로 매핑
    idx = 0 # 노드 번호

    # 그래프의 각 좌표에 대해 노드 번호를 부여하고 초기화
    for i in range(N):
        for j in range(N):
            if graph[i][j] == '1': # 아파트인 경우
                node_mapping[(i, j)] = idx # 좌표를 노드 번호로 매핑
                parent[idx] = idx # 초기에는 자기 자신이 부모
                rank[idx] = 0 # 초기 rank=0
                idx += 1

    # Union 연산
    for (x, y), id1 in node_mapping.items():
        for dx, dy in [(-1, 0), (0, 1), (1, 0), (0, -1)]: # 상하좌우 탐색
            nx, ny = x + dx, y + dy
            if (nx, ny) in node_mapping: # 인접 노드가 아파트인 경우
                id2 = node_mapping[(nx, ny)]
                union(parent, rank, id1, id2) # 두 노드 병합

    # 모든 노드의 루트 노드를 찾고, 루트별 노드 수 계산(counter 사용)
    from collections import Counter
    root_count = Counter(find(parent, node) for node in parent)

    # 결과 출력
    sizes = sorted(root_count.values())
    print(len(sizes))
    print(*sizes, sep='\n')

f()
  • 추가로 부모 및 순위 정보 저장을 위한 메모리 필요
  • 연결 관계를 효율적으로 관리하는 데 적합하지만, 구현이 더 복잡

Flood Fill

-   DFS와 유사하지만, 방문한 위치를 특정 값으로 변경하여 추가 배열을 사용하지 않는다.
-   그래프를 직접 수정하며 탐색하므로 메모리를 절약할 수 있다.
def flood_fill(graph, x, y):
    stack = [(x, y)]
    size = 0
    while stack:
        cx, cy = stack.pop()
        if graph[cx][cy] == '1':  # 방문 확인
            graph[cx][cy] = '0'  # 방문 처리
            size += 1
            for dx, dy in [(-1, 0), (0, 1), (1, 0), (0, -1)]: # 상하좌우 탐색
                nx, ny = cx + dx, cy + dy
                if 0 <= nx < N and 0 <= ny < N and graph[nx][ny] == '1':
                    stack.append((nx, ny))
    return size

def f():
    global N
    N = int(input())
    graph = [list(input()) for _ in range(N)]  # 문자열을 리스트로 변환
    apartment = []

    for i in range(N):
        for j in range(N):
            if graph[i][j] == '1':
                apartment.append(flood_fill(graph, i, j))

    apartment.sort()
    print(len(apartment))
    print(*apartment, sep='\n')

f()
  • visited 배열 대신 그래프를 직접 수정하여 메모리 절약

방식 비교

방식 시간복잡도 공간복잡도 특징
BFS O(N^2) O(N^2) 구현 간단
DFS O(N^2) O(N^2) 재귀 호출
Union-Find O(N^2 * ⍺(N^2)) O(N^2) 연결 관계 관리에 효율적
Flood Fill O(N^2) O(N^2) 그래프 수정

 

728x90