graph 기초개념 및 구현실습
.
그림, 실습코드 등 학습자료 출처 : https://github.com/ythwork
1. 그래프 개요
- 그래프 G는 두 집합 V,E로 나타낸다.
1) V(G) : 노드 혹은 정점(vertex)의 집합
2) E(G) : 정점간을 잇는 edge의 집합
- 그래프의 특징
1) 자기간선(self-edge)를 가질 수 없다.
다시말해 (v,v) or <v,v>를 가질 수 없다.
2) 두 정점 사이에 같은 에지를 여러개 가질 수 없다.
이런 특징이 있는 그래프를 다중그래프라고 한다.
2. 그래프의 종류
1) 무방향 그래프
2) 방향 그래프
3. adjacent와 incident
4. 경로와 길이
5. cycle과 connected
6. degree(차수) : 정점 v에 부속된 에지의 수
아래의 경우 차수는?
7. in-degree와 out-degree
8. subgraph(부분그래프)와 spanning subgraph (신장부분그래프)
임의의 그래프 G에 대한 부분그래프중에서 모든 정점과 연결된 부분 그래프를 말한다.
9. 그래프 표현
10. Graph traversal(그래프 순회)
그래프 G에서 한 정점 v가 주어졌을때 모든 정점을 중복되지 않게 방문하는 것
1) 너비우선탐색(BFS, Breadth first search)
-
트리에서 큐를 이용한 레벨순서순회가 예시다.
-
정점 v에서 시작하여 인접한 정점을 모두 방문하고 방문한 인접 정점에 대해서 모든 인접한 정점을 방문하는 방식, 큐를 이용함
2) 깊이우선탐색(DFS, Depth first search)
-
트리에서 스택을 이용한 전위,중위, 후위 순회가 예시다.
-
정점 v에서 시작하여 방문하지 않은 정점 중 한 방향으로 쭉 따라간 후 다시 돌아와 방문하지 않은 다른 정점을 따라 다시 쭉 따라간다. 처음 시작한 정점에 돌아오고 더 이상 방문할 정점이 남아있지 않다면 종료
step 1) 3번이 스타트 위치라고 치자, 현재 위치에서 3 스텍 프레임이 쌓인다. top이 3을 가리킴. top이 현재 가리키는 것이 현재 위치라고 할 수 있다.
step 2) 그리고 0번으로 이동한다고 하면 0 스텍프레임이 3스텍 프레임 위로 쌓인다. top이 0을 가리킴
step 3) 그 다음에 1로 이동한다고 하면 1번이 가장 위의 스텍프레임으로 쌓이게 된다. top이 1을 가리킴.
- 파이썬 코드로 구현실습
graph representation을 이용하여 구현할 수 있어야 한다.
from queue1 import Queue
from stack1 import Stack
class GNode:
def __init__(self, vertex=None):
self.vertex=vertex
self.link=None
class Graph:
def __init__(self):
#인접 리스트로 구현
self.adjacency_list=[]
#방문 여부 체크
self.visited=[]
def add_vertex(self, vnum=1):
self.adjacency_list.extend([None for _ in range(vnum)])
self.visited.extend([False for _ in range(vnum)])
def __add_node(self, vertex, node):
cur=self.adjacency_list[vertex]
#아직 에지가 하나도 없다면
if not cur:
self.adjacency_list[vertex]=node
else:
while cur.link:
cur=cur.link
cur.link=node
def insert_edge(self, u, v):
unode=GNode(u)
vnode=GNode(v)
self.__add_node(u, vnode)
self.__add_node(v, unode)
def init_visited(self):
for i in range(len(self.visited)):
self.visited[i]=False
def bfs(self, v):
## step1) 큐를 하나만든다.
## step2) 큐에다가 첫번째 인덱스(시작 정점)를 삽입한다.
## step3) visited라는 array에 전부 false로 채워져 있는데
## visited의 v에 True를 할당한다.
## step4) 큐가 비어있을때까지 while문이 돌것이다.
## step5) 큐에서 v를 하나 받아오는데 이 방식을 큐에서 dequeue하는 방식
## step6) v를 방문했다고 체크 (print문)
## step7) adjacency_list를 u로 할당하여 얻어온다.
## step8) u가 방문하지 않았으면 visited를 True로 바꿔준다.
## step9) u를 u.link로 만들어준다.
q=Queue()
#방문 체크 리스트를 초기화한다
#O(v)
self.init_visited()
#첫번째 정점을 큐에 넣고
#방문 체크
q.enqueue(v)
self.visited[v]=True
# 방문
print(v, end=" ")
while not q.empty():
v=q.dequeue()
#인접 리스트를 얻어온다
u=self.adjacency_list[v]
while u:
#아직 방문하지 않은 노드라면
#큐에 넣고 방문 체크!
if not self.visited[u.vertex]:
q.enqueue(u.vertex)
self.visited[u.vertex]=True
u=u.link
def __dfs_recursion(self, v):
#방문
print(v, end=" ")
#방문 체크
self.visited[v]=True
u=self.adjacency_list[v]
while u:
if not self.visited[u.vertex]:
self.__dfs_recursion(u.vertex)
u=u.link
def dfs(self, v):
self.init_visited()
self.__dfs_recursion(v)
def iter_dfs(self, v):
## iter : 결국에는 while문이나for문 쓰겠다.
## v는 스타팅 포인트
"""
시작 정점으로 돌아가
더 이상 방문할 정점이 없어야 종료
"""
s=Stack()
# 스텍을 이용할 것이기 때문에 스텍을 하나 만듬
self.init_visited()
## 내부적으로 visited라는 함수를 이용해서 visit 배열을 false로 초기화
s.push(v)
## 방문 체크 및 방문
self.visited[v]=True
print(v, end=" ")
## 스텍이 비어있지 않는 이상 계속 반복됨
while not s.empty():
is_moved = False
v = s.peek()
## pop을 한다는 것은 스텍을 날려서 전단계로 돌아간다는
## 의미니까 아직은 그럴 단계는 아니고 그냥 peek로 현재 위치를 받아옴
## v와 연결된 모든 인접 리스트를 받아온다.
u = self.adjacency_list[v]
## u로 받아온것은 adjacency의 single_linked_list로 되어 있다.
while u:
if not self.visited[u.vertex]:
## u가 아직 방문하지 않았다면
s.push(u.vertex)
#방문 체크 및 방문
self.visited[u.vertex]=True
## 다음 노드로 이동했다는 것을 의미함
print(u.vertex, end=" ")
#아직 방문하지 않은 정점을 방문했으므로
is_moved=True
## 방문했다는 의미는 이동을 했다는 의미이므로 is_moved를
## True로 만든다.
break
u = u.link
if not is_moved:
## while을 빠져나왔음에도 불구하고 is_move가 false라는 의미는
## 이동할 곳이 없다는 의미이므로 pop해서 back해주면 된다.
s.pop()
## < visited >
## 배열이고 노드 6개가 [F,F,F,F,F,F]로 되어 있으며 인덱스가 vertex다
## 방문했으면 False가 True로 바뀐다
## < is_moved >
## 노드가 갈 수 있는 곳을 다 갔는가를 체크하는 용도
g=Graph()
g.add_vertex(6)
g.insert_edge(1, 0)
g.insert_edge(0, 3)
g.insert_edge(3, 4)
g.insert_edge(4, 2)
g.insert_edge(2, 5)
- BFS
#예상 출력 결과 : 3 0 4 1 2 5
g.bfs(3)
3 0 4 1 2 5
- DFS
#예상 출력 결과 : 3 0 1 4 2 5
g.dfs(3)
3 0 1 4 2 5
- iter_DFS
g.iter_dfs(3)
3 0 1 4 2 5