Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python 딕셔너리를 활용한 그래프 구현 완벽 가이드

딕셔너리로 그래프 표현하기

Python에서는 딕셔너리(Dictionary)를 활용해 그래프(Graph)를 손쉽게 구현할 수 있습니다. 딕셔너리에서 각 키(key)는 그래프의 정점(vertex)을 나타내고, 값(value)에는 해당 정점과 연결된 정점들의 목록을 저장합니다. 이렇게 구성된 전체 구조는 곧 그래프 G(V, E)의 인접 리스트(Adjacency List)와 같은 형태가 됩니다.

기본 딕셔너리 객체를 사용해도 되지만, 여기서는 collections 모듈의 defaultdict를 사용합니다. defaultdict는 존재하지 않는 키에 접근할 때 기본값을 자동으로 생성해 주는 등 유용한 부가 기능을 제공하며, 하나의 추가 쓰기 가능 인스턴스 변수를 가지고 있습니다.

입력 파일 준비하기

이 예제에서는 정점의 개수, 간선의 개수, 정점 이름 목록, 그리고 간선 정보가 담긴 텍스트 파일을 사용합니다. 무방향 그래프(undirected graph)이므로 각 간선은 (u,v)와 (v,u)처럼 양방향으로 두 번 기록됩니다.

예제에서 사용되는 그래프는 다음과 같습니다.

Python 딕셔너리를 활용한 그래프 구현 완벽 가이드

위 그래프를 표현하는 입력 파일은 아래와 같습니다.

Graph_Input.txt

6
8
A|B|C|D|E|F
A,B
B,A
A,C
C,A
B,D
D,B
B,E
E,B
C,E
E,C
D,E
E,D
D,F
F,D
E,F
F,E

파일 처리 순서는 다음과 같습니다. 먼저 정점 이름들을 읽어 각 정점마다 빈 리스트를 생성하고, 이후 간선 정보를 한 줄씩 읽으면서 연결 관계를 리스트에 채워 넣습니다.

그래프 생성 코드

from collections import defaultdict

def create_graph(filename):
    # 키(정점)와 대응하는 리스트(연결 정점)로 구성된 딕셔너리 생성
    graph = defaultdict(list)
    with open(filename, 'r') as graph_file:
        vertex = int(graph_file.readline())      # 정점 개수
        edges = int(graph_file.readline())       # 간선 개수
        vert_names = graph_file.readline()
        vert_names = vert_names.rstrip('\n')     # 줄바꿈 문자 제거
        nodes = vert_names.split('|')            # 정점 이름 분리
        for node in nodes:                       # 정점별 빈 리스트 생성
            graph[node] = []
        # 파일에서 간선을 읽어 리스트 채우기
        for line in graph_file:
            line = line.rstrip('\n')             # 줄바꿈 문자 제거
            edge = line.split(',')
            graph[edge[0]].append(edge[1])       # edge[0]: 출발, edge[1]: 도착
    return graph

my_graph = create_graph('Graph_Input.txt')
for node in my_graph.keys():                     # 그래프 출력
    print(node + ': ' + str(my_graph[node]))

실행 결과

A: ['B', 'C']
B: ['A', 'D', 'E']
C: ['A', 'E']
D: ['B', 'E', 'F']
E: ['B', 'C', 'D', 'F']
F: ['D', 'E']

이제 생성된 그래프 G(V, E)에 대해 몇 가지 기본 연산을 살펴보겠습니다. 먼저 출발 정점에서 도착 정점까지의 경로(path)를 구하는 방법입니다. 아래 코드를 실행하려면 앞서 소개한 방법으로 그래프를 먼저 생성해야 합니다.

특정 경로 찾기

# 출발점에서 도착점까지의 경로를 찾는 함수
def get_path(graph, src, dest, path=[]):
    path = path + [src]
    if src == dest:              # 도착점을 찾으면 종료
        return path
    for vertex in graph[src]:
        if vertex not in path:
            new_path = get_path(graph, vertex, dest, path)
            if new_path:
                return new_path
    return None

my_graph = create_graph('Graph_Input.txt')
path = get_path(my_graph, 'A', 'C')
print('노드 A에서 C까지의 경로: ' + str(path))

실행 결과

노드 A에서 C까지의 경로: ['A', 'B', 'D', 'E', 'C']

모든 경로 찾기

다음은 출발 정점에서 도착 정점까지 가능한 모든 경로를 구하는 방법입니다. 마찬가지로 실행 전에 그래프를 먼저 생성해야 합니다.

# 출발점에서 도착점까지의 모든 경로를 찾는 함수
def get_all_path(graph, src, dest, path=[]):
    path = path + [src]
    if src == dest:              # 도착점을 찾으면 종료
        return [path]
    paths = []
    for vertex in graph[src]:
        if vertex not in path:
            new_paths = get_all_path(graph, vertex, dest, path)
            for new_path in new_paths:
                paths.append(new_path)
    return paths

my_graph = create_graph('Graph_Input.txt')
paths = get_all_path(my_graph, 'A', 'C')
print('노드 A에서 C까지의 모든 경로:')
for path in paths:
    print(path)

실행 결과

노드 A에서 C까지의 모든 경로:
['A', 'B', 'D', 'E', 'C']
['A', 'B', 'D', 'E', 'C']
['A', 'B', 'D', 'F', 'E', 'C']
['A', 'B', 'D', 'F', 'E', 'C']
['A', 'B', 'D', 'F', 'E', 'C']
['A', 'B', 'E', 'C']
['A', 'C']

최단 경로 찾기

마지막으로 출발 정점에서 도착 정점까지의 최단 경로(shortest path)를 구하는 방법입니다. 이 코드 역시 실행 전에 앞서 설명한 방법으로 그래프를 생성해야 합니다.

# 출발점에서 도착점까지의 최단 경로를 찾는 함수
def get_shortest_path(graph, src, dest, path=[]):
    path = path + [src]
    if src == dest:              # 도착점을 찾으면 종료
        return path
    shortest = None
    for vertex in graph[src]:
        if vertex not in path:
            new_path = get_shortest_path(graph, vertex, dest, path)
            if new_path:
                if not shortest or len(new_path) < len(shortest):
                    shortest = new_path
    return shortest

my_graph = create_graph('Graph_Input.txt')
path = get_shortest_path(my_graph, 'A', 'C')
print('노드 A에서 C까지의 최단 경로: ' + str(path))

실행 결과

노드 A에서 C까지의 최단 경로: ['A', 'C']