그래프
객체 간의 연결 관계를 표현하는 자료구조 → 매우 일반적인 자료구조
오일러 문제
모든 다리를 한 번만 건너서 처음 출발했던 장소로 돌아오는 문제
- 용어 표현
- 위치 → 정점(node)
- 다리 → 간선(edge)
- 오일러 정리
- 모든 정점에 연결된 간선의 수가 짝수이면 오일러 경로 존재
그래프 정의
- 수학적 표현 : G = (V, E)
- V는 정점(vertex)들의 집합
- E는 간선(edge)들의 집합
- 정점들 간의 관계를 의미
- 정점들 간의 관계를 의미
→ 정점들과 간선들의 각 유한집합의 자료구조
그래프의 종류
- 간선의 종류
- 무방향 간선 : 간선을 통해 양방향으로 갈 수 있음
- (A, B)
- (A, B) = (B, A)
- 방향 간선 : 한쪽 방향으로만 갈 수 있음
- <A, B>
- <A.B> ≠ <B, A>
- 무방향 간선 : 간선을 통해 양방향으로 갈 수 있음
- 그래프 종류
- 무방향 그래프
- 방향 그래프
- 가중치 그래프, 네트워크
- 간선에 비용이나 가중치가 할당된 그래프
- 간선에 비용이나 가중치가 할당된 그래프
- 그래프 이론
그래프 용어
인접 정점(adjacent vertex)
: 간선에 의해 연걸된 정점- 정점 0과 정점 1은 서로 인접
- 정점 0과 정점 1은 서로 인접
차수(degree)
: 정점에 연결된 다른 정점의 수- 정점 0의 차수는 3
- 정점 0의 차수는 3
경로(path)
: 정점의 나열로 표현- 단순경로 : 0,1,2,3
- 사이클(cycle) : 0,1,2,0
경로의 길이
: 경로를 구성하는 데 사용된 간선의 수- 단순경로 0 - 1 - 2 - 3의 길이 = 3
- 단순경로 0 - 1 - 2 - 3의 길이 = 3
완전그래프
: 모든 정점이 연결되어 있는 그래프- 정점 수를 n일 때 간선 수는 n(n-1)/2
그래프 ADT
- 객체
- 정점의 집합과 간선의 집합
- 정점의 집합과 간선의 집합
- 연산
create_graph
::= 그래프를 생성한다.init(g)
::= 그래프 g를 초기화한다.insert_vertex(g, v)
::= 그래프 g에 정점 v를 삽입한다.insert_edge(g, u, v)
::= 그래프 g에 간선(u, v)을 삽입한다.delete_vertex(g, v)
::= 그래프 g의 정점 v를 삭제한다.delete_edge(g, u, v)
::= 그래프 g의 간선(u, v)을 삭제한다.is_empty(g)
::= 그래프 g가 공백 상태인지 확인한다.adjacent(v)
::= 정점 v에 인접한 정점들의 리스트를 반환한다.search(v)
::= 그래프 g에서 정점 v를 찾는다.destroy_graph(g)
::= 그래프 g를 제거한다.
그래프 구현 방법
인접 행렬 방법 - 2차원 배열 사용
- 간선( i, j )이 그래프에 존재하면 2차원 배열을 1로 지정
- 간선( i, j)이 그래프에 존재하지 않으면 0으로 지정한다.
- 인접 행렬 구조체 코드 구현
#define MAX_VERTICES 50
typedef struct GraphType{
int n;
int adj_mat[MAX_VERTICES][MAX_VERTICES];
}GraphType;
인접리스트 방법
- 각 정점에 인접한 정점들을 연결리스트로 표현
- 자료형
- 인접 리스트 구조체 코드 구현
#define MAX_VERTICES 50
typedef struct GraphNode{
int vertex;
struct GraphNode *link;
}GraphNode;
typedef struct GraphType{
int n;
GraphNode *adj_list[MAX_VERTICES];
}Graphtype;
그래프 탐색
- 그래프의 가장 기본적인 연산
- 한 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문
- 많은 문제의 해결법 → 그래프의 노드를 탐색하는 것으로 해결
- 해(solution) = 출발지에서 목적지까지의 경로
- 해(solution) = 출발지에서 목적지까지의 경로
- 2가지 방법 존재
- 깊이 우선 탐색(DFS)
- 너비 우선 탐색(BFS)
깊이 우선 탐색(DFS)
- 미로탐색처럼, 한 방향으로 갈 수 있을 때까지 계속 간다.
- 더 이상 갈 수 없게 되면, 다시 가장 가까운 갈림길로 돌아온다.
- 이곳으로부터 다른 방향으로 다시 탐색을 진행한다.
- 인접 행렬 깊이 우선 탐색 프로그램 코드
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_VERTICES 50
typedef struct GraphType{
int n; // 정점의 개수
int adj_mat[MAX_VERTICES][MAX_VERTICES];
}GraphType;
int visited[MAX_VERTICES];
void init(GraphType* g){
int r,c;
g -> n = 0;
for(r = 0; r < MAX_VERTICES; r++)
for(c =0; c < MAX_VERTICES; c++)
g -> adj_mat[r][c] = 0;
}
// 정점 추가
void insert_vertex(GraphType* g, int v){
if(((g->n)+1) > MAX_VERTICES){
fprintf(stderr,"그래프 : 정점 개수 초과");
return;
}
g -> n++;
}
// 간선 삽입
void insert_edge(GraphType* g, int start, int end){
if(start >= g->n || end >= g->n){
fprintf(stderr,"그래프 : 정점 번호 오류");
return;
}
g -> adj_mat[start][end] = 1;
g -> adj_mat[end][start] = 1;
}
// 인접 행렬로 표현된 그래프에 대한 깊이 우선 탐색
void dfs_mat(GraphType* g, int v){
int w;
visited[v] = TRUE;
printf("정점 %d -> ",v); // 방문한 정점 출력
for(w = 0; w<g->n;w++)
if(g->adj_mat[v][w] && !visited[w]) dfs_mat(g,w); // 정점 w에서 DFS 새로 시작
}
int main(){
GraphType *g;
g = (GraphType*)malloc(sizeof(GraphType));
init(g);
for(int i = 0; i <4; i++) insert_vertex(g,i);
insert_edge(g,0,1);
insert_edge(g,0,2);
insert_edge(g,0,3);
insert_edge(g,1,2);
insert_edge(g,2,3);
printf("깊이 우선 탐색\n");
dfs_mat(g,0);
free(g);
}
- 인접 리스트 깊이 우선 탐색 프로그램
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 50
typedef struct GraphNode
{
int vertex;
struct GraphNode* link;
}GraphNode;
typedef struct GraphType{
int n; // 정점의 개수
GraphNode* adj_list[MAX_VERTICES];
}GraphType;
// 그래프 초기화
void init(GraphType* g){
int v;
g -> n = 0;
for (v = 0; v < MAX_VERTICES; v++) g -> adj_list[v] = NULL;
}
// 정점 삽입 연산
void insert_vertex(GraphType* g,int v){
if(((g ->n)+1)> MAX_VERTICES){
fprintf(stderr,"그래프: 정점 개수 초과");
return;
}
g -> n++;
}
// 간선 삽입 연산, v를 u의 인접 리스트에 삽입한다.
// 정점 u에 간선 (u,v)를 삽입하는 연산
void insert_edge(GraphType* g, int u, int v){
GraphNode* node;
if(u >= g->n || v >= g->n){
fprintf(stderr,"그래프 : 정점 번호 오류");
}
node = (GraphNode*)malloc(sizeof(GraphNode));
node -> vertex = v;
node -> link = g -> adj_list[u];
g -> adj_list[u] = node;
}
void print_adj_list(GraphType* g){
for(int i = 0; i <g->n; i++){
GraphNode* p = g -> adj_list[i];
printf("정점 %d의 인접리스트 ", i);
while(p!=NULL){
printf("-> %d ", p->vertex);
p = p-> link;
}
printf("\n");
}
}
int main(){
GraphType* g;
g = (GraphType*)malloc(sizeof(GraphType));
init(g);
for(int i = 0; i < 4; i++) insert_vertex(g,i);
insert_edge(g,0,1);
insert_edge(g,1,0);
insert_edge(g,0,2);
insert_edge(g,2,0);
insert_edge(g,0,3);
insert_edge(g,3,0);
insert_edge(g,1,2);
insert_edge(g,2,1);
insert_edge(g,1,2);
insert_edge(g,2,3);
insert_edge(g,3,2);
print_adj_list(g);
free(g);
return 0;
}
<출력>
정점 0의 인접리스트 -> 3 -> 2 -> 1
정점 1의 인접리스트 -> 2 -> 2 -> 0
정점 2의 인접리스트 -> 3 -> 1 -> 0
정점 3의 인접리스트 -> 2 -> 0
너비 우선 탐색(BFS)
- 시작 정점으로부터 가까운 정점을 먼저 방문
- 멀리 떨어져 있는 정점을 나중에 방문하는 순회법
→ 큐를 이용해서 구현
- BFS 알고리즘
- 큐에 정점을 넣고 방문 표시
- 큐에서 dequeue를 통해 가장 앞에 있는 정점을 가져옴(여기서는 정점 0)
- 정점과 연결된 노드들 중 아직 방문하지 않은 노드를 순서대로 큐에 넣고 방문 표시
- 큐가 빌 때까지 2~3번 반복
- 큐를 이용한 너비우선탐색(BFS) 프로그램
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_QUEUE_SIZE 10
typedef int element;
typedef struct{ // 큐 타입
element queue[MAX_QUEUE_SIZE];
int front, rear;
}QueueType;
// 오류 함수
void error(const char* message){
fprintf(stderr,"%s\n",message);
exit(1);
}
// 큐 초기화
void queue_init(QueueType *q){
q -> front = q-> rear = 0;
}
// 공백 상태 검출
int is_empty(QueueType *q){
return (q->front == q ->rear);
}
// 포화 상태 검출
int is_full(QueueType *q){
return ((q->rear+1) % MAX_QUEUE_SIZE == q->front);
}
// 큐 삽입
void enqueue(QueueType* q, element item){
if(is_full(q)) error("큐가 포화상태");
q->rear = (q->rear +1) % MAX_QUEUE_SIZE;
q->queue[q->rear] = item;
}
// 큐 삭제
element dequeue(QueueType *q){
if(is_empty(q)) error("큐가 공백");
q->front = (q->front+1) % MAX_QUEUE_SIZE;
return q-> queue[q->front];
}
#define MAX_VERTICES 50
typedef struct GraphType{
int n; // 정점의 개수
int adj_mat[MAX_VERTICES][MAX_QUEUE_SIZE];
}GraphType;
int visited[MAX_VERTICES];
// 그래프 초기화
void graph_init(GraphType* g){
int r,c;
g->n = 0;
for(r = 0; r< MAX_VERTICES; r++)
for(c = 0; c < MAX_VERTICES; c++)
g-> adj_mat[r][c] = 0;
}
// 정점 삽입 연산
void insert_vertex(GraphType* g,int v){
if(((g ->n)+1)> MAX_VERTICES){
fprintf(stderr,"그래프: 정점 개수 초과");
return;
}
g -> n++;
}
// 정점 u에 간선 (u,v)를 삽입하는 연산
void insert_edge(GraphType* g, int start, int end){
if(start >= g->n || end >= g->n){
fprintf(stderr,"그래프 : 정점 번호 오류");
}
g -> adj_mat[start][end] = 1;
g-> adj_mat[end][start] = 1;
}
void bfs_mat(GraphType* g, int v){
int w;
QueueType q;
queue_init(&q); // 큐 초기화
visited[v] = TRUE; // 정점 v 방문 표시
printf("%d 방문 -> ", v);
enqueue(&q,v); // 시작 정점을 큐에 저장
while(!is_empty(&q)){
v = dequeue(&q); // 큐에 정점 추출
for(w = 0; w < g -> n; w++) // 인접 정점 탐색
if(g->adj_mat[v][w] && !visited[w]){
visited[w] = TRUE; // 방문 표시
printf("%d 방문 ->",w);
enqueue(&q,w); // 방문한 정점을 큐에 저장
}
}
}
int main(){
GraphType* g;
g = (GraphType*)malloc(sizeof(GraphType));
graph_init(g);
for(int i = 0; i < 6; i++) insert_vertex(g,i);
insert_edge(g,0,2);
insert_edge(g,2,1);
insert_edge(g,2,3);
insert_edge(g,0,4);
insert_edge(g,4,5);
insert_edge(g,1,5);
bfs_mat(g,0);
printf("\n");
free(g);
return 0;
}
<출력>
정점 0의 인접리스트 -> 3 -> 2 -> 1
정점 1의 인접리스트 -> 2 -> 2 -> 0
정점 2의 인접리스트 -> 3 -> 1 -> 0
정점 3의 인접리스트 -> 2 -> 0
- 인접리스트를 이용한 버전 BFS
// 위의 나머지 부분은 인접 리스트를 이용해서 DFS를 이용할때와 같음
// 큐 구현 생략
void bfs_list(GraphType* g, int v){
GraphNode* w;
QueueType q;
init(&q);
visited[v] = TRUE;
printf("%d 방문 ->", v);
enqueue(&q,v);
while(!is_empty(&q)){
v = dequeue(&q);
for(w = g->adj_list[v]; w; w = w->link)
if(!visited[w->vertex]){
visited[w->vertex] = TRUE;
printf("%d 방문 -> ",w->vertex);
enqueue(&q, w-> vertex)
}
}
}