dew's CSE Studying
10 그래프1 (C언어로 쉽게 풀어쓴 자료구조) 본문
10.1 그래프란?
그래프의 소개
그래프(graph): 객체 사이의 연결 관계를 표현한 수 있는 자료구조
그래프의 역사
오일러(Euler)-Konigsberg의 다리 문제 해결 1736
10.2 그래프의 정의와 용어
그래프의 정의
그래프: 정점(vertex)과 간선(edges)들의 유한집합
G=(V,E)
V(G): 그래프 G의 정점들의 집합
E(G): 그래프 G의 간선들의 집합
정점(=노드): 여러 가지 특성을 가질 수 있는 객체
간선(=링크): 이러한 정점들 간의 관계
V(G1) = {0, 1, 2, 3}
E(G1) = {(0,1), (0,2), (0,3), (1,2)}
무방향 그래프와 방향 그래프
무방향 그래프(undirected graph): 간선을 통해서 양방향으로 갈 수 있음 (A,B)=(B,A)
방향 그래프(directed graph): 간선에 방향성이 존재하는 그래프 <A,B>≠<B,A>
네트워크
가중치 그래프(weighted graph), 네트워크(network): 간선에 비용이나 가중치가 할당된 그래프
사용: 도시와 도시를 연결하는 도로의 길이, 회로소자의 용량 표시, 통신망의 사용료 표시 등
부분 그래프(subgraph)
:어떤 그래프의 정점의 일부와 간선의 일부로 이루어진 그래프
V(S)⊆V(G)
E(S)⊆E(G)
정점의 차수
인접 정점(adjacent vertex): 간선에 의해 직접 연결된 정점
무방향 그래프에서 정점의 차수(degree)=그 정점에 인접한 정점의 수
무방향 그래프에서 모든 정점의 차수를 합하면 간선 수의 2배가 된다(하나의 간선이 두 개의 정점에 인접하기 때문)
방향 그래프에서는 외부에서 오는 간선의 개수를 진입 차수(in-degree), 외부로 향하는 간선의 개수를 진출 차수(out-degree)라고 한다
경로
무방향 그래프에서 정점 s로부터 정점 e까지의 경로=정점의 나열 -> (s,v1,v2,...,vk,e)
-이 경우 반드시 간선(s,v1),(v1,v2),...,(vk,e)가 존재해야 한다
-방향 그래프라면 반드시 간선 <s,v1>,<v1,v2>,...,<vk,e>가 존재해야 한다.
단순경로(simple path): 경로 중에서 반복되는 간선이 없는 경로
사이클(cycle): 단순 경로의 시작 정점과 종료 정점이 동일한 경로
연결 그래프(connected graph)
:무방향 그래프 G에 있는 모든 정점쌍에 대하여 항상 경로가 존재할 때
↔비연결 그래프(unconnected graph)
-트리는 그래프의 특수한 형태로서 사이클을 가지지 않는 그래프이다
완전 그래프(complete graph)
:그래프에 속해있는 모든 정점이 서로 연결되어 있는 그래프
무방향 완전 그래프의 정점 수를 n이라고 하면, 하나의 정점은 n-1개의 다른 정점으로 연결되므로
간선의 수 = nx(n-1)/2
10.3 그래프의 표현 방법
방법1: 인접행렬(adjacency matrix): 2차원 배열을 사용하여 그래프를 표현한다
방법2: 인접리스트(adjacency list): 연결 리스트를 사용하는 그래프를 표현한다
인접 행렬
그래프의 정점 수가 n이라면 nxn의 2차원 배열인 인접행렬 M의 각 원소를 할당한다
if(간선 (i,j)가 그래프에 존재) M[i][j]=1;
otherwise M[i][j]=0;
이때 인접 행렬의 대각선 성분은 모두 0으로 표시된다
(i,j)은 i->j와 j->i를 동시에 의미한다 => 대칭행렬이다!
따라서 무방향 그래프의 경우 배열의 상위삼각/하위삼각만 저장하면 메모리를 절약할 수 있다
n개의 정점을 가지는 그래프를 인접행렬로 표시하기 위해서는 간선의 수에 무관하게 항상 n^2개의 메모리 공간이 필요하다
-밀집그래프(dense graph): 적합
-희소그래프(sparse graph): 적합x(메모리 낭비가 크다)
[장점]
-두 정점을 연결하는 간선의 존재 여부를 O(1)시간 안에 즉시 알 수 있다
-정점의 차수도 인접 행렬의 행이나 열을 조사하면 O(n)의 연산에 의해 알 수 있다
정점i의 차수: 인접 배열의 i번째 행에 있는 값을 모두 더하면 된다
[단점]
-그래프에 존재하는 모든 간선의 수를 알아내려면 인접 행렬 전체를 조사->n^2번의 조사가 필요하게 되어 O(n^2)의 시간이 요구됨
인접 행렬을 이용한 그래프 추상 데이터 타입의 구현
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 50
typedef struct GraphType
{
int n; //정점의 개수
int adj_mat[MAX_VERTICES][MAX_VERTICES];
} GraphType;
//그래프 초기화
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 print_adj_mat(GraphType *g)
{
for (int i=0; i<g->n; i++) {
for (int j=0; j<g->n; j++) {
printf("%2d ", g->adj_mat[i][j]);
}
printf("\n");
}
}
void 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);
print_adj_mat(g);
free(g);
}
-이렇게 구현 시 한정된 개수의 정점까지만 그래프에 삽입할 수 있다
인접리스트
:그래프를 표현함에 있어 각각의 정점에 인접한 정점들을 연결리스트로 표현한 것
-연결리스트의 노드들: 인접 정점을 저장
-헤더노드들은 하나의 배열로 구성되어있다.
-정점의 번호만 알면 이 번호를 배열의 인덱스로 하여 각 정점의 연결 리스트에 쉽게 접근할 수 있다
-정점의 수가 n개이고 간선의 수가 e개인 무방향그래프를 표시하기 위해서는 n개의 연결리스트가 필요하고, n개의 헤더노드와 2e개의 노드가 필요하다
-인접행렬표현은 간선의 개수가 적은 희소 그래프의 표현에 적합
-간선(i,j)의 존재 여부나 정점 i의 차수를 알기 위해서는 연결리스트에 있는 노드의 수만큼(=정점차수만큼)의 시간이 필요하다=>O(n)
-전체 간선의 수를 알아내려면 O(n+e)의 연산이 요구된다.(헤더노드를 포함하여 모든 인접리스트를 조사해야함)
인접리스트를 이용한 그래프 추상 데이터타입의 구현
#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의 인접리스트에 삽입한다
void insert_edge(GraphType* g, int u, int v)
{
GraphNode* node;
if (u >= g->n || v >= g->n) {
fprintf(stderr, "그래프: 정점 번호 오류");
return;
}
node = (GraphNode*)malloc(sizeof(GraphNode)); //간선을 나타내는 노드를 생성
node->vertex = v; //노드의 간선을 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, 2, 3);
insert_edge(g, 3, 2);
print_adj_list(g);
free(g);
return 0;
}
10.4 그래프의 탐색
탐색: 하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문하는 것
- 깊이 우선 탐색(DFS: depth first search)
- 너비 우선 탐색(BFS: breath first search)
10.5 깊이 우선 탐색
깊이 우선 탐색: 시작 정점에서 한 방향으로 계속 가다가 더 이상 갈 수 없게 되면 다시 가장 가까운 갈림길로 돌아와서 다른 방향으로 다시 탐색을 진행하는 방법
깊이 우선 탐색의 구현(인접 행렬 버전)
방법1: 순환호출을 이용 (얘를 사용할 거다)
방법2: 명시적인 스택을 사용하여 인접한 정점들을 스택에 저장했다가 다시 꺼내서 작업을 하는 것
//인접 행렬로 표현된 그래프에 대한 깊이 우선 탐색
void dfs_mat(GraphType* g, int v)
{
int w;
visited[v] = TRUE; //정점 v의 방문 표시
printf("정점 %d -> ", v); //방문한 정점 출력
for (w=0; w<g->n; w++) //인접 정점 탐색
if (g->adj_mat[v][w] && !visited[w])
dfs_mat(g,w); //정점 w에서 DFS새로 시작
}
배열 visited: 방문 여부를 기록하기 위해 사용
visited 배열값은 모두 FALSE로 초기화해놨다가 방문할 때마다 TRUE로 변경되도록 한다.
깊이 우선 탐색의 구현(인접 리스트 버전)
int visited[MAX_VERTICES];
//인접리스트로 표현된 그래프에 대한 깊이 우선 탐색
void dfs_list(GraphType* g, int v)
{
GraphNode* w;
visited[v] = TRUE; //정점 v의 방문 표시
printf("정점 %d -> ", v); //방문한 정점 출력
for (w=g->adj_list[v]; w; w=w->link) //인접 정점 탐색
if (!visited[w->vertex])
dfs_list(g, w->vertex); //정점 w에서 DFS 새로 시작
}
명시적인 스택을 이용한 깊이 우선 탐색의 구현
깊이 우선탐색의 분석
인접리스트로 표현: O(n+e)
인접행렬로 표현: O(n^2)
=>희소 그래프인 경우 깊이 우선 탐색은 인접 리스트가 인접 행렬보다 시간적으로 유리하다
10.6 너비 우선 탐색
너비 우선 탐색: 시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 방법
-가까운 거리에 있는 정점들을 차례로 저장한 후 꺼낼 수 있는 자료구조인 큐가 필요하다
너비 우선 탐색의 구현(인접 행렬 버전)
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_QUEUE_SIZE 20
typedef int element;
typedef struct {
element queue[MAX_QUEUE_SIZE];
int front, rear;
} QueueType;
//오류 함수
void error(char *message)
{
fprintf(stderr, "%s\n", message);
exit(1);
}
void queue_init(QueueType* q)
{
q->front = 0;
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;
}
//삭제 함수
int 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_VERTICES];
} 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++;
}
//간선 삽입 연산
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 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(void)
{
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);
printf("너비 우선 탐색\n");
bfs_mat(g, 0);
printf("\n");
free(g);
return 0;
}
너비 우선 탐색의 구현(인접 리스트 버전)
너비 우선 탐색의 분석
인접리스트: O(n+e)
인접행렬: O(n^2)
=>희소그래프를 사용할 경우 인접리스트 사용이 효율적이다
'3-1 > 자료구조 again' 카테고리의 다른 글
12 정렬 (C언어로 쉽게 풀어쓴 자료구조) (0) | 2024.11.25 |
---|---|
11 그래프2 (C언어로 쉽게 풀어쓴 자료구조) (2) | 2024.11.15 |
09 우선순위 큐 (C언어로 쉽게 풀어쓴 자료구조) (2) | 2024.10.23 |
08 트리 (C언어로 쉽게 풀어쓴 자료구조) (2) | 2024.10.23 |
07 연결리스트2 (C언어로 쉽게 풀어쓴 자료구조) (5) | 2024.10.19 |