그래프는 정점(vertex)의 집합 V(G)와 에지(Edge)의 집합 E(G)로 정의
- G = (V, E)
- G = (V, E)
- V(G) = {0, 1, 2, 3}
- E(G) = {(0,1), (0,2), (0,3), (2,3)}
- 정점의 개수가 n인 경우, 최대 에지의 개수 : n(n-1)/2
- 에지 : 정점과 정점을 잇는 선
- G = <V, E>
- V(G) = {0, 1, 2, 3}
- E(G) = {<0,1>, <0,2>, <1,2>, <2,3>, <3,2>}
- 정점의 개수가 n인 경우, 최대 에지의 개수 : n(n-1)
- <0,1> : 0이 head, 1이 tail
- 정점이 head이자 tail
- 중복 에지 인정
- 정점 u, v 사이에 에지 (u, v) 가 있는 경우, u와 v는 인접하다
- u ∈ V(G), v ∈ V(G)고 (u, v) ∈ E(G)이면 u와 v는 adjacent
- 경로(path) : (v1, v2), (v2, v3), (v3, v4)가 집합 E(G)의 원소일 때 v1에서 v4까지 정점 순서 v1->v2->v3->v4
- 경로의 길이 = 에지의 개수
- 어떤 임의의 정점 u와 다른 어떤 임의의 정점 v를 골랐을 때 정점 사이에 경로가 있으면 이를 연결되었다(connected)
- 연결요소 : 정점 집합 {0, 3, 4, 6}과 {1, 2, 5, 7}을 각각 연결 요소(connected component)임.
방향그래프의 차수 : 진입차수 + 진출차수 = d(v)
진입차수 : in-d(v)
진출 차수 : out-d(v)
그래프 G'가 G에 대해 V(G') ⊆ (G)고 E(G') ⊆ E(G)면 그래프 G'는 그래프 G의 부분 그래프
그래프 G'가 그래프 G에 대해 V'=V고 E(G') ⊆ E(G)를 만족
ex) G'는 V'=V를 만족하므로 신장 부분 그래프이지만, G''에는 정점 3이 누락되었으므로 V'=V를 만족하지 못해서 부분 그래프이지만 신장 부분 그래프는 아님
O(d(v))
1) 어떤 정점 v에 대해 인접한 모든 노드를 탐색 : 해당 정점을 인덱스로 삼아 해당되는 연결리스트를 통해 순회하므로, 모든 정점을 조사할 필요 없이 해당 정점의 인접한 정점들만 조사
2) 정점 u에 대해 (u, v) ∈ E(G)인지를 검사 : 해당 정점을 인덱스로 연결 리스트를 가져와 인접한 모든 노드를 순회해야 함.
class Graph:
def __init__(self, vertex_num = None):
self.adj_list = []
self.vtx_num = 0 #정점의 개수
self.vtx_arr = [] #정점의 존재 여부
if vertex_num:
self.vtx_num = vertex_num
self.vtx_arr = [True for _ in range(self.vtx_num)]
self.adj_list = [[] for _ in range(self.vtx_num)] #연결리스트 대신 동적배열 사용
def is_empty(self):
if self.vtx_num == 0:
return True
return False
def add_vertex(self):
for i in range(len(self.vtx_arr)): # 중간에 삭제된 정점이 있는지 확인
if self.vtx_arr[i] == False: #삭제된 정점이라는 의미임
self.vtx_num += 1
self.vtx_arr[i] = True
return i
self.adj_list.append([])
self.vtx_num += 1
self.vtx_arr.append(True)
return self.vtx_num -1
def delete_vertex(self, v) :
if v >= self.vtx_num :
raise Exception(f"There is no vertex of {v}")
if self.vtx_arr[v]:
self.adj_list[v] = []
self.vtx_num -= 1
self.vtx_arr[v] = False
for adj in self.adj_list:
for vertex in adj:
if vertex == v:
adj.remove(vertex)
if self.vtx_arr[v] : self.adj_list[v] = []
:해당 정점이 있다면 인접리스트 초기화self.vtx_arr[v] = False
: 해당 정점 비활성화, 추후 활성화하여 사용 가능하도록 비활성화for adj in self.adj_list: for vertex in adj:
def add_edge(self, u, v) :
self.adj_list[u].append(v)
self.adj_list[v].append(u)
def delete_edge(self, u, v):
self.adj_list[u].remove(v)
self.adj_list[v].remove(u)
def adj(self, v):
return self.adj_list[v]
O(n)
1) 어떤 정점 v에 대해 인접한 모든 노드를 탐색 : v행에 대하여 모든 열 탐색
O(1)
2) (u, v)가 있는지 여부를 확인하는 연산 : adj_matrix[u][v] 유무 확인
ex) 출발 정점이 3인 경우, 출발 정점을 지나 인접 정점 모두 탐색, 이후 인접 정점들의 인접 정점 탐색 순으로 탐색함
from queue import Queue
class Graph:
def __init__(self, vertex_num):
self.adj_list = [[] for _ in range(vertex_num)]
self.visited = [False for _ in range(vertex_num)]
def add_edge(self, u, v):
self.adj_list[u].append(v)
self.adj_list[v].append(u)
def init_visited(self):
for i in range(len(self.visited)):
self.visited[i] = False
def bfs(self, v):
q = Queue()
self.init_visited() # 방문 체크리스트 초기화
q.put(v) # 첫 번째 정점을 큐에 넣음
self.visited[v] = True # 첫 번째 정점 방문 완료
while not q.empty():
v = q.get() # 이미 방문한 정점 dequeue
print(v, end='=')
adj_v = self.adj_list[v] #이미 방문한 정점의 인접 정점 리스트
for u in adj_v:
if not self.visited[u]:
q.put(u)
self.visited[u] = True
q = Queue()
: 큐 생성self.init_visited()
: 모든 정점에 대해 방문 기록 초기화q.put(v)
: 정점 v를 q에 넣음self.visited[v] = True
: 해당 정점 방문 완료while not q.empty():
: q가 빌 때까지 while 루프를 돌면서 모든 정점 방문v = q.get()
: get()을 통해 dequeue로 q의 front값을 pop하면서 그 값을 다음 정점으로 선정adj_v = self.adj_list[v]
: adj_v는 해당 정점 v에 대한 인접한 모든 정점시작 정점을 기준으로 인접한 모든 노드를 방문 후, 다시 시작 정점으로 돌아와 가보지 않은 방향으로 다시 방문
def __dfs_recursion(self, v):
print(v, end='=')
self.visited[v] = True
adj_v = self.adj_list[v]
for u in adj_v:
if not self.visited[u]:
self.__dfs_recursion(u)
def dfs(self, v):
self.init_visited()
self.__dfs_recursion(v)
def iter_dfs(self, v):
s = Stack()
self.init_visited()
s.push(v)
self.visited[v] = True
print(v, end='=')
is_visited = False
while not s.empty():
is_visited=False
v = s.peak()
adj_v = self.adj_list[v]
for u in adj_v:
if not self.visited[u]:
s.push(u)
self.visited[u] = True
is_visited = True
break
if not is_visited:
s.pop()
s.push(v)
: 최초 방문한 정점을 stack에 쌓음is_visited = False
: 아직 방문하지 않은 정점을 방문하였는가?v = s.peak()
: dfs와는 다르게 dequeue를 통해 정점을 가져오지 않고 peak 메서드 사용하여 스택에는 정점이 그대로 쌓여있음is_visited = True break
: 인접 정점들에 대해 순회하며 방문하지 않은 정점을 만난 경우 True로 변경 후, 방문하지 않았던 정점에 대하여 for문을 빠져나가 while 루프를 다시 돈다.def dfs_all(self):
self.init_visited()
for i in range(len(self.visited)):
if not self.visited[i]:
self.__dfs_recursion(i)