Python/파이썬 자료구조 알고리듬
23. 그래프(Graph) - 깊이 우선 검색(depth first search)
컴닥
2019. 6. 17. 18:26
반응형
1. 재귀
방문한 노드는 마크합니다.
따라서 마크용 셋을 미리 만들어야 합니다.
마크용으론 리스트보다 set이 좋은 경우가 많은데
set은 해시를 이용해서 검색 시 속도가 빠르기 때문입니다. https://wiki.python.org/moin/TimeComplexity
재귀적이지만 비교적 단순한 알고리듬입니다.
스택으로도 만들 수 있습니다.
for 문 내부에 재귀가 있습니다.
그렇기 때문에 하나의 노드에서 재귀로 깊이 우선 검색을 먼저 하게 되고,
for 문으로 다음 노드로 넘어가서 또 깊이 우선 검색을 하게 되는 반복 구조입니다.
class Graph:
def __init__(self):
self.graph = dict() # {}
self.marked = set()
def add(self, v, w):
self.graph.setdefault(v, {w})
self.graph[v].add(w)
self.graph.setdefault(w, {v})
self.graph[w].add(v)
def show(self):
for node in self.graph:
print(node, '->', self.graph[node])
def init_marked(self):
self.marked.clear()
def dfs(self, v):
self.marked.add(v)
print('Visited vertex:', v)
for w in self.graph[v]:
if w not in self.marked:
self.dfs(w)
g = Graph()
g.add(0, 1)
g.add(0, 3)
g.add(1, 2)
g.add(3, 4)
g.add(1, 3)
g.show()
print('--')
g.dfs(0)
g.init_marked()
print('--')
g.dfs(1)
0 -> {1, 3}
1 -> {0, 2, 3}
3 -> {0, 1, 4}
2 -> {1}
4 -> {3}
--
Visited vertex: 0
Visited vertex: 1
Visited vertex: 2
Visited vertex: 3
Visited vertex: 4
--
Visited vertex: 1
Visited vertex: 0
Visited vertex: 3
Visited vertex: 4
Visited vertex: 2
2. 스택
스택으로 코딩해 봅니다.
class Graph:
def __init__(self):
self.graph = {}
def add(self, v, w):
self.graph.setdefault(v, {w})
self.graph[v].add(w)
self.graph.setdefault(w, {v})
self.graph[w].add(v)
def show(self):
for node in self.graph:
print(node, '->', self.graph[node])
def dfs(self, start):
stack = [start]
marked = {start}
while stack:
v = stack.pop()
print('Visited vertex:', v)
for w in self.graph[v]:
if w not in marked:
stack.append(w)
marked.add(w)
재귀가 아닌 스택으로 작성되었기 때문에 marked 리스트를 메서드 내부에 넣을 수 있습니다.
marked 리스트를 메서드 내부에 넣으니 self. 도 안 써도 되고, marked를 별도로 clear 하는 부분도 필요 없어졌습니다.
깔끔해졌네요. ^^
g = Graph()
g.add(0, 1)
g.add(0, 3)
g.add(1, 2)
g.add(3, 4)
g.add(1, 3)
g.show()
print('--')
g.dfs(0)
print('--')
g.dfs(1)
0 -> {1, 3}
1 -> {0, 2, 3}
3 -> {0, 1, 4}
2 -> {1}
4 -> {3}
--
Visited vertex: 0
Visited vertex: 3
Visited vertex: 4
Visited vertex: 1
Visited vertex: 2
--
Visited vertex: 1
Visited vertex: 3
Visited vertex: 4
Visited vertex: 2
Visited vertex: 0
두 예의 결괏값은 다릅니다.
이 차이는 오른쪽(?) 왼쪽(?) 노드 중 뭘 먼저 탐색했냐의 차이일 뿐이고 둘 다 정답이 맞습니다.
* 스택 편에서 말씀드렸지만... list를 스택으로 사용하는 것은 비효율적입니다.
3. for 문 제거
class Graph:
def __init__(self):
self.graph = {}
def add(self, v, w):
self.graph.setdefault(v, {w})
self.graph[v].add(w)
self.graph.setdefault(w, {v})
self.graph[w].add(v)
def show(self):
for node in self.graph:
print(node, '->', self.graph[node])
def dfs(self, start):
stack = [start]
marked = set() # marked를 비운 상태로 시작합니다.
while stack:
v = stack.pop()
if v not in marked:
print('Visited vertex:', v)
marked.add(v)
stack.extend(self.graph[v].difference(marked))
g = Graph()
g.add(0, 1)
g.add(0, 3)
g.add(1, 2)
g.add(3, 4)
g.add(1, 3)
g.show()
print('--')
g.dfs(0)
print('--')
g.dfs(1)
파이썬의 반복문은 빠르지 않기 때문에..
이렇게 작성하면 조금 더 빠를 수 있습니다.
파이썬은 C언어로 작성되어 있고,
C언어로 작성된 부분들을 이용하면
속도 향상에 도움이 됩니다.
0 -> {1, 3}
1 -> {0, 2, 3}
3 -> {0, 1, 4}
2 -> {1}
4 -> {3}
--
Visited vertex: 0
Visited vertex: 3
Visited vertex: 4
Visited vertex: 1
Visited vertex: 2
--
Visited vertex: 1
Visited vertex: 3
Visited vertex: 4
Visited vertex: 0
Visited vertex: 2
반응형