728x90
11.1 최소 비용 신장 트리
신장 트리(Spanning tree)
- 그래프내의 모든 정점을 포함하는 트리
- 모든 정점들이 연결되어 하고 사이클을 포함하면 안된다.
- 그래프에 있는 n개의 정점을 정확히 (n-1)개의 간선으로 연결해야 한다.
최소 비용 신장 트리(MST, Minimum Spanning Tree)
- 신장 트리 중 사용된 간선들의 가중치 합이 최소인 신장 트리이다.
11.2 크루스칼의 MST 알고리즘
- 그리디 알고리즘 사용: 선택할 대마다 그 순간 가장 좋다고 생각되는 것을 선택하는 방법
- 그리디 알고리즘에서 순간의 선택은 그 당시에는 최적이다.
- 하지만 최적의 지역적인 해답을 모은것이 반드시 전역적으로 최적이라는 보장은 없다.
- 가중치를 기준으로 간선을 정렬 후 MST가 될 때까지 간선을 하나씩 선택 or 삭제하는 알고리즘
오름차순
- 낮은 가중치 간선부터 시작해서 하나씩 그래프에 추가
- 사이클을 형성하는 간선은 추가하지 않는다.
- 간선 + 1 = 정점
내림차순
- 높은 가중치의 간선부터 시작해서 하나씩 그래프에서 삭제
- 두 정점을 연결하는 다른 경로가 없을 경우 해당 간선을 제거하지 않는다.
- 간선 + 1 = 정점
더보기
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_VERTICES 100
#define INF 1000
int parent[MAX_VERTICES]; // 부모 노드
// 초기화
void set_init(int n)
{
for (int i = 0; i<n; i++)
parent[i] = -1;
}
// curr가 속하는 집합을 반환한다.
int set_find(int curr)
{
if (parent[curr] == -1)
return curr; // 루트
while (parent[curr] != -1) curr = parent[curr];
return curr;
}
// 두개의 원소가 속한 집합을 합친다.
void set_union(int a, int b)
{
int root1 = set_find(a); // 노드 a의 루트를 찾는다.
int root2 = set_find(b); // 노드 b의 루트를 찾는다.
if (root1 != root2) // 합한다.
parent[root1] = root2;
}
struct Edge { // 간선을 나타내는 구조체
int start, end, weight;
};
typedef struct GraphType {
int n; // 간선의 개수
struct Edge edges[2 * MAX_VERTICES];
} GraphType;
// 그래프 초기화
void graph_init(GraphType* g)
{
g->n = 0;
for (int i = 0; i < 2 * MAX_VERTICES; i++) {
g->edges[i].start = 0;
g->edges[i].end = 0;
g->edges[i].weight = INF;
}
}
// 간선 삽입 연산
void insert_edge(GraphType* g, int start, int end, int w)
{
g->edges[g->n].start = start;
g->edges[g->n].end = end;
g->edges[g->n].weight = w;
g->n++;
}
// qsort()에 사용되는 함수
int compare(const void* a, const void* b)
{
struct Edge* x = (struct Edge*)a;
struct Edge* y = (struct Edge*)b;
return (x->weight - y->weight);
}
// kruskal의 최소 비용 신장 트리 프로그램
void kruskal(GraphType *g)
{
int edge_accepted = 0; // 현재까지 선택된 간선의 수
int uset, vset; // 정점 u와 정점 v의 집합 번호
struct Edge e;
set_init(g->n); // 집합 초기화
qsort(g->edges, g->n, sizeof(struct Edge), compare);
printf("크루스칼 최소 신장 트리 알고리즘 \n");
int i = 0;
while (edge_accepted < (g->n - 1)) // 간선의 수 < (n-1)
{
e = g->edges[i];
uset = set_find(e.start); // 정점 u의 집합 번호
vset = set_find(e.end); // 정점 v의 집합 번호
if (uset != vset) { // 서로 속한 집합이 다르면
printf("간선 (%d,%d) %d 선택\n", e.start, e.end, e.weight);
edge_accepted++;
set_union(uset, vset); // 두개의 집합을 합친다.
}
i++;
}
}
int main(void)
{
GraphType *g;
g = (GraphType *)malloc(sizeof(GraphType));
graph_init(g);
insert_edge(g, 0, 1, 29);
insert_edge(g, 1, 2, 16);
insert_edge(g, 2, 3, 12);
insert_edge(g, 3, 4, 22);
insert_edge(g, 4, 5, 27);
insert_edge(g, 5, 0, 10);
insert_edge(g, 6, 1, 15);
insert_edge(g, 6, 3, 18);
insert_edge(g, 6, 4, 25);
kruskal(g);
free(g);
return 0;
}
11.3 프림의 MST 알고리즘
- 시작 정점에서부터 출발하여 신장 트리 집합을 단계적으로 확장해나가는 방법
- 트리가 n- 1개의 간선을 가질 때까지 계속된다.
더보기
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_VERTICES 100
#define INF 1000L
typedef struct GraphType {
int n; // 정점의 개수
int weight[MAX_VERTICES][MAX_VERTICES];
} GraphType;
int selected[MAX_VERTICES];
int distance[MAX_VERTICES];
// 최소 dist[v] 값을 갖는 정점을 반환
int get_min_vertex(int n)
{
int v, i;
for (i = 0; i <n; i++)
if (!selected[i]) {
v = i;
break;
}
for (i = 0; i < n; i++)
if (!selected[i] && (distance[i] < distance[v])) v = i;
return (v);
}
//
void prim(GraphType* g, int s)
{
int i, u, v;
for (u = 0; u<g->n; u++)
distance[u] = INF;
distance[s] = 0;
for (i = 0; i<g->n; i++) {
u = get_min_vertex(g->n);
selected[u] = TRUE;
if (distance[u] == INF) return;
printf("정점 %d 추가\n", u);
for (v = 0; v<g->n; v++)
if (g->weight[u][v] != INF)
if (!selected[v] && g->weight[u][v]< distance[v])
distance[v] = g->weight[u][v];
}
}
int main(void)
{
GraphType g = { 7,
{{ 0, 29, INF, INF, INF, 10, INF },
{ 29, 0, 16, INF, INF, INF, 15 },
{ INF, 16, 0, 12, INF, INF, INF },
{ INF, INF, 12, 0, 22, INF, 18 },
{ INF, INF, INF, 22, 0, 27, 25 },
{ 10, INF, INF, INF, 27, 0, INF },
{ INF, 15, INF, 18, 25, INF, 0 } }
};
prim(&g, 0);
return 0;
}
11.5 다익스트라의 최단 경로 알고리즘
- 네트워크에서 하나의 시작 정점으로부터 모든 다른 정점까지의 최단 경로를 찾는 알고리즘이다.
1. S 정점에서 시작
2. A정점을 거쳐가는 최단 경로 탐색
3. C정점을 거쳐가는 최단 경로 탐색
4. D 정점을 거쳐가는 최단 경로 탐색
5. B, E 정점을 거쳐가는 최단 경로 탐색
더보기
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define TRUE 1
#define FALSE 0
#define MAX_VERTICES 100
#define INF 1000000 /* 무한대 (연결이 없는 경우) */
typedef struct GraphType {
int n; // 정점의 개수
int weight[MAX_VERTICES][MAX_VERTICES];
} GraphType;
int distance[MAX_VERTICES];/* 시작정점으로부터의 최단경로 거리 */
int found[MAX_VERTICES]; /* 방문한 정점 표시 */
int choose(int distance[], int n, int found[])
{
int i, min, minpos;
min = INT_MAX;
minpos = -1;
for (i = 0; i<n; i++)
if (distance[i]< min && !found[i]) {
min = distance[i];
minpos = i;
}
return minpos;
}
void print_status(GraphType* g)
{
static int step=1;
printf("STEP %d: ", step++);
printf("distance: ");
for (int i = 0; i < g->n; i++) {
if (distance[i] == INF)
printf(" * ");
else
printf("%2d ", distance[i]);
}
printf("\n");
printf(" found: ");
for (int i = 0; i<g->n; i++)
printf("%2d ", found[i]);
printf("\n\n");
}
//
void shortest_path(GraphType* g, int start)
{
int i, u, w;
for (i = 0; i<g->n; i++) /* 초기화 */
{
distance[i] = g->weight[start][i];
found[i] = FALSE;
}
found[start] = TRUE; /* 시작 정점 방문 표시 */
distance[start] = 0;
for (i = 0; i<g->n-1; i++) {
print_status(g);
u = choose(distance, g->n, found);
found[u] = TRUE;
for (w = 0; w<g->n; w++)
if (!found[w])
if (distance[u] + g->weight[u][w]<distance[w])
distance[w] = distance[u] + g->weight[u][w];
}
}
int main(void)
{
GraphType g = { 7,
{{ 0, 7, INF, INF, 3, 10, INF },
{ 7, 0, 4, 10, 2, 6, INF },
{ INF, 4, 0, 2, INF, INF, INF },
{ INF, 10, 2, 0, 11, 9, 4 },
{ 3, 2, INF, 11, 0, INF, 5 },
{ 10, 6, INF, 9, INF, 0, INF },
{ INF, INF, INF, 4, 5, INF, 0 } }
};
shortest_path(&g, 0);
return 0;
}
11.6 플로이드 최단 경로 알고리즘
- 그래프에 존재하는 모든 정점 사이의 최단 경로를 한번에 찾아주는 알고리즘
- 2차원 인접 행렬 구성
- 정점의 개수만큼 라운드를 진행
- 각 라운드마다 새로운 경로로 사용 가능한 더 짧은 길이를 선택하는 과정을 반복한다.
초기상태
1,2 라운드
3,4 라운드
5라운드
총 5개의 정점이 존재함으로 5번의 라운드를 진행하고 알고리즘을 종료
더보기
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define MAX_VERTICES 100
#define INF 1000000 // 무한대 (연결이 없는 경우를 나타냄)
// 그래프를 나타내는 구조체
typedef struct GraphType {
int n; // 정점의 개수
int weight[MAX_VERTICES][MAX_VERTICES]; // 가중치 행렬
} GraphType;
int A[MAX_VERTICES][MAX_VERTICES]; // 최단 거리 행렬
// 최단 거리 행렬 출력 함수
void printA(GraphType *g) {
int i, j;
printf("===============================\n");
for (i = 0; i < g->n; i++) {
for (j = 0; j < g->n; j++) {
if (A[i][j] == INF)
printf(" * "); // 무한대는 '*'로 표시
else
printf("%3d ", A[i][j]);
}
printf("\n");
}
printf("===============================\n");
}
// 플로이드-워셜 알고리즘을 이용한 최단 거리 계산 함수
void floyd(GraphType* g) {
int i, j, k;
// 초기 거리 행렬 설정 (입력된 그래프의 가중치 행렬을 복사)
for (i = 0; i < g->n; i++)
for (j = 0; j < g->n; j++)
A[i][j] = g->weight[i][j];
printA(g); // 초기 거리 행렬 출력
// 모든 정점 쌍에 대해 최단 거리 계산 수행
for (k = 0; k < g->n; k++) { // k는 경유지 정점
for (i = 0; i < g->n; i++) { // i는 출발 정점
for (j = 0; j < g->n; j++) { // j는 도착 정점
// 현재 i에서 j까지의 최단 거리보다, i -> k -> j 경로를 거치는 것이 더 짧다면 갱신
if (A[i][k] + A[k][j] < A[i][j])
A[i][j] = A[i][k] + A[k][j]; // 최단 거리 갱신
}
}
printA(g); // 갱신된 거리 행렬 출력
}
}
int main(void) {
// 예제 그래프 초기화 (7개의 정점, 가중치 행렬 정의)
GraphType g = { 7,
{{ 0, 7, INF, INF, 3, 10, INF },
{ 7, 0, 4, 10, 2, 6, INF },
{ INF, 4, 0, 2, INF, INF, INF },
{ INF, 10, 2, 0, 11, 9, 4 },
{ 3, 2, INF, 11, 0, INF, 5 },
{ 10, 6, INF, 9, INF, 0, INF },
{ INF, INF, INF, 4, 5, INF, 0 } }
};
floyd(&g); // 플로이드-워셜 알고리즘 실행
return 0;
}
11.7 위상 정렬
- 사이클이 없는 방향 그래프에서 정점을 선형으로 정렬
- 순서가 정해진 작업을 수행해야 할 때 그 순서를 결정
- 여러 경우의 수가 있을 수 있다.
- 입학 → 1 → 2 → 3 → 4 → 졸업
↓→→→→→→→→→→→→↑
시험 (예를 들어 시험을 통해 한번에 졸업하는 경우)
위상 정렬 진행 순서
- 진입 차수가 0인 정점을 큐에 삽입
- 큐에서 원소를 꺼내 연결된 모든 간선을 제거
- 큐가 빌때까지 반복
※ 모든 원소가 방문 전 큐가 비어있으면 사이클이 존재
※ 진입차수: 들어오는 간선의 수
※ 진출차수: 나가는 간선의 수
더보기
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#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 graph_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;
node->link = g->adj_list[u];
g->adj_list[u] = node;
}
#define MAX_STACK_SIZE 100
typedef int element;
typedef struct {
element stack[MAX_STACK_SIZE];
int top;
} StackType;
// 스택 초기화 함수
void init(StackType *s)
{
s->top = -1;
}
// 공백 상태 검출 함수
int is_empty(StackType *s)
{
return (s->top == -1);
}
// 포화 상태 검출 함수
int is_full(StackType *s)
{
return (s->top == (MAX_STACK_SIZE - 1));
}
// 삽입함수
void push(StackType *s, element item)
{
if (is_full(s)) {
fprintf(stderr, "스택 포화 에러\n");
return;
}
else s->stack[++(s->top)] = item;
}
// 삭제함수
element pop(StackType *s)
{
if (is_empty(s)) {
fprintf(stderr, "스택 공백 에러\n");
exit(1);
}
else return s->stack[(s->top)--];
}
// 위상정렬을 수행한다.
int topo_sort(GraphType *g)
{
int i;
StackType s;
GraphNode *node;
// 모든 정점의 진입 차수를 계산
int *in_degree = (int *)malloc(g->n * sizeof(int));
for (i = 0; i < g->n; i++) // 초기화
in_degree[i] = 0;
for (i = 0; i < g->n; i++) {
GraphNode *node = g->adj_list[i];//정점 i에서 나오는 간선들
while (node != NULL) {
in_degree[node->vertex]++;
node = node->link;
}
}
// 진입 차수가 0인 정점을 스택에 삽입
init(&s);
for (i = 0; i < g->n; i++) {
if (in_degree[i] == 0) push(&s, i);
}
// 위상 순서를 생성
while (!is_empty(&s)) {
int w;
w = pop(&s);
printf("정점 %d ->", w); //정점 출력
node = g->adj_list[w]; //각 정점의 진입 차수를 변경
while (node != NULL) {
int u = node->vertex;
in_degree[u]--; //진입 차수를 감소
if (in_degree[u] == 0) push(&s, u);
node = node->link; // 다음 정점
}
}
free(in_degree);
printf("\n");
return (i == g->n); // 반환값이 1이면 성공, 0이면 실패
}
//
int main(void)
{
GraphType g;
graph_init(&g);
insert_vertex(&g, 0);
insert_vertex(&g, 1);
insert_vertex(&g, 2);
insert_vertex(&g, 3);
insert_vertex(&g, 4);
insert_vertex(&g, 5);
//정점 0의 인접 리스트 생성
insert_edge(&g, 0, 2);
insert_edge(&g, 0, 3);
//정점 1의 인접 리스트 생성
insert_edge(&g, 1, 3);
insert_edge(&g, 1, 4);
//정점 2의 인접 리스트 생성
insert_edge(&g, 2, 3);
insert_edge(&g, 2, 5);
//정점 3의 인접 리스트 생성
insert_edge(&g, 3, 5);
//정점 4의 인접 리스트 생성
insert_edge(&g, 4, 5);
//위상 정렬
topo_sort(&g);
// 동적 메모리 반환 코드 생략
return 0;
}
728x90
반응형
'C언어로 쉽게 풀어쓴 자료구조' 카테고리의 다른 글
[C언어로 쉽게 풀어쓴 자료구조] 12장 정렬 (0) | 2025.01.31 |
---|---|
[C언어로 쉽게 풀어쓴 자료구조] 10장 그래프 Ⅰ (1) | 2025.01.14 |
[C언어로 쉽게 풀어쓴 자료구조] 9장 우선순위 큐 (0) | 2025.01.02 |
[C언어로 쉽게 풀어쓴 자료구조] 8장 트리 - Ⅱ (0) | 2024.12.12 |
[C언어로 쉽게 풀어쓴 자료구조] 8장 트리 - Ⅰ (0) | 2024.11.24 |