graph
tree
그래프의 한 종류로 DAG(Directed Acyclic Graphe) 방향성이 있는 비순환 그래프의 한 정류
Directed Graph 방향 그래프이다.
cycle 불가능하고, 자체 간선(self-loop) 불가능, 비순환그래프
하나의 루트 노드만이 존재하고 모든 자식 노드는 하나의 부모 노드만을 가진다.
부모-자식 관계는 top-bottom 과 bottom-top으로 이뤄진다
계층 모델이다.
DFS, BFS 안의 pre-, in-, post-order로 순회한다.
간선의 수는 노드가 N인 트리는 항상 n-1의 간선을 가진다.
경로는 임의의 두 노드 간의 경로로 유일하다.
예시 및 종류로는 이진 트리, 이진 탐색 트리, 균형트리, 이진힙 등이 있다.
=> 정리하자면 트리는 그래프 중에서 방향성이 있는 비순환 그래프이다.
출처 : https://developer-mac.tistory.com/64
루트 노드(혹은 다른 임의의 노드)에서 시작해서, 인접한 노드를 먼저 탐색하는 방법이다.
시작 정점으로부터 가까운 정점을 먼저 방문하고, 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법이다
주로 두 노드 사이의 최단 경로
를 찾고 싶을 때 이 방법을 선택한다.
예를 들면, 지구 상에 존재하는 모든 친구 관계를 그래프로 표현한 후 'A'와 'B의 사이에 존재하는 경로를 찾을 때
: 그래프 탐색 방법중 BFS 외에 DFS(Depth-First Search) 인 깊이 우선 탐색은 모든 친구 관계를 다 살펴봐야 하지만, 너비 우선 탐색은 'A' 와 가까운 관계부터 탐색하므로 조금 더 효율적이다.
최대한 넓게 이동한 다음, 더 이상 갈 수 없을 때 아래로 이동한다.
BFS는 3가지 조건을 만족하는데 [1] 최소 비용 문제, [2] 간선의 가중치가 1, [3] 정점과 간선의 개수가 적어 시간 제약과 메모리 제한 내에 만족한다.
오른쪽 그림이 BFS의 탐색 방법인데,
[1] 루트 노드에서 시작
[2] 자식 노드를 ①에 저장
[3] ①에 저장된 노드들을 차례로 방문하고, 자식들을 ②에 저장
[4] ②에 저장된 노드를 차례대로 방문하고, 각각의 자식들을 ③에 저장
[5] 위 과정을 반복
[6] 모든 노드를 방문하면 탐색을 마침
DFS 와 비교하면 DFS는 갈림길에서 하나의 길로 들어서서 막다른 길이 나올 때까지 깊게
탐색한다면, BFS는 갈림길에서 연결되어 있는 모든 길을 한 번 씩 탐색한뒤 다시 연결되어 있는 모든 길을 넓게
탐색함
BFS는 현재 정점에 연결된 가까운 점들로부터 탐색함
즉, 루트 노드를 큐에 넣고 방문 처리를 하고 노드의 이웃 노드를 스택이 아닌 큐에 집어 넣어 자연스럽게 먼저 들어간 노드를 먼저 탐색(FIFO) 한다.
큐를 pop 해서 해당 노드의 방문하지 않은 모든 인접 노드를 큐에 넣고 방문 처리를 큐가 빌 때가지 반복함
class Graph:
def __init__(self):
self.graph = defaultdict(list)
def addEdge(self, u, v):
self.graph[u].append(v)
def BFS(self, s):
visited = [False] * (max(self.graph)+1)
queue = []
queue.append(s)
visited[s] = True
while queue:
s = queue.pop(0)
print(s, end = ' ')
for i in self.graph[s]:
if visited[i] == False:
queue.append(i)
visited[i] = True
if __name__ == '__main__':
g = Graph()
g.addEdge(0,1)
g.addEdge(0,2)
g.addEdge(1,2)
g.addEdge(2,0)
g.addEdge(2,3)
g.addEdge(3,3)
g.BFS(2)
큐를 구현시 list를 사용하면 큐의 방향에 따라 O(N)이 발생할 수 있으므로,
이중 연결 리스트인 collections.deque로 큐 구현 하기
from collections import deque
graph = [
[],
[2, 3, 4],
[1, 5, 6],
[1],
[1, 7],
[2, 8],
[2, 8],
[4],
[5, 6]
]
queue = deque()
visited = [False] * len(graph)
queue.appendleft(1)
visited[1] =True
print(1)
while len(queue) >0 :
cur = queue.pop()
for s in graph[cur]:
if visited[s]==False:
print(s)
queue.appendleft(s)
visited[s] = True
N은 노드(node), E는 간선(edge) 일때,
인집 리스트는 O(N+E), 인접 행렬은 O(N^2) 이다.
일반적으로 간선(edge)의 크기가 N^2에 비해 상대적으로 적어 인접 리스트 방식이 효율적임
인접 행렬의 경우 정점의 개수 N 만큼 도는 이중 for문을 돌려 두 정점 간의 간선이 존재하는지 확인해야 해서 N의 제곱만큼 돌아서 O(N^2)이 된다.
인접 리스트로 구현되면, 존재하는 간선의 정보만 저장되어 있으면 인접 행렬에서 사용한 이중 for문이 필요하지 않는데, 다음 노드가 방문하였는지 확인할 때 간선의 개수 E의 두 배만큼의 시간이 걸린다.
각 노드를 방문할 때 정점의 개수인 N만큼 걸려서 O(N+2*E) = O(N+E)가 된다.
프로그래머스 게임 맵 최단거리와 Leetcode 1091. Shortest Path in Binary Matrix
프로그래머스 게임 맵 최단거리 :
https://school.programmers.co.kr/learn/courses/30/lessons/1844
def solution(maps):
if maps[0][0] == 0 or maps[-1][-1] == 0:
return -1
ROWS, COLS = len(maps), len(maps[0])
visited = [[0]*COLS for _ in range(ROWS)]
queue = [(0,0,1)]
visited[0][0] =1
while queue:
x,y,d = queue.pop(0)
if x==ROWS-1 and y==COLS-1:
return d
for i,j in [(1,0), (-1,0), (0,1), (0,-1)]:
if 0<=x+i<ROWS and 0<=y+j<COLS:
if visited[x+i][y+j] == 0 and maps[x+i][y+j] == 1:
queue.append((x+i, y+j, d+1))
visited[x+i][y+j] = 1
return -1
Leetcode 1091. Shortest Path in Binary Matrix :
https://leetcode.com/problems/shortest-path-in-binary-matrix/
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
visited = [[0] * COLS for _ in range(ROWS)]
if grid[0][0] or grid[-1][-1]==1:
return -1
queue = [(0,0,1)]
visited[0][0] = 1
while queue:
x,y,d = queue.pop(0)
if x==ROWS-1 and y==COLS-1:
return d
for i in range(-1,2):
for j in range(-1,2):
if 0<=x+i<ROWS and 0<=y+j<COLS:
if visited[x+i][y+j] == 0 and grid[x+i][y+j]==0:
queue.append((x+i, y+j, d+1))
visited[x+i][y+j]=1
return -1
두 문제 모두 주어진 이진 행렬에서 시작 지점부터 목적 지점까지의 최단 경로의 길이를 계산하는 BFS(너비 우선 탐색)을 구현한다. 우선, visited 배열을 생성하여 각 위치를 방문했는지 여부를 저장한다.시작 지점과 목적 지점이 이동할 수 없는 경우, 즉 시작 지점이 1로 표시되어 있거나 목적 지점이 1로 표시되어 있는 경우에는 -1을 반환한다.
큐를 초기화하고 시작 지점을 큐에 추가한다. 시작 지점은 (0, 0)이며, 시작 지점에서부터의 거리는 1로 초기화된다. 시작 지점은 방문했음을 표시하기 위해 visited 배열에도 업데이트한다.
큐가 비어있지 않은 동안, 큐에서 하나의 위치를 꺼내어 이동 가능한 모든 방향으로 탐색한다. 이동할 수 있는 방향은 상하좌우 및 대각선 방향이다.
탐색하려는 위치가 이동 가능한 위치인지 확인하는데 유효한 범위 내에 있고, 방문하지 않은 위치이며, 장애물이 없는 경우에는 큐에 추가하고 해당 위치를 방문했음을 표시한다.
목적 지점에 도달한 경우 해당 지점까지의 거리를 반환한다.
모든 탐색이 끝났는데도 목적 지점에 도달하지 못한 경우에는 -1을 반환한다.
문제를 푸는 접근 방향성은 같다.
- Shortest Path in a Grid with Obstacles Elimination
https://leetcode.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/
- As Far from Land as Possible
https://leetcode.com/problems/as-far-from-land-as-possible/
-https://developer-mac.tistory.com/64
-https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/