Computer Science/자료구조

비선형 자료구조 (그래프)

Luinesse 2023. 9. 11. 20:45
반응형

이번 포스트에서는 비선형 자료구조 중 하나인 그래프에 대하여 설명하겠습니다.

 

그래프

그래프는 간선으로 연결된 정점들의 모음을 표현한 자료구조입니다.

이때 정점과 정점을 연결하는 간선들이 방향을 가지지 않는 경우, 이 그래프를 무방향 그래프라고 합니다.

반대로, 방향을 가지는 경우 방향 그래프라고 정의합니다.

 

부분그래프는 그래프 G의 정점 V와 간선 E가 있을 때, 부분그래프 G'는 다음 수식을 만족합니다.

G' = (V', E'), V' 는 V에 속하고, E'는 E에 속해야합니다.

 

무방향 그래프 G 내 임의의 서로 다른 두 정점 u, v에 대해, u에서 v로 가는 경로가 존재한다면, G는 연결 되었다고 합니다.

이때, G 내에서 최대로 연결된 부분그래프를 연결요소 라고한다.

 

반대로 방향 그래프 G 에서 위 조건을 만족하면 각각 강력연결, 강력연결요소 라고합니다.

 

트리 또한 그래프 중 하나로 구별됩니다.

 

신장트리는 그래프 G의 모든 정점을 포함하면서 G 내 간선들로만 구성된 트리를 의미합니다.

이때, 최소비용 신장트리는 최소 비용을 갖는 신장 트리를 의미합니다.

여기서 비용은 가중치가 부여된 무방향 그래프 G의 신장 트리 T 내의 간선들의 가중치 합으로 정의됩니다.

최소비용 신장트리는 추후 포스트에서 서술할 크루스칼 알고리즘, 프림 알고리즘 등으로 찾을 수 있습니다.

 

그래프의 표현법

그래프는 인접 행렬 기반 방법과 인접 리스트 기반 방법으로 표현합니다.

인접행렬은 V^2의 공간을 요구하고, 인접리스트는 V+E의 공간을 요구합니다.

따라서, 그래프에서 표현할 양이 적다면, 인접리스트의 방법이 효율적이고, 그렇지 않다면 인접행렬이 효율적일 수 있습니다.

 

먼저 인접행렬은 정점 수가 n인 그래프를 n * n 크기의 2차원 배열 M에 표현하는 방법으로, 정점 i에서 정점 j로의 간선이 존재한다면, M[i][j]에 1을 저장하고, 그렇지않다면 0으로 저장하는 방법입니다.

 

1
2
3
4
5
6
#define N 4                //
int g[N][N];
 
void addEdge(int s, int e) {
    g[s][e] = g[e][s] = 1;                    //무방향 그래프인 경우로 해석하기에 양쪽 정점으로 다 간선이 있다고 가정
}
cs

다음은 인접리스트 방법입니다. 인접리스트는 정점 수 n인 그래프를 크기 n의 연결리스트 배열 L로 표현하는 방법으로, L[i]가 정점 i의 인접 정점들의 리스트를 참조하도록 표현합니다. 여기서 인접이란, 하나의 간선으로 연결된 두 정점들을 인접 하다고 정의합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct Node {
    int v;
    struct Node *next;
} Node;
 
#define N 4
#define E 5
 
Node* head[N];
int e[E][2= {{0,1}, {0,2}, {0,3}, {1,2}, {2,3}};
 
void addEdge(int s, int e) {
    Node* x = (Node *)malloc(sizeof(Node));
    x->= e;
    x->next = nullptr;
    x->next = head[s];
    head[s] = x;
}
cs

 

그래프의 탐색

그래프의 탐색 방법으로는 깊이우선탐색(DFS), 너비우선탐색(BFS)가 있습니다.

 

먼저 깊이우선탐색입니다. 정점 v에서 시작할 때, DFS는 정점 v를 방문한 후, v의 각 미방문 인접 노드 w에 대해 DFS를 다시 적용하는 탐색방법입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct Node {
    int v;
    struct Node* next;
}
 
void dfs(int v, int* visited) {
    visited[v] = 1;                            //방문 표시
    cout << v << "=>";
    for(Node* p = head[v]; p != nullptr; p = p->next) {            //현재 방문한 노드 v에 대해 인접한 노드를 모두 방문.
        if(!visited[p->v])    dfs(p->v, visited);                    //미방문 노드 발견 시 dfs를 해당 미방문노드를 기준으로 재실행.
    }
}
cs

다음으로 너비우선탐색입니다. 정점 v에서 시작할 때, BFS는 정점 v를 방문한 후, 큐에 삽입하고, 공백 큐가 아닌 동안 큐에서 삭제하여 얻은 정점의 각 미방문 인접 노드를 방문 및 큐에 삽입하는 절차를 반복하는 탐색방법입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
typedef struct Node {
    int v;
    struct Node* next;
}
 
Node* qHead = nullptr;
Node* qTail = nullptr;
 
void bfs(int v) {
    int visited[N] = {0};
    visited[v] = 1;
    cout << v << "=>";
    addQueue(v)                        //일반적으로 코드를 구현해보실때는 c에서 큐 라이브러리를 불러와서 enqueue 연산을 수행하면 됩니다.
    while(qHead != nullptr) {        //이 부분또한 큐가 공백인지 확인하는 empty()를 사용하셔도 됩니다.
        int v = removeQueue();        //이 부분도 동일합니다.
        for(Node *= gHead[v]; p != nullptr; p = p->next) {
            if(!visited[p->v]) {
                visited[p->v] = 1;
                cout << v << "=>";
                addQueue(p->v);
            }
        }
    }
}
 
void addQueue(int v) {                            //enqueue
    Node* x = (Node*)malloc(sizeof(Node));
    x->= v;
    x->next = nullptr;
    if(qHead == nullptr)    qHead = qTail = x;
    else {
        qTail->next = x;
        qTail = qTail->next;
    }
}
 
int removeQueue() {                                //dequeue
    if(qHead == nullptr)    exit(-1);
    Node* p =qHead;
    qHead = qHead->next;
    int v = p->v;
    free(p);
    if(qHead == nullptr)    qTail = nullptr;
    return v;
}        
cs

다음은 탐색 중 한 정점과 다른 한 정점으로의 최단경로를 찾는 탐색인 다익스트라 알고리즘입니다.

 

다익스트라 알고리즘은 최단경로가 결정되지 않은 노드 중 시작 노드 s로 부터 최단경로로 도달 가능한 노드 u를 찾고, 최단 경로 미결정 노드 중 u를 거쳐서 도달 할 수 있는 최단경로 미결정 노드들의 거리를 갱신하는 작업을 모든 노드의 최단경로를 찾을 때 까지 반복하는 탐색 알고리즘입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int findMin() {                        //방문하지 않는 노드 중 최단거리 노드 찾아서 리턴
    int minPos = -1;
    double min = DBL_MAX;
    for(int t = 0; t < N; t++) {
        if(!found[t] && dist[t] < min) {
            min = dist[t];
            minPos = t;
        }
    }
    return minPos;
}
 
void dijkstra(int s) {
    for(int t = 0; t < N; t++) {                //각 노드 초기화.
        dist[t] = DBL_MAX;
        found[t] = 0;
    }
    dist[s] = 0;                            //루트노드는 거리가 0
    for(int i = 0; i < N; i++) {
        int u = findMin();                    //최단거리 노드를 찾음
        found[u] = 1;                        //해당노드는 최단거리를 찾았다고 표현
        for(int v = 0; v < N; v++) {
            if(!found[v] && dist[u] + w[u][v] < dist[v]) {                //아직 최단거리를 찾지 않는 노드 중 u 노드를 거쳐서 도달할 수 있는 노드들의 거리를 갱신함. (u가 최단거리니까 거쳐야함)
                dist[v] = dist[u] + w[u][v];
            }
        }
    }
}
cs

이렇게 그래프에 대해서 포스트해보았습니다.

 

다음 포스트에서는 해시테이블에 대해서 포스트하도록 하겠습니다.

반응형