Graph
그래프는 여러 노드들이 개수 제한 없이 서로 연결된 상태를 말하며,
각 노드들은 계층구조를 갖지 않는다
트리는 그래프의 한 모델이지만 다음과 같은 차이를 보인다.
Graph | Tree |
비순환 or 순환 방식 directed or undirected 네트워크 모델 |
비순환 방식 항상 directed 계층 모델 |
그래프는 다음과 같이 크게 세 종류로 나뉜다.
- 방향 그래프 (Directed graph)
- 무방향 그래프 (Undirected graph)
- 가중 그래프 (Weighted graph)
- 이 경우 ( _ , 가중치) 형태로 행렬 or 리스트에 저장한다.
Graph representation
그래프를 구현하는 방법에는 두 가지가 있다.
- 인접 행렬 (Adjacency Matrix)
두 vertex의 연결 여부, 즉 edge의 존재 여부를 표현한다. - 인접 리스트 (Adjacency List)
각각의 vertex에 대한 인접 vertex들을 linked list로 표현한다.
인접 행렬 (Adjacency Matrix) | 인접 리스트 (Adjacency List) |
구현이 쉬움. Edge[i][j]->특정 노드 연결 여부 탐색, 수정 시간이 O(1)로 매우 빠름 노드의 수가 커질 수록 필요 메모리가 매우 커지고 낭비도 커짐 |
실제 데이터들만 저장하므로, 메모리 낭비를 최소화할 수 있다. 특정 노드 연결에 대해 탐색 시간이 최대 O(n)으로 오래 걸릴 수 있음 |
graph_representation.py
# 인접 행렬
adjacencyMatrix = [
[0, 1, 0],
[1, 0, 1],
[0, 1, 0]
]
# 인접 리스트
adjacencyList = [[] for _ in range (3)] # 2차원 배열 선언
adjacencyList[0].append(1)
adjacencyList[0].append(2)
adjacencyList[1].append(0)
adjacencyList[2].append(0)
# 원소가 1부터 시작한다면 0번째 칸 비우는 것 주의
DFS (Depth First Search, 깊이 우선 탐색)
DFS와 BFS는 모든 노드를 검사하는 그래프 완전 탐색 방법이다.
그 중 DFS는 현재 탐색하고 있는 경로를 끝까지 탐색한 후, 그 다음 경로를 탐색하는 것을 의미한다.
경로를 끝까지 탐색한 후 더 이상 갈 수 없을 때 이전 노드로 돌아가(back tracking) 방문하지 않은 노드에 대해 탐색한다.
*시간 복잡도 : 인접 행렬 O(n²) / 인접 리스트 O(n+e)
(*why? 인접 리스트에서 DFS가 n번 호출되긴 하지만 인접행렬과는 달리 DFS하나당 각 정점에 연결되어 있는 간선의 개수만큼 탐색을 하므로 예측이 어려움. 하지만 DFS가 다 끝난 후를 생각하면, 모든 정점을 한번씩 다 방문하고, 모든 간선을 한번씩 모두 검사했다고 할 수 있으므로 O(n+e)의 시간이 걸렸다고 할 수 있음. 반면 인접 행렬에서는 정점마다 항상 n번씩 방문)
크게 다음과 같은 순으로 진행된다.
- 스택의 최상단 노드 확인
- 최상단 노드에게 방문하지 않은 인접 노드 있다면, 그 노드를 스택에 넣고 방문 처리 (=> 1. 해당 노드의 자식 노드를 스택에 넣고),
방문하지 않은 인접 노드 없으면 스택에서 최상단 노드를 뺌 (=>2. 뺀 노드 출력, 3. 스택에서 꺼낸 노드의 자식 노드를 다시 스택에 추가) - 1,2를 반복 수행
위의 원리를 이용하여 재귀함수로 구현할 수 있다.
재귀함수
함수 호출이 반복적으로 일어날 때 가장 마지막에 호출된 함수가 수행되어야 그 전의 함수 호출이 종료되기 때문에, 재귀함수는 내부적으로 스택 자료구조와 동일하다. 따라서 스택 자료구조를 활용해야 하는 다수의 알고리즘은 재귀함수를 이용해 구현될 수 있다. 그 중 DFS가 대표적인 예이다.
재귀, 인접행렬를 이용한 구현.c
#include <stdio.h>
int graph[11][11]; //인접 행렬로 구현한 그래프 (1~10노드 이용)
int visited[11]; //방문 여부 확인
int DFS(int cur, int n){
int i;
visited[cur] = 1; //시작 노드를 방문으로 체크
for(i=1; i<=n; i++){
if(graph[cur][i] == 1 && visited[i] == 0){
//현재 노드에 인접하면서 방문하지 않은 노드일 시
printf("%d ", i);
DFS(i, n); //해당 인접 노드에서 그것의 인접 노드 계속 탐색(DFS)
}
}
}
재귀, 인접리스트를 이용한 구현.py
def DFS(graph, start, visited):
visited[start] = True
print(start, end=' ')
for i in graph[start]: # v번째 리스트 내부 조사, 즉 v노드의 인접 노드 조사
if not visited[i]:
DFS(graph, i, visited) # 방문하지 않은 경우 (다음 노드 방문 처리 + 해당 인접 노드 탐색 진행) ...
활용
- 모든 경우를 조사하는 완전 탐색
- 순열 조합 구현
BFS (Breath First Search, 너비우선 탐색)
DFS와 마찬가지로 모든 노드를 검사하는 그래프 완전 탐색 방법이다.
시작 노드로부터 length가 낮은 것부터, 즉 가까운 노드부터 탐색하는 알고리즘이다.
인접한 노드들을 큐에 넣고 가장 먼저 들어온 것부터 꺼내 해당 노드의 인접 노드를 검사하고, 큐의 순서에 따라 이를 반복한다.
큐(queue)를 이용하여 구현한다.
*시간 복잡도 : 인접 행렬 O(n²) / 인접 리스트 O(n+e) - 위의 논리와 같음
크게 다음과 같은 순으로 진행된다.
- 시작 노드를 큐에 삽입하고 이를 방문 처리
- 큐에서 하나의 노드를 꺼냄
- 꺼낸 노드에 연결된 노드 중 방문하지 않은 노드를 방문하고 방문 처리, 차례대로 큐에 삽입
(큐에 넣은 노드는 방문 처리된 노드들, 이후 FIFO 방식으로 인접노드들 검사됨) - 2,3을 반복 수행
-> 핵심은 인접 노드 (그 중 방문하지 않은 노드)를 queue에 넣고, queue의 요소들이 존재할 때까지 노드들을 하나씩 꺼내며 검사한다!
# 키워드
✔️ 방문처리
✔️ queue 가 존재할때까지 하나씩 빼며
✔️ 문제의 조건에 만족하도록 인접한 것 queue에 append!
큐, 인접리스트를 이용한 구현.py
from collections import deque
def BFS(graph, start, visited):
queue = deque([start])
visited[start] = True
while queue: # 큐가 빌 때까지 반복
v = queue.popleft() # 큐에서 원소를 뽑고
print(v, end=' ')
for i in graph[v]:
if not visited[i]: # 뽑은 원소 인접 노드 탐색, 방문하지 않은 것들 큐에 삽입
queue.append(i)
visited[i] = True # 이후 큐에 들어있는 다음 노드들 진행
활용
- depth를 계산해야 하는 문제
- 가중치 그래프에서 최단 거리 찾기
연습 문제
백준 #1260 DFS와 BFS
https://www.acmicpc.net/problem/1260
1260.py
from collections import deque
def DFS(start):
visited[start] = True
print(start, end=' ')
for i in graph[start]:
if not visited[i]:
DFS(i)
def BFS(start):
queue = deque([start])
visited_2[start] = True
while queue:
v = queue.popleft()
print(v, end=' ')
for i in graph[v]:
if not visited_2[i]:
queue.append(i)
visited_2[i] = True
n, m, v = map(int, input().split())
# 인접 리스트
graph = [[] for _ in range (n+1)]
visited = [False] * (n+1)
visited_2 = [False] * (n+1)
for _ in range (m):
a, b = map(int, input().split())
graph[a].append(b)
graph[b].append(a)
for i in graph:
i.sort()
# *** 큰 순으로 진행하기 위해 그래프를 정렬하여 오름차순임을 보장하기
DFS(v)
print()
BFS(v)
연습 문제
백준 #13565 침투
DFS, node.js
스택 사용한 DFS
https://www.acmicpc.net/problem/13565
13565.js
const data = require('fs').readFileSync('/dev/stdin').toString().split('\n');
const [m, n] = data[0].split(' ').map(x=>parseInt(x));
const matrix = Array.from({ length : m }, () => Array(n).fill());
for(let i = 1; i < m + 1; i++){
matrix[i - 1] = data[i].split('').map(x=>parseInt(x));
}
const dx = [-1, 1, 0, 0];
const dy = [0, 0, 1, -1];
function DFS(row, col, visited, stack){
stack.push([row, col]);
while(stack.length){
let [row, col] = stack.pop();
visited[row][col] = true;
if(row == m - 1){
return 1;
}
for(let i = 0; i < 4; i++){
if(0 <= row + dy[i] && row + dy[i] < m && 0 <= col + dx[i] && col + dx[i]< n){
if(!visited[row + dy[i]][col + dx[i]]
&& matrix[row + dy[i]][col + dx[i]] === 0){
stack.push([row + dy[i], col + dx[i]]);
}
}
}
}
return 0;
}
let flag = 0;
const visited = Array.from({ length : m }, () => Array(n).fill(false));
for(let i = 0; i < n; i++){
if(matrix[0][i] === 0 && !visited[0][i]){
if(DFS(0, i, visited, [])){
flag = 1;
break;
}
}
}
console.log(flag ? 'YES' : 'NO');
'CS > Algorithm' 카테고리의 다른 글
이진 탐색 Binary Search (0) | 2024.06.13 |
---|---|
정렬 Sort (0) | 2024.06.12 |
조합 / 순열 구현 (0) | 2024.03.12 |
완전 탐색 Brute Force (0) | 2024.03.08 |
그리디 greedy (0) | 2024.01.16 |