본문 바로가기

파이썬 스터디

6. 그래프, 그래프 탐색

- 그래프

- DFS

- BFS

 

정점(vertex):그래프를 구성하는 개체(점)=노드

간선(edge): 정점과 정점을 연결하는 선

그래프(gragh): 정점과 간선으로 구성된 자료구조

방향그래프(directed graph): 간선에 방향이 있는 그래프   cf. 양방향 그래프(undirected graph). 그냥 그래프와 같다.

차수(degree): 정점에 연결된 간선의 수

방향 그래프에서는 정점으로 들어오는 간선과 나가는 간선을 indgree, outdegree로 구분.

경로(path): 정점을 중복해서 지나지 않는 간선의 나열

사이클: 시작 정점과 끝 정점이 같은 경로

방향 그래프에서 사이클을 판단할 때는 방향을 고려해야한다.

순환 그래프(cyclic graph)

비순환 그래프(acyclic graph)

 

그래프는 두 정점 사이의 관계집합이라 할 수 있다.

그래프를 표현할 때는 두 정점 사이의 관계를 저장하자.

 

그래프의 표현에는 2가지가 있다.

인접 행렬

인접 리스트

 


인접 행렬 vs 인접 리스트
두 정점의 연결 확인                  O(1)   O(degree(V))
한 정점과 연결된 정점 확인      O(V)    O(degree(V))
필요한 메모리 크기 (공간 복잡도)  O(V²)     O(V+E)

 

보통은 인접리스트를 많이 사용한다.

 

https://www.acmicpc.net/problem/1260

인접 행렬과 인접 리스트로 나타내보기

 

5 4 1
1 2
1 3
3 4
4 5

#인접 행렬
n, m, start = map(int, input().split())
graph = [[0] * (n+1) for _ in range(n+1)]

for _ in range(m):
    a, b = map(int, input().split())
    graph[a][b] = 1
    graph[b][a] = 1

for i in range(1, n+1):
    print(*graph[i][1:])

0 1 1 0 0
1 0 0 0 0
1 0 0 1 0
0 0 1 0 1
0 0 0 1 0

#인접 그래프
n, m, start = map(int, input().split())
graph = [[] for _ in range(n+1)]

for _ in range(m):
    a, b = map(int, input().split())
    graph[a].append(b)
    graph[b].append(a)

for i in range(1, n + 1):
    print(*graph[i])

2 3
1
1 4
3 5
4

 

그럼 그래프를 탐색해보자.

dfs(depth first search) 깊이 우선 탐색

n = 7  # 노드 개수
graph = [[] for _ in range(n+1)]

graph[1] = [2, 3]
graph[2] = [1, 4, 5]
graph[3] = [1, 6, 7]
graph[4] = [2]
graph[5] = [2]
graph[6] = [3]
graph[7] = [3]

visit = [False] * (n+1)  # 방문 여부 리스트

def dfs(node):
    print(node, end=' ')
    for conn_node in graph[node]:
        if not visit[conn_node]:
            visit[conn_node] = True
            dfs(conn_node)

visit[2] = True
dfs(2)

 

bfs(breadth first search) 너비 우선 탐색

큐를 이용해 구현한다.

def bfs(node):
    d.append(node)#시작노드
    visit[node] = True  # 큐에 노드를 추가할 때 방문 체크
    while d:#큐에 값이 있을 때까지 돈다.
        now_node = d.popleft()#큐에서 꺼내고 탐색
        print(now_node)
        for conn_node in graph[now_node]:#인접노드 가져옴
            if not visit[conn_node]:
                d.append(conn_node)
                visit[conn_node] = True  # 큐에 노드를 추가할 때 방문 체크

 

정답코드

n, m, start = map(int, input().split())
graph = [[] for _ in range(n+1)]
for _ in range(m):
    a, b = map(int, input().split())
    graph[a].append(b)
    graph[b].append(a)

for i in range(n+1):
    graph[i].sort()#순서대로 방문

visit = [False] * (n+1)
visit[start] = True
dfs(start)
print()

visit = [False] * (n+1)
bfs(start)

 

https://www.acmicpc.net/problem/2667

이차원 배열을 그래프라고 생각. 각각의 칸이 정점

이차원 배열을 돌며 아직 방문하지 않은 1을 만나면 새로운 단지로 추가

n = int(input())
graph = [list(map(int, list(input().rstrip()))) for _ in range(n)]
visit = [[False for _ in range(n)] for _ in range(n)]
count = []

for i in range(n):
    for j in range(n):
        if graph[i][j] == 1 and not visit[i][j]:
            visit[i][j] = True
            d = deque()
            d.append((i, j))
            now_count = 1#현재 단지 크기

            # BFS
            while d:
                now_i, now_j = d.popleft()

                next_i, next_j = now_i + 1, now_j  # 아래 방향
                if 0 <= next_i < n and 0 <= next_j < n:#범위체크
                    if graph[next_i][next_j] == 1 and not visit[next_i][next_j]:
                        visit[next_i][next_j] = True
                        d.append((next_i, next_j))
                        now_count += 1

                next_i, next_j = now_i - 1, now_j  # 위 방향
                if 0 <= next_i < n and 0 <= next_j < n:
                    if graph[next_i][next_j] == 1 and not visit[next_i][next_j]:
                        visit[next_i][next_j] = True
                        d.append((next_i, next_j))
                        now_count += 1

                next_i, next_j = now_i, now_j + 1  # 오른쪽 방향
                if 0 <= next_i < n and 0 <= next_j < n:
                    if graph[next_i][next_j] == 1 and not visit[next_i][next_j]:
                        visit[next_i][next_j] = True
                        d.append((next_i, next_j))
                        now_count += 1

                next_i, next_j = now_i, now_j - 1  # 왼쪽 방향
                if 0 <= next_i < n and 0 <= next_j < n:
                    if graph[next_i][next_j] == 1 and not visit[next_i][next_j]:
                        visit[next_i][next_j] = True
                        d.append((next_i, next_j))
                        now_count += 1

            count.append(now_count)

print(len(count))
for c in sorted(count):
    print(c)

 

 

중복코드 개선

(1, 0) (-1, 0) (0, 1) (0, -1) 쌍을 미리 만들어 두고 반복문을 돌리자.

from collections import deque
import sys
input = sys.stdin.readline

di = [1, -1, 0, 0]
dj = [0, 0, 1, -1]

n = int(input())
graph = [list(map(int, list(input().rstrip()))) for _ in range(n)]
visit = [[False for _ in range(n)] for _ in range(n)]
count = []

for i in range(n):
    for j in range(n):
        if graph[i][j] == 1 and not visit[i][j]:
            visit[i][j] = True
            d = deque()
            d.append((i, j))
            now_count = 1

            # BFS
            while d:
                now_i, now_j = d.popleft()

                for k in range(4):
                    next_i, next_j = now_i + di[k], now_j + dj[k]
                    if 0 <= next_i < n and 0 <= next_j < n:
                        if graph[next_i][next_j] == 1 and not visit[next_i][next_j]:
                            visit[next_i][next_j] = True
                            d.append((next_i, next_j))
                            now_count += 1

            count.append(now_count)

print(len(count))
for c in sorted(count):
    print(c)

 

 

https://www.acmicpc.net/problem/7576

자연스럽게 BFS 떠올릴 수 있다.

모든 익은 토마토를 큐에 담아두고 BFS 알고리즘을 적용

d = deque()

d.append((i, j, 0))  # 0은 현재 날짜

# BFS
while d:
    now_i, now_j, now_date = d.popleft()

    for k in range(4):
        next_i, next_j = now_i + di[k], now_j + dj[k]
        if 0 <= next_i < n and 0 <= next_j < n:
            if graph[next_i][next_j] == 0:
                graph[next_i][next_j] = 1
                d.append((next_i, next_j, now_date + 1))

'파이썬 스터디' 카테고리의 다른 글

5. Dynamic Porgramming 동적계획법  (0) 2025.02.03
객체지향 언어 파이썬  (3) 2024.11.15
파이썬 중급 문법  (0) 2024.11.13
파이썬 메소드  (0) 2024.11.02
세트와 딕셔너리  (6) 2024.09.08