그래프(graph)
연결되어 있는 객체 간의 관계를 표현할 수 있는 자료 구조
그래프는 정점과 간선의 집합으로 구성되며, 정점은 노드라고도 불리고 간선은 링크라고 불린다.
그래프 표현
인접 행렬(Adjacenecy Marerix)
인접 행렬은 그래프를 2차원 배열로 표현한 것
장점
- 두 정점을 연결하는 간선의 존재 여부를 O(1) 시간 안에 알 수 있다.
단점
- 모든 정점에 대해 간선 정보를 대입해야 하므로 O(n²)의 시간복잡도가 소요된다.
인접 리스트(Adjacenecy List)
인접 리스트는 그래프를 표현함에 있어 각각의 정점에 인접한 정점들을 연결 리스트로 표시한 것
장점
- 정점들의 연결 정보를 탐색할 때 O(n)시간이면 가능 (n: 간선의 갯수)
- 공간의 낭비가 적음
단점
- 두 점의 연결 확인 시 인접 행렬에 비해 시간이 오래걸림
그래프 순회
그래프 순회란 그래프 탐색이라고도 불리며 그래프의 각 정점을 방문하는 과정을 말한다.
깊이 우선 탐색(Depth First Search : DFS)
갈 수 있는 만큼 최대한 깊이 가고, 더 이상 갈 곳이 없다면 이전 정점으로 돌아가는 방식
주로 스택이나 재귀, 백트래킹으로 구현
재귀 구조로 구현
graph = {
1: [2, 3, 4],
2: [5],
3: [5],
4: [],
5: [6, 7],
6: [],
7: [3],
}
def recursive_dfs(v, discovered=[]):
discovered.append(v)
for w in graph[v]:
if not w in discovered:
discovered = recursive_dfs(w, discovered)
return discovered
# recursive dfs: [1, 2, 5, 6, 7, 3, 4]
스택을 이용한 반복 구조로 구현
graph = {
1: [2, 3, 4],
2: [5],
3: [5],
4: [],
5: [6, 7],
6: [],
7: [3],
}
def iterative_dfs(start_v):
discovered = []
stack = [start_v]
while stack:
v = stack.pop()
if v not in discovered:
discovered.append(v)
for w in graph[v]:
stack.append(w)
return discovered
# iterative dfs: [1, 4, 3, 5, 7, 6, 2]
넓이 우선 탐색(Bredth Frist Search : BFS)
시작 정점을 방문한 후 시작 정점에 인접한 모든 정점을 방문한 후 시작 정좀에 인접한 모든 정점들을 우선 방문하는 방식
주로 큐를 사용해 구현
큐를 이용한 반복 구조로 구현
graph = {
1: [2, 3, 4],
2: [5],
3: [5],
4: [],
5: [6, 7],
6: [],
7: [3],
}
def iterative_bfs(start_v):
discovered = [start_v]
queue = [start_v]
while queue:
v = queue.pop(0)
for w in graph[v]:
if w not in discovered:
discovered.append(w)
queue.append(w)
return discovered
# iterative bfs: [1, 2, 3, 4, 5, 6, 7]
백트래킹(Backtracking)
해결책에 대한 후보를 구축해 나아가다 가능성이 없다고 판단되는 즉시 후보를 포시(백트랙; Backtrack)해 정답을 찾아가는 범용적인 알고리즘으로 제약 충족 문제에 특히 유용하다.
주로 재귀로 구현
제약 충족 문제
제약 충족 문제란 수많은 제약 조건을 충족하는 상태를 찾아내는 수학 문제를 일컫는다.