: 정점(vertex)를 간선(edge, link)로 연결한 형태의 자료구조이다.
트리도 그래프의 일종이지만 그래프는 사이클을 형성할 수 있고 이웃한 정점끼리 상하 관계를 갖지 않는다.
(1) 연결/비연결 그래프
(+) 완전 그래프(Complete graph)
모든 정점에 간선이 있고, 한 정점에서 다른 정점과 모두 연결되어 있는 그래프
(2) 방향/무방향 그래프
(3) 가중치 그래프
: 간선에 가중치가 부여된 그래프
간선에 부여된 값인 가중치는 비용(cost)라고도 불리며 음수가 될 수도 있다.
(4) 서브 그래프 (=부분 그래프)
: 특정 그래프의 정점과 간선의 일부분으로 이루어진 그래프
N X N 크기의 행렬로 그래프를 표현하는 방식으로 <행, 열> 값은 <출발 정점, 도착 정점>을 의미한다.
연결된 상태는 1,
연결되지 않은 상태는 0 으로 표기한다.
가중치 그래프인 경우 가중치 값으로 표기한다.
사진 출처
방향 그래프
사진 출처
무방향 그래프
public class Graph {
public static void main(String[] args) {
int[][] edges = new int[][] {
{1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 5}, {4, 5}
};
int N = 5;
int[][] matrix = new int[N + 1][N + 1];
// 간선 정보로 인접 행렬 채우기 (무방향 그래프)
for (int[] edge : edges) {
matrix[edge[0]][edge[1]] = 1;
matrix[edge[1]][edge[0]] = 1; // 무방향이므로 양방향으로 연결
}
// 인접 행렬 출력
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
}
// 출력 결과
0 1 1 1 0
1 0 1 0 1
1 1 0 0 0
1 0 0 0 1
0 1 0 1 0
그래프의 특정 정점과 연결된 정점들을 연결 리스트로 표현하는 방식이다.
사진 출처
방향 그래프
사진 출처
무방향 그래프
사진 출처
가중치 그래프
public class Graph {
public static void main(String[] args) {
int[][] edges = new int[][] {
{1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 5}, {4, 5}
};
int N = 5;
List<List<Integer>> adjList = new ArrayList<>();
// 정점의 수 만큼 리스트 초기화
for (int i = 0; i <= N; i++) {
adjList.add(new ArrayList<>());
}
// 간선 정보로 인접 리스트 채우기 (무방향 그래프)
for (int[] edge : edges) {
adjList.get(edge[0]).add(edge[1]);
adjList.get(edge[1]).add(edge[0]); // 무방향이므로 양방향으로 연결
}
// 인접 리스트 출력
for (int i = 1; i <= N; i++) {
System.out.print(i + ": ");
for (int neighbor : adjList.get(i)) {
System.out.print(neighbor + " ");
}
System.out.println();
}
}
}
// 출력 결과
1: 2 3 4
2: 1 3 5
3: 1 2
4: 1 5
5: 2 4
그래프에서 더 이상 방문 가능한 정점이 없을 때까지 최대한 깊이 탐색하기를 반복하는 탐색 방법이다.
DFS 구현 방식은 크게 재귀 호출을 이용하는 방법과 스택을 이용하는 방법으로 나눌 수 있다.
배열을 사용하여 특정 정점의 방문 여부를 기록할 수 있으며,
뒤로 돌아가기 기능을 위해서 스택 또는 재귀호출을 사용한다.
public class Graph {
static ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
static boolean[] visited;
public static void main(String[] args) {
int N = 5; // 정점 개수
int[][] edges = {
{1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 5}, {4, 5}
};
visited = new boolean[N + 1];
// 인접 리스트 초기화
for (int i = 0; i <= N; i++) {
graph.add(new ArrayList<>());
}
// 간선 정보 추가 (무방향 그래프)
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
// DFS 시작 (예: 1번 정점부터 탐색)
DFS(1);
}
public static void DFS(int node) {
visited[node] = true; // 현재 노드 방문 처리
System.out.print(node + " "); // 현재 노드 출력
// 현재 노드에 연결된 다른 노드들을 탐색
for (int next : graph.get(node)) {
if (!visited[next]) {
DFS(next); // 방문하지 않은 노드라면 재귀 호출
}
}
}
}
public static void DFS(int start) {
Stack<Integer> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
int node = stack.pop();
if (!visited[node]) {
visited[node] = true;
System.out.print(node + " ");
// 연결된 노드들을 스택에 넣는다.
// 스택은 LIFO(후입선출)이기 때문에, 방문 순서를 제어하고 싶으면 리스트를 정렬하거나 역순으로 넣어야 한다.
for (int next : graph.get(node)) {
if (!visited[next]) {
stack.push(next);
}
}}}}}
최대한 넓게 탐색하기를 반복하는 방식으로 인접한 모든 정점들을 방문하여 탐색하는 방식이다.
Queue(큐)를 이용해서 구현하며, 먼저 들어온 정점을 먼저 처리(FIFO)한다.
public static void BFS(int start) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.poll();
System.out.print(node + " ");
for (int next : graph.get(node)) {
if (!visited[next]) {
visited[next] = true;
queue.offer(next);
}
}
}
}
한 정점에서 목적지 정점까지 이르는 가중치의 합이 최소가 되는 경로를 결정하는 알고리즘이다.
| 알고리즘 | 특징 | 시간 복잡도 |
|---|---|---|
| 다익스트라 (Dijkstra) | 음의 가중치가 없는 경우, 최단 경로 | O((V + E) log V) |
| 벨만-포드 (Bellman-Ford) | 음의 가중치가 있는 그래프에 적합 | O(V * E) |
| 플로이드-워셜 (Floyd-Warshall) | 모든 쌍의 최단 경로 | O(V^3) |
최단 경로 알고리즘으로 가장 대표적인 알고리즘은 다익스트라 알고리즘이다.
간선의 가중치가 음이 아닌 수라는 가정하에 사용 가능한 알고리즘으로, 특정 정점에서 다른 모든 정점까지의 최단 거리를 구해주는 알고리즘이다.
public class Dijkstra {
static final int INF = Integer.MAX_VALUE; // 무한대 설정
public static void main(String[] args) {
int N = 5; // 노드의 수
List<List<int[]>> graph = new ArrayList<>();
// 그래프 초기화 (각 노드에 인접 노드 추가)
for (int i = 0; i <= N; i++) {
graph.add(new ArrayList<>());
}
// (목적지, 비용) 형태로 간선 추가
graph.get(1).add(new int[]{2, 2});
graph.get(1).add(new int[]{3, 5});
graph.get(2).add(new int[]{3, 1});
graph.get(2).add(new int[]{4, 2});
graph.get(3).add(new int[]{4, 3});
graph.get(4).add(new int[]{5, 1});
graph.get(5).add(new int[]{1, 7});
// 다익스트라 알고리즘 실행
dijkstra(1, graph, N);
}
public static void dijkstra(int start, List<List<int[]>> graph, int N) {
int[] distance = new int[N + 1];
Arrays.fill(distance, INF); // 거리 배열 초기화
distance[start] = 0;
// 우선순위 큐를 사용하여 최단 거리 노드 탐색
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
pq.add(new int[]{0, start}); // (거리, 노드)
while (!pq.isEmpty()) {
int[] current = pq.poll();
int dist = current[0];
int node = current[1];
// 이미 더 짧은 경로로 방문한 적 있으면 무시
if (distance[node] < dist) {
continue;
}
// 인접 노드들 확인
for (int[] neighbor : graph.get(node)) {
int nextNode = neighbor[0];
int edgeCost = neighbor[1];
int newDist = dist + edgeCost;
if (newDist < distance[nextNode]) {
distance[nextNode] = newDist;
pq.add(new int[]{newDist, nextNode});
}
}
}
// 결과 출력
for (int i = 1; i <= N; i++) {
if (distance[i] == INF) {
System.out.println(i + " 노드는 도달 불가");
} else {
System.out.println(i + " 노드까지의 최단 거리: " + distance[i]);
}
}
}
}
1 노드까지의 최단 거리: 0
2 노드까지의 최단 거리: 2
3 노드까지의 최단 거리: 3
4 노드까지의 최단 거리: 4
5 노드까지의 최단 거리: 5
25.04.28 스터디 내용으로 변경하였습니다.