[Date Structure] 그래프
1. 그래프
- 그래프는 요소들이 서로 복잡하게 연결되어 있는 관계를 표현하는 자료구조이다.
- 그래프는 선형 자료구조들이나 트리보다 더 일반화된 자료구조로, 선형 자료구조나 트리도 그래프의 한 종류이다.
- 그래프는 정점과 간선의 집합이다.
- 정점(노드) : 여러 가지 특성을 가질 수 있는 객체
- 간선(링크) : 객체 정점들 간의 관계
그래프의 종류 | 정의 |
---|---|
무방향 그래프 | 간선에 방향이 표시되지 않는 그래프 |
방향 그래프 | 간선에 방향성이 존재하는 그래프 |
가중치 그래프(네트워크) | 간선에 비용이나 가중치가 할당된 그래프 |
부분 그래프 | 정점의 집합과 간선의 집합의 부분 집합으로 이루어진 그래프 |
그래프의 용어 | 정의 |
---|---|
인접 정점 | 간선에 의해 직접 연결된 정점 |
정점의 차수 | 정점에 연결된 간선의 수 |
경로 | 간선을 따라갈 수 있는 길 |
경로의 길이 | 경로를 구성하는 데 사용된 간선의 수 |
단순 경로 | 경로 중에서 반복되는 간선이 없는 경로 |
사이클 | 시작 정점과 종료 정점이 동일한 단순 경로 |
연결 그래프 | 그래프의 모든 정점들 사이에 경로가 존재하는 그래프 |
트리 | 사이클을 갖지 않는 연결 그래프 |
완전 그래프 | 모든 정점이 서로 연결되어 있는 그래프 |
그래프의 추상 자료형
데이터 : 정점의 집합과 간선의 집합
연산 :
- init() : 그래프를 초기화한다.
- is_empty() : 그래프가 공백 상태인지 확인한다.
- insert_vertex(v) : 그래프에 정점 v를 삽입한다.
- insert_edge(u, v) : 그래프에 간선 (u, v)를 삽입한다.
- delete_vertex(v) : 그래프의 정점 v를 삭제한다.
- delete_dege(u, v) : 그래프 g의 간선 (u, v)를 삭제한다.
- adjacent(v) : 정점 v에 인접한 모든 정점의 집합을 반환한다.
2. 인접 행렬을 이용한 그래프의 표현
- 인접 행렬 : n개의 정점을 표현하는 n x n의 2차원 배열
- n개의 정점을 가지는 그래프를 인접 행렬로 표현하기 위해서는 간선의 수에 무관하게 항상 $n^2$개의 메모리 공간이 필요하다.
그래프의 연산 | 시간 복잡도 | 방법 |
---|---|---|
두 정점을 연결하는 간선의 존재 여부 확인 | $O(1)$ | M[u][v]의 값을 조사하여 정점 u와 정점 v를 연결하는 간선의 존재하는지 바로 알 수 있다. |
정점의 차수 | $O(n)$ | 인접 행려의 행이나 열을 조사하여 알 수 있다. |
그래프에 존재하는 간선의 개수 | $O(n^2)$ | 인접 행렬 전체를 조사해야 한다. |
그래프의 종류 | 대칭 행렬 여부 |
---|---|
무방향 그래프 | O |
방향 그래프 | X |
가중치 그래프 | 인접 행렬의 각 항목은 해당 간선의 가중치 값을 갖는다. |
flowchart TB
A --- X & Z
X --- Z & Y
Y --- Z
4
A 0 1 0 1
X 1 0 1 1
Y 0 1 0 1
Z 1 1 1 0
#include <stdio.h>
#include <stdlib.h>
#define MAX_VTXS 256
void error(char str[]) {
printf("%s\n", str);
exit(1);
}
typedef char VtxData; // 그래프 정점에 저장할 데이터의 자료형
int adj[MAX_VTXS][MAX_VTXS]; // 인접 행렬을 위한 2차원 배열(adj[i][j]는 i번째 정점과 j번째 정점 간의 간선의 유무 또는 간선의 가중치를 나타낸다.)
int vsize; // 그래프의 전체 정점의 개수
VtxData vdata[MAX_VTXS]; // 정점을 저장하는 데이터 배열
int is_empty_graph() { return (vsize == 0); }
int is_full_graph() { return vsize >= MAX_VTXS; }
void init_graph()
{
int i, j;
vsize = 0;
for (i = 0; i < MAX_VTXS; i++)
for (j = 0; j < MAX_VTXS; j++)
adj[i][j] = 0;
}
void insert_vertex(VtxData name)
{
if (is_full_graph())
error("Error: 그래프 정점의 개수 초과\n");
else
vdata[vsize++] = name;
}
void insert_edge(int u, int v, int val)
{
adj[u][v] = val;
}
void insert_edge2(int u, int v, int val)
{
adj[u][v] = adj[v][u] = val;
}
void print_graph(char* msg)
{
int i, j;
printf("%s", msg);
printf("%d\n", vsize);
for (i = 0; i < vsize; i++) {
printf("%c ", vdata[i]);
for (j = 0; j < vsize; j++)
printf(" %3d", adj[i][j]);
printf("\n");
}
}
void load_graph(char *filename)
{
int i, j, val, n;
char str[80];
FILE *fp = fopen(filename, "r");
if (fp != NULL) {
init_graph();
fscanf(fp, "%d", &n);
for (i = 0; i < n; i++) {
fscanf(fp, "%s", str);
insert_vertex(str[0]);
for(j = 0; j < n; j++) {
fscanf(fp, "%d", &val);
if (val != 0)
insert_edge(i, j, val);
}
}
fclose(fp);
}
}
int main()
{
load_graph("graph.txt");
print_graph("그래프(인접행렬)\n");
return 0;
}
그래프(인접행렬)
4
A 0 1 0 1
X 1 0 1 1
Y 0 1 0 1
Z 1 1 1 0
3. 인접 리스트를 이용한 그래프의 표현과 구현
-
인접 리스트 : 그래프의 각 정점에 인접한 정점들을 연결 리스트로 표현하는 방법
- 무방향 그래프의 경우 간선 (i, j)가 추가되면, 정점 i의 연결 리스트에 j 노드를 추가해야 하고 정점 j의 연결 리스트에 i 노드를 추가해야 한다.
- 정점의 수가 n개이고 간선의 수가 e개인 무방향 그래프를 표시하기 위해서는 n개의 연결 리스트가 필요하고, n개의 헤더 포인터와 2e개의 노드가 필요하다.
- 인접 행렬 표현은 희소 그래프(정점의 개수에 비해 간선의 개수가 매우 적은 그래프)의 표현에 적합하다.
- 그래프에서 간선 (i, j)의 존재 여부나 정점 i의 차수를 알기 위해서는 인접 리스트에서의 정점 i의 연결 리스트를 탐색해야 하므로 연결 리스트에 있는 노드의 수(정점의 차수)만큼의 시간 $O(n + e)$ 이 필요하다.
typedef struct GraphNode {
int id;
struct GraphNode* link;
} GNode;
typedef char VtxData;
GNode* adj[MAX_VTXS];
int vsize;
VtxData vdata[MAX_VTXS];
int is_empty_graph() {return vsize == 0;}
int is_full_graph() {return (vsize >= MAX_VTXS);}
void init_graph()
{
int i;
vsize = 0;
for (i = 0; i < MAX_VTXS; i++)
adj[i] = NULL;
}
void reset_graph()
{
int i;
GNode* n;
for (i = 0; i < vsize; i++)
while( adj[i] != NULL) {
n = adj[i];
adj[i] = n -> link;
free(n);
}
vsize = 0;
}
void insert_vertex(char name)
{
if (is_full_graph())
error("Error : 그래프 정점의 개수 초과\n");
else
vdata[vsize++] = name;
}
void insert_edge(int u, int v)
{
GNode* n = (GNode*)malloc(sizeof(GNode));
n -> link = adj[u];
n -> id = v;
adj[u] = n;
}
4. 그래프의 탐색
- 그래프의 탐색 : 하나의 정점에서 시작하여 모든 정점들을 한 번씩 방문하는 작업
- 많은 그래프 문제들이 단순히 정점들을 탐색하는 것만으로 해결된다.
깊이 우선 탐색
- 미로를 탐색할 때 한 방향으로 갈 수 있을 때까지 계속 가다가 더 이상 갈 수 없게 되면 다시 가장 가까운 갈림길로 돌아와서 다른 방향을 다시 탐색하는 방법(스택을 이용한 미로 탐색 방법과 유사)
- 시작 정점에서부터 임의의 인접한 정점으로 깊이 탐색을 진행한다. 이때 방문한 정점은 표시를 하고, 방문하지 않은 인접 정점으로만 탐색이 가능하다.
- 더 이상 방문하지 않은 인접 정점이 없으면, 가장 마지막에 만났던 정점으로 되돌아간다.
- 다시 아직 방문하지 않은 인접 정점을 찾아 다시 동일한 방법의 탐색을 진행한다.
- 더 이상 방문하지 않은 정점이 없으면, 탐색이 완료된다.
인접 행렬을 이용한 그래프의 깊이 우선 탐색
8
A 0 1 1 0 0 0 0 0
B 1 0 0 1 0 0 0 0
C 1 0 0 1 1 0 0 0
D 0 1 1 0 0 1 0 0
E 0 0 1 0 0 0 1 1
F 0 0 0 1 0 0 0 0
G 0 0 0 0 1 0 0 1
H 0 0 0 0 1 0 1 0
#include <stdio.h>
#include <stdlib.h>
#define MAX_VTXS 256
void error(char str[])
{
printf("%s", str);
exit(1);
}
typedef char VtxData; // 그래프 정점에 저장할 데이터의 자료형
int adj[MAX_VTXS][MAX_VTXS]; // 인접 행렬
int vsize; // 전체 정점의 개수
VtxData vdata[MAX_VTXS]; // 정점에 저장할 데이터 배열
int is_empty_graph() { return vsize == 0; }
int is_full_graph() { return vsize >= MAX_VTXS; }
void init_graph()
{
int i, j;
vsize = 0;
for (i = 0; i < MAX_VTXS; i++)
for (j = 0; j < MAX_VTXS; j++)
adj[i][j] = 0;
}
void insert_vertex(VtxData name)
{
if (is_full_graph())
error("Error : 그래프 정점의 개수 초과\n");
else
vdata[vsize++] = name;
}
void insert_edge(int u, int v, int val) // 방향 그래프
{
adj[u][v] = val;
}
void insert_edge2(int u, int v, int val) // 무방향 그래프
{
adj[u][v] = adj[v][u] = val;
}
void load_graph(char *filename)
{
int i, j, val, n;
char str[80];
FILE *fp = fopen(filename, "r");
if (fp != NULL) {
init_graph();
fscanf(fp, "%d", &n);
for (i = 0; i < n; i++) {
fscanf(fp, "%s", str);
insert_vertex(str[0]);
for (j = 0; j < n; j++) {
fscanf(fp, "%d", &val);
if (val != 0)
insert_edge(i, j, val);
}
}
}
fclose(fp);
}
int visited[MAX_VTXS]; // 방문 여부를 기록하는 배열
void reset_visited() // 방문 여부를 기록하는 배열을 초기화하는 함수
{
int i;
for (i = 0; i < vsize; i++) {
visited[i] = 0;
}
}
void DFS(int v)
{
int w;
visited[v] = 1;
printf("%c ", vdata[v]);
for (w = 0; w < vsize; w++)
if (adj[v][w] != 0 && visited[w] == 0)
DFS(w);
}
void main()
{
load_graph("graph.txt");
reset_visited();
printf("DFS ===> ");
DFS(0);
printf("\n");
}
DFS ===> A B D C E G H F
인접 리스트를 이용한 그래프의 깊이 우선 탐색
void DFS(int v)
{
GNode *p;
visited[v] = 1;
printf("%c ", vdata[v]);
for(p = adj[v]; p != NULL; p = p -> link)
if(visited[p -> id] == 0)
DFS(p -> id);
}
너비 우선 탐색
시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법
5. 연결 성분 검사
연결 성분의 개수를 구하는 것이 핵심이다.
연결 성분 ; 최대로 연결된 부분 그래프
깊이 우선 탐색이나 너비 우선 탐색을 이용해서 연결성분을 찾을 수 있다.
6. 신장트리
그래프 내의 모든 정점을 포함하는 트리
신장트리도 트리의 일종이므로 모든 정점들이 연결되어 있고 사이클이 없어야 한다.