반응형

 

 

 

 

 

 

 

 

1. 최단 경로 

: 정점 u와 정점 v가 연결되는 경로 중에서 간선들의 가중치 합이 최소가 되는 경로이다.

 

MST와 최단 경로의 차이

MST는 각 정점에 한 번씩 도달해야 하고, 총 비용은 가능한 모든 조합 중 최소여야 한다.

반면, 최단 경로는 가능한 최저 비용으로 시작 정점에서 목표 정점까지 도달하는 것이다.

 

알고리즘 -> 다익스트라, 플로이드

 

 

 

 

 

 

 

 

 

2. 다익스트라 알고리즘 - 최단경로

 

 

1) 동작 방식

 

 

 

 

 

 

2) 핵심 코드

 

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;
        previous[i] = start;
    }
    
    found[start] = TRUE;    /* 시작 정점 방문 표시 */
    distance[start] = 0;
    
    for (i = 0; i<g->n-1; i++) {
        
        u = choose(distance, g->n, found);
        found[u] = TRUE;
        
        print_path(start, u);
        
        printf("<%d>\n", distance[u]);

        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];
                    previous[w] = u;
                }
            }
        }
    }
}

 

if (distance[u] + g->weight[u][w] < distance[w]){

                    distance[w] = distance[u] + g->weight[u][w];

                    previous[w] = u;

}

: (u까지의 거리와 u에서 w까지의 거리를 더한 것)이 (w까지의 총 거리)보다 적으면

(w까지의 총 거리)를 (u까지의 거리와 u에서 w까지의 거리를 더한 것)으로 변경한다.

 

 

 

 

3) 전체 코드

 

#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 previous[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 print_path(int start, int end)     // 뒤에서부터 출력하기 위해 재귀로 구현
{
   int u = end ;
   if(start == end) {
      printf("%d -> ", start);
      return;
   }
   else {
      print_path(start, previous[u]);
      printf("%d -> ", u);
   }
}

//
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;
        previous[i] = start;
    }
    
    found[start] = TRUE;    /* 시작 정점 방문 표시 */
    distance[start] = 0;
    
    for (i = 0; i<g->n-1; i++) {
        
        u = choose(distance, g->n, found);
        found[u] = TRUE;
        
        print_path(start, u);
        
        printf("<%d>\n", distance[u]);

        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];
                    previous[w] = u;
                }
            }
        }
    }
}

// 그래프 초기화
void graph_init(GraphType *g)
{
     // 구현
    g->n = 0;
    for(int i=0; i<MAX_VERTICES; i++){
        for(int j=0; j<MAX_VERTICES; j++){
            if(i == j){
                g->weight[i][j] = 0;
            } else {
                g->weight[i][j] = INF;
            }
        }
    }
}

void insert_edge(GraphType *g, int start, int end, int key)
{
    if( start >= g->n || end >= g->n ){
        fprintf(stderr,"그래프 : 정점 번호 오류");
        return;
    }
     
     // ƒ⁄µÂ ª¿‘
    g->weight[start][end] = key;
    g->weight[end][start] = key;
}

/*  */
void read_graph(GraphType *g, char *filename)
{
     // 구현
    int number, u, v, key;
   
    FILE *fp;
    fp = fopen(filename, "rt");
   
    if (fp == NULL)
    {
        printf("file %s open error!\n", filename);
        return;
    }
   
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d\n", &u) != EOF && fscanf(fp, "%d\n", &v) != EOF && fscanf(fp, "%d\n", &key) != EOF){
       
        insert_edge(g, u, v, key);
    }

    fclose(fp);
}


int main(void)
{
    GraphType g;
    
    graph_init(&g);
    read_graph(&g, "input.txt");
    
    shortest_path(&g, 4);
    
    return 0;
}

 

 

 

 

 

 

4) 시작 정점부터 경로를 출력하기 위해 previous[u]라는 배열을 하나 더 선언하였다.

previous[u] = u 전의 노드이다.

 

void print_path(int start, int end)     // 뒤에서부터 출력하기 위해 재귀로 구현
{
   int u = end ;
   if(start == end) {
      printf("%d -> ", start);
      return;
   }
   else {
      print_path(start, previous[u]);
      printf("%d -> ", u);
   }
}

//
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;
        previous[i] = start;
    }
    
    found[start] = TRUE;    /* 시작 정점 방문 표시 */
    distance[start] = 0;
    
    for (i = 0; i<g->n-1; i++) {
        
        u = choose(distance, g->n, found);
        found[u] = TRUE;
        
        print_path(start, u);
        
        printf("<%d>\n", distance[u]);

        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];
                    previous[w] = u;
                }
            }
        }
    }
}

 

: 처음에 previous 배열을 모두 start로 초기화시켜준다.

: 그리고 노드를 하나씩 정해갈 때마다 previous[u]값을 설정한다.

: print_path에서 재귀를 통해 뒤에서부터 출력한다.

 

 

 

 

5) 시간 복잡도

: O(n^2)

 

 

 

 

 

 

 

3. 플로이드 알고리즘 - 최단경로

 

1) 작동 방식

: 모든 정점 사이의 최단 경로를 찾는다.

:  (i 에서 j로 가는 거리)와 (i -> k + k ->j 로 가는 거리) 중에 더 짧은 경로로 간다.

 

 

 

2) 전체 코드

 

#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++) {
		for (i = 0; i<g->n; i++)
			for (j = 0; j<g->n; 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)
{
	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;
}

 

 

 

 

 

 

 

3) 시간 복잡도

O(n^3)

 

- 다익스트라 알고리즘도 모든 정점부터 시작하는 최단 경로를 구하려면 O(n^2)을 n번 반복해야 하기 때문에

시간복잡도가 O(n^3)가 된다.

 

 

 

 

 

 

 

 

 

 

4. 위상 정렬

: 방향이 있는 그래프에서 선행 순서를 위배하지 않으면서도 모든 정점을 방문하는 알고리즘이다.

 

 

1) 동작 방식

: 선수 노드가 없는 노드를 선택한다.

: 선택한 노드의 선행 관계를 삭제한다.

: 선택되지 않은 노드들 중 선수 노드가 없는 노드를 다시 선택한다.

x 모든 정점이 선택될 때까지 반복

 

 

 

 

 

 

 

2) 전체 코드

 

#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;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

1. 신장 트리(spanning tree)

: 그래프 내의 모든 정점을 포함하는 트리이다. 사이클이 없어야 한다 !!

- n개의 정점을 가지는 그래프의 신장 트리는 n-1개의 간선을 가진다.

 

 

 

 

 

 

 

2. 최소비용 신장 트리(Minimum Spanning Tree)

: 네트워크에 있는 모든 정점들을 가장 적은 수의 간선과 비용으로 연결한 신장 트리이다.

 

 

 

 

3. 크루스칼의 MST

 

 

1) 동작 과정

- 간선을 비용에 따라 정렬한다.

- 선택되지 않은 간선 중, 가장 비용이 적은 간선을 선택한다.

- 간선 두 정점의 대표 정점을 반환하고, 대표 정점이 기존 대표 정점과 일치하지 않으면 기존 집합과 새로운 집합을 합친다.

 

크루스칼 알고리즘 동작과정

 

 

 

 

- 탐욕적인 방법(greedy algorythm)

: 각 단계에서 최선의 방법을 선택하는 과정을 반복한다.

: 검증 필요

: Kruskal 알고리즘은 최적임이 증명되었다.

 

 

- union-find 알고리즘

: 원소가 어떤 집합에 속하는지 알아낸다.

Kruskal의 MST 알고리즘에서 사이클 검사에 이용한다.

 

 

 

2) 핵심 코드

// kruskal의 최소 비용 신장 트리 프로그램
void kruskal(GraphType *g, GraphType *t)
{
     // 구현
    int edge_accepted = 0;
    HeapType h;
    int uset, vset;
    element e;              // 힙 요소
    
    init(&h);
    insert_all_edges(&h, g);
    set_init(g->n);
    
    while(edge_accepted < (g->n-1)){
        e = delete_min_heap(&h);
        uset = set_find(e.u);		
        vset = set_find(e.v);
        if(uset != vset){
            printf("(%d, %d) %d \n", e.u, e.v, e.key);
            edge_accepted++;
            t->adj_mat[e.u][e.v] = e.key;
            set_union(uset, vset);
        }
    }
}

 

 

 

3) 전체 코드

#include <stdio.h>
#include "minheap.h"
#include "unionfind.h"

#define MAX_VERTICES 100
#define INF 9999

typedef struct GraphType {
    int n;                    // 정점의 개수
    int adj_mat[MAX_VERTICES][MAX_VERTICES];
} GraphType;

// 그래프 초기화
void graph_init(GraphType *g)
{
     // 구현
    g->n = 0;
    for(int i=0; i<MAX_VERTICES; i++){
        for(int j=0; j<MAX_VERTICES; j++){
            g->adj_mat[i][j] = INF;
        }
    }
}

void insert_edge(GraphType *g, int start, int end, int key)
{
    if( start >= g->n || end >= g->n ){
        fprintf(stderr,"그래프 : 정점 번호 오류");
        return;
    }
     
     // ƒ⁄µÂ ª¿‘
    g->adj_mat[start][end] = key;
    g->adj_mat[end][start] = key;
}

/*  */
void read_graph(GraphType *g, char *filename)
{
     // 구현
    int number, u, v, key;
   
    FILE *fp;
    fp = fopen(filename, "rt");
   
    if (fp == NULL)
    {
        printf("file %s open error!\n", filename);
        return;
    }
   
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d %d %d\n", &u, &v, &key) != EOF){
        insert_edge(g, u, v, key);
    }

    fclose(fp);
}


/*  */
void write_graph(GraphType *g, char *filename)
{
     // 구현
    int i, j;
    FILE *fp;
    
    if (filename == NULL) fp = stdout;
    else {
         fp = fopen(filename, "wt");
        if (fp == NULL)
        {
            printf("file %s open error!\n", filename);
             return;
        }
    }
   
   fprintf(fp, "%d\n", g->n);

   // 구현: fprintf(fp, "%d\n", ...)을 사용한다
   for(i=0; i<(g->n); i++){
       for(j=0; j<(g->n); j++){
           if(g->adj_mat[i][j] != INF)
               fprintf(fp, "%d %d %d\n", i, j, g->adj_mat[i][j]);
       }
   }
   
    if (filename != NULL) fclose(fp);
}

// 인접 행렬이나 인접 리스트에서 간선들을 읽어서 최소 히프에 삽입
// 현재는 예제 그래프의 간선들을 삽입한다.
void insert_all_edges(HeapType *h, GraphType *g)
{
     // 구현
    for(int i=0; i<g->n; i++){
        for(int j=0; j<g->n; j++){
            if(i < j && g->adj_mat[i][j] != INF){
                element e;
                e.u = i;
                e.v = j;
                e.key = g->adj_mat[i][j];
                insert_min_heap(h, e);
            }
        }
    }
}

// kruskal의 최소 비용 신장 트리 프로그램
void kruskal(GraphType *g, GraphType *t)
{
    // 구현
    int edge_accepted = 0;
    HeapType h;
    int uset, vset;
    element e;              // 힙 요소
    
    init(&h);
    insert_all_edges(&h, g);
    set_init(g->n);
    
    while(edge_accepted < (g->n-1)){
        e = delete_min_heap(&h);
        uset = set_find(e.u);       // 대표 정점 반환
        vset = set_find(e.v);
        if(uset != vset){           // 대표 정점이 다르면
            printf("(%d, %d) %d \n", e.u, e.v, e.key);
            edge_accepted++;
            t->adj_mat[e.u][e.v] = e.key;
            set_union(uset, vset);      // 합친다.
        }
    }
}

int main()
{
    GraphType g, t;        // 입력 그래프, 결과 트리
    
    graph_init(&g);
//    read_graph(&g, "input.txt");
    read_graph(&g, "input2.txt");

    graph_init(&t);
    t.n = g.n;
    
    printf("선택된 간선<순서대로> : \n");
    kruskal(&g, &t);
    
    printf("\n파일로 출력:\n");
    write_graph(&t, "output.txt");
    write_graph(&t, NULL);    // to stdout
    
    return 0;
}

 

 

 

 

4) kruskal의 MST 알고리즘 복잡도

: 크루스칼의 알고리즘은 대부분 간선을 정렬하는 시간에 좌우된다.

- 사이클 테스트 등의 작업은 비교적 매우 신속하게 수행된다.

 

: 네트워크의 간선 e개를 퀵정렬과 같은 효율적인 알고리즘으로 정렬하면 

크루즈칼 알고리즘의 시간 복잡도는 O(e * log(e)), 최악의 경우 O(e^2)

힙정렬은 최악의 경우에도 O(e * log(e))

 

 

 

4. 프림의 MST

: 시작 정점부터 출발하여 신장 트리 집합을 단계적으로 확장해 나간다.

: 신장 트리 집합에 인접한 정점 중에서 최저 간선으로 연결된 정점을 선택하여 신장 트리 집합에 추가한다.

 

 

 

- dist[u] : <신장트리집합>에서 u까지의 최소거리

- selected[u] : 신장트리집합에 u를 넣으면 selected[u] = 1;

- connectVertex[u] = 신장트리집합에 넣을 때 u와 연결된 점

 

 

 

 

1) 핵심 코드

//
void prim(GraphType* g, int s)
{
    int i, u, v;

    for (u = 0; u<g->n; u++){
        distance[u] = INF;
        connectVertex[u] = s;       // 중요! 시작점 설정, connectVertex = 신장트리집합에 넣을 때 u와 연결된 점
    }
    
    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 %d> %d\n", connectVertex[u], u, distance[u]);
        printf("selected[] = \t");
        for(int j=0; j<g->n; j++){
            printf("%7d", selected[j]);
        }
        printf("\n");
        
        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];
                    connectVertex[v] = u;
                }
        
        printf("dist[] = \t\t");
        for(int j=0; j<g->n; j++){
            printf("%7d", distance[j]);
        }
        printf("\n\n");
        
    }
}

 

 

 

 

2) 전체 코드

 

#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];
int connectVertex[MAX_VERTICES];

// 최소 dist[v] 값을 갖는 정점을 반환
int get_min_vertex(int n)
{
    int v = -1, i;
    for (i = 0; i <n; i++){
        if (!selected[i]) {
            v = i;
            break;
        }
    }
    
    if(v == -1)
        return -1;
    
    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;
        connectVertex[u] = s;       // 중요! 시작점 설정, connectVertex = 신장트리집합에 넣을 때 u와 연결된 점
    }
    
    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 %d> %d\n", connectVertex[u], u, distance[u]);
        printf("selected[] = \t");
        for(int j=0; j<g->n; j++){
            printf("%7d", selected[j]);
        }
        printf("\n");
        
        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];
                    connectVertex[v] = u;
                }
        
        printf("dist[] = \t\t");
        for(int j=0; j<g->n; j++){
            printf("%7d", distance[j]);
        }
        printf("\n\n");
        
    }
}

int main(void)
{
    GraphType g = { 6,
    {{ 0, 10, INF, 20, 70, INF },
    { 10,  0, 50, 30, 60, INF },
    { INF, 50, 0, INF, 40, 90 },
    { 20, 30, INF, 0, INF, 80 },
    { 70, 60, 40, INF, 0, INF },
    { INF, INF, 90, 80, INF, 0} }
    };
    
    prim(&g, 0);
    
    return 0;
}

 

 

 

 

 

 

 

3) 크루스칼과 프림의 알고리즘 복잡도

 - 간선이 아주 적은 그래프(sparse)

: 크루스칼의 알고리즘이 유리 - O(e * log(e)) -> 계속 정렬을 해야 하니까

- 간선이 아주 많은 그래프(dense)

: 프림 알고리즘이 유리 - O(n^2)

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

 

 

 

 

 

1. 깊이 우선 탐색(DFS : Depth-First Search)

: 한 방향으로 갈 수 있을 때까지 가다가 더 이상 갈 수 없으면

가장 가까운 갈림길로 돌아와서 이 곳으로부터 다른 방향으로 다시 탐색 진행한다.

: 되돌아가기 위해서는 스택이 필요하다.(재귀함수로 묵시적인 스택 활용 가능)

 

 

1) 핵심 코드 - 인접행렬 이용

void dfs_mat(GraphType *g, int v)
{
     int w;
     visited[v] = 1;
     for (w = 0; w < g->n; w++)
          if ((g->adj_mat[v][w] == 1) && (visited[w] == 0)) {
              
              printf("<%d %d>\n", v, w);
              dfs_mat(g, w);
          }
}

 

- visited[] 배열에 해당 정점을 방문했다고 표시한다.

- 해당 정점에 연결되어 있고, 아직 방문하지 않은 정점을 차례로 방문한다.

 

 

 

 

 

2) 전체 코드 - 인접행렬 이용

#include <stdio.h>

#define MAX_VERTICES 50

typedef struct GraphType {
     int n;
     int adj_mat[MAX_VERTICES][MAX_VERTICES];
} GraphType;

int visited[MAX_VERTICES] = {0};

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_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 read_graph(GraphType *g, char *filename)
{
    int number, u, v;
   
    FILE *fp;
    fp = fopen(filename, "rt");
   
    if (fp == NULL)
    {
        printf("file %s open error!\n", filename);
        return;
    }
   
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d\n", &u) != EOF && fscanf(fp, "%d\n", &v) != EOF){
       
        insert_edge(g, u, v);
    }

    fclose(fp);
}

void dfs_mat(GraphType *g, int v)
{
     int w;
     visited[v] = 1;
     for (w = 0; w < g->n; w++)
          if ((g->adj_mat[v][w] == 1) && (visited[w] == 0)) {
              
              printf("<%d %d>\n", v, w);
              dfs_mat(g, w);
          }
}
int main(void)
{
     GraphType graph;
     int v;

     graph_init(&graph);
     read_graph(&graph, "infile.txt");
     //read_graph(&graph, "infile2.txt");
     
     printf("Enter 정점:");
     scanf("%d", &v);
     
     dfs_mat(&graph, v);
}
     
    

 

 

 

 

 

 

3) 핵심 코드 - 인접리스트 이용

void dfs_list(GraphType *g, int v){
    GraphNode *w;
    
    visited[v] = 1;
    
    for(w=g->adj_list[v]; w; w=w->link){
        if(!visited[w->vertex]){
            printf("<%d %d>\n", v, w->vertex);
            dfs_list(g, w->vertex);
        }
    }
}

 

 

 

 

4) 전체 코드 - 인접리스트 이용

#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 50
typedef int element;
typedef struct GraphNode
{
    int vertex;
    struct GraphNode *link;
} GraphNode;

typedef struct GraphType {
    int n;    // 정점의 개수
    GraphNode *adj_list[MAX_VERTICES];
} GraphType;

int visited[MAX_VERTICES] = {0};


void graph_init(GraphType *g)
{
    int v;
    g->n=0;
    
    for(v=0;v<MAX_VERTICES;v++){
        g->adj_list[v]=NULL; }
}


void insert_edge(GraphType *g, int u, int v)
{
    GraphNode *node_u, *node_v;
    if( u >= g->n || v >= g->n ){
        fprintf(stderr, "그래프 : 정점 번호 오류");
        return;
    }
    node_u = (GraphNode *)malloc(sizeof(GraphNode));
    if (node_u == NULL) {
        fprintf(stderr, "malloc 오류"); return;
    }
    
    node_u->vertex = v;
    node_u->link = g->adj_list[u];      // g->adj_list[u] 는 링크로서 vertex가 있는 첫 번째 노드를 가리킴
    g->adj_list[u] = node_u;
    
    
    node_v = (GraphNode *)malloc(sizeof(GraphNode));
    if (node_v == NULL) {
        fprintf(stderr, "malloc 오류"); return;
    }
    
    node_v->vertex = u;
    node_v->link = g->adj_list[v];
    g->adj_list[v] = node_v;
}

void remove_node(GraphNode **phead, element item) {     // 포인터에 대한 포인터를 꼭 써야하는 경우 !
    
    GraphNode *p, *prevp;
    
    if (*phead == NULL)
        printf("그래프가 비었습니다\n");
    else if ((*phead)->vertex == item) {
        p = *phead;
        *phead = (*phead)->link;
        free(p);
    }
    else {
        p = *phead;
        do {
            prevp = p;
            p = p->link;
        }while (p != NULL && p->vertex != item);
        if (p != NULL) {
            prevp->link = p->link;
            free(p);
        }
        else
            printf("삭제하려는 %d 값이 없습니다\n", item);
    }
}

void delete_edge(GraphType *g, int u, int v)
{
    
    if( u >= g->n || v >= g->n ){
        fprintf(stderr, "그래프 : 정점 번호 오류");
        return;
    }
    
    remove_node(&g->adj_list[u], v);
    remove_node(&g->adj_list[v], u);
}

void read_graph(GraphType *g, char *filename)
{
    int number, u, v;
    FILE *fp;
    fp = fopen(filename, "rt");
    if (fp == NULL)
    {
        printf("file open error!\n");
        return;
    }
    
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d\n", &u) != EOF && fscanf(fp, "%d\n", &v) != EOF){
        
        insert_edge(g, u, v);
    }
    
    fclose(fp);
}


void write_graph(GraphType *g, char *filename)
{
    int u;
    FILE *fp;
    GraphNode *ptr;
    
    if (filename == NULL) fp = stdout;
    else {
        fp = fopen(filename, "w");
        if (fp == NULL)
        {
            printf("file %s open error!\n", filename);
            return;
        }
    }
    
    fprintf(fp, "%d\n", g->n);
    
    for(u=0; u<(g->n); u++){
        ptr = g->adj_list[u];
        while (ptr != NULL){
            if(u < ptr->vertex)
                fprintf(fp, "%d %d\n", u, ptr->vertex);
            ptr = ptr->link;
        }
    }
    
    if (filename != NULL) fclose(fp);
}

void dfs_list(GraphType *g, int v){
    GraphNode *w;
    
    visited[v] = 1;
    
    for(w=g->adj_list[v]; w; w=w->link){
        if(!visited[w->vertex]){
            printf("<%d %d>\n", v, w->vertex);
            dfs_list(g, w->vertex);
        }
    }
}

int main(void)
{
    GraphType g;
    int v;
    graph_init(&g);
    
    read_graph(&g, "input.txt");
    
    printf("Enter 정점:");
    scanf("%d", &v);
    
    dfs_list(&g, v);
}

 

 

 

 

 

 

2. 넓이 우선 탐색(BFS : Breadth - First Search)

: 시작 정점으로부터 가까운 정점을 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법이다.

: 큐를 사용하여 구현한다.

 

 

 

1) 핵심 코드 - 인접행렬 이용

void bfs_mat(GraphType *g, int v)
{
    int w;
    QueueType q;
    init(&q);
    
    visited[v] = 1;
    enqueue(&q, v);
    
    while(!is_empty(&q)){
        v = dequeue(&q);
        
        for(w=0; w<g->n; w++){
            if (g->adj_mat[v][w] == 1 && visited[w] == 0) {
                visited[w] = 1;
                enqueue(&q, w);
                printf("<%d %d>\n", v, w);
            }
        }
    }
}

 

 

 

2) 전체 코드 - 인접행렬 이용

#include <stdio.h>
#include <stdlib.h>
#include "queue.h"  // <------
#define MAX_VERTICES 50

typedef struct GraphType {
    int n;
    int adj_mat[MAX_VERTICES][MAX_VERTICES];
} GraphType;

int visited[MAX_VERTICES] = {0};

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_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 read_graph(GraphType *g, char *filename)
{
    int number, u, v;
    
    FILE *fp;
    fp = fopen(filename, "rt");
    
    if (fp == NULL)
    {
        printf("file %s open error!\n", filename);
        return;
    }
    
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d\n", &u) != EOF && fscanf(fp, "%d\n", &v) != EOF){
        
        insert_edge(g, u, v);
    }
    
    fclose(fp);
}

int visited[MAX_VERTICES];

void init(QueueType *q);
void enqueue(QueueType *q, element item);
element dequeue(QueueType *q);
int is_empty(QueueType *q);


void bfs_mat(GraphType *g, int v)
{
    int w;
    QueueType q;
    init(&q);
    
    visited[v] = 1;
    enqueue(&q, v);
    
    while(!is_empty(&q)){
        v = dequeue(&q);
        
        for(w=0; w<g->n; w++){
            if (g->adj_mat[v][w] == 1 && visited[w] == 0) {
                visited[w] = 1;
                enqueue(&q, w);
                printf("<%d %d>\n", v, w);
            }
        }
    }
}

int main(void)
{
    GraphType graph;
    int v;
    
    graph_init(&graph);
    read_graph(&graph, "infile.txt");
    //read_graph(&graph, "infile2.txt");
    
    printf("Enter 정점:");
    scanf("%d", &v);
    
    bfs_mat(&graph, v);
}


 

 

 

 

 

 

3) 핵심 코드 - 인접리스트 이용

void bfs_list(GraphType *g, int v)
{
    GraphNode *w;
    QueueType q;
    init(&q);
    
    visited[v] = TRUE;
    enqueue(&q, v);
    while(!is_empty(&q)){
         v = dequeue(&q);       // 빼고
        
        for(w=g->adj_list[v]; w; w = w->link)
            if(!visited[w->vertex]){
                printf("<%d %d>\n", v, w->vertex);
                visited[w->vertex] = TRUE;
                enqueue(&q, w->vertex);        // 넣고
        }
    }
}

 

 

 

 

4) 전체 코드 - 인접리스트 이용

#include <stdio.h>
#include <stdlib.h>
#include "queue.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++;
}

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;
    
    node = (GraphNode *)malloc(sizeof(GraphNode));
    node->vertex = u;
    node->link = g->adj_list[v];
    g->adj_list[v] = node;
}


int visited[MAX_VERTICES];

void init(QueueType *q);
void enqueue(QueueType *q, element item);
element dequeue(QueueType *q);
int is_empty(QueueType *q);

void bfs_list(GraphType *g, int v)
{
    GraphNode *w;
    QueueType q;
    init(&q);
    
    visited[v] = TRUE;
    enqueue(&q, v);
    while(!is_empty(&q)){
         v = dequeue(&q);       // 빼고
        
        for(w=g->adj_list[v]; w; w = w->link)
            if(!visited[w->vertex]){
                printf("<%d %d>\n", v, w->vertex);
                visited[w->vertex] = TRUE;
                enqueue(&q, w->vertex);        // 넣고
        }
    }
}

void read_graph(GraphType *g, char *filename)
{
    int number, u, v;
   
    FILE *fp;
    fp = fopen(filename, "rt");
   
    if (fp == NULL)
    {
        printf("file %s open error!\n", filename);
        return;
    }
   
    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d %d\n", &u, &v) != EOF){
       
        insert_edge(g, u, v);
    }

    fclose(fp);
}
int main(void)
{
     GraphType graph;
    int v;
    
     graph_init(&graph);
     read_graph(&graph, "infile.txt");
     
     printf("Enter 정점:");
     scanf("%d", &v);
     
     bfs_list(&graph, v);
}

 

 

 

 

 

 

 

 

 

 

3. 시간복잡도 분석

 

1) 인접행렬 이용 - O(n^2)

2) 인접리스트 이용 - O(n+e)

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

 

 

1. 그래프 관련 용어

1) 그래프 G는 (V, E) 로 표시한다.

 

- 정점(Vertices)

: '노드' 라고도 불린다.

 

- 간선(Edge)

: '링크' 이라고도 불린다.

 

 

 

 

 

2) 인접 정점 & 차수

- 인접 정점 : 간선에 의해 직접 연결된 정점이다.

ex) G1에서 정점 0의 인접 정점 = 정점 1, 정점 2, 정점 3

- 무방향 그래프의 차수 : 하나의 정점에 연결된 다른 정점의 수이다.

ex) G1에서 정점 0의 차수 = 3

- 방향 그래프의 차수 ( 모든 진입 차수, 진출 차수의 수는 간선의 수와 동일하다. )

① 진입 차수 : 외부에서 오는 간선의 수

진출 차수 : 외부로 향하는 간선의 수

 

 

 

 

 

2. 그래프 표현의 예

V(G1) = {0, 1, 2, 3}, E(G1) = {(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)}

V(G2) = {0, 1, 2}, E(G2) = {<0,1>, <1, 0>, <1, 2>}

 

() : 양방향, <> : 일방향

 

 

 

3. 그래프의 종류

1) 무방향 그래프

2) 방향 그래프

3) 가중치 그래프 : 간선에 비용이나 가중치가 할당된 그래프이다. '네트워크' 라고도 한다.

4) 부분 그래프 : 정점 집합 V(G)와 간선 집합 E(G)의 부분 집합으로 이루어진 그래프이다.

 

 

 

 

 

4. 그래프의 경로

 

1) 단순 경로 : 경로 중에서 반복되는 간선이 없는 경로이다.

2) 사이클 : 단순 경로의 시작 정점과 종료 정점이 동일한 경로이다.

 

 

 

5. 그래프의 연결 정도

 

1) 연결 그래프 : 무방향 그래프 G에 있는 모든 정점쌍에 대해 항상 경로가 존재한다.

(a) 연결 그래프, (b) 연결 그래프 아님

 

 

2) 완전 그래프

: 모든 정점이 연결되어 있는 그래프이다.

- n개의 정점을 가진 무방향 완전그래프의 간선의 수 : n (n-1) / 2

if n = 4, 간선의 수 = (4 x 3) / 2 = 6

 

 

 

 

6. 그래프 표현 방법

 

1) 인접행렬 방법

: (a), (b)와 같은 무방향 그래프의 간선의 수는 (1의 개수)/2,

(c)와 같은 방향 그래프의 간선의 개수는 (1의 개수) 이다.

 

#include <stdio.h>
#define MAX_VERTICES 50

typedef struct GraphType {
     int n;
     int adj_mat[MAX_VERTICES][MAX_VERTICES];
} GraphType;


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_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 delete_edge(GraphType *g, int start, int end)
{
    if( start >= g->n || end >= g->n ){
        fprintf(stderr,"그래프 : 정점 번호 오류");
        return;
    }
     
     // ƒ⁄µÂ ª¿‘
    g->adj_mat[start][end] = 0;
    g->adj_mat[end][start] = 0;
}

void read_graph(GraphType *g, char *filename)
{
     int number, u, v;
    
     FILE *fp;
     fp = fopen(filename, "rt");
    
     if (fp == NULL)
     {
         printf("file %s open error!\n", filename);
         return;
     }
    
     fscanf(fp, "%d\n", &number);
     g->n = number;
     
     // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
     while(fscanf(fp, "%d %d\n", &u, &v) != EOF){
        
         insert_edge(g, u, v);
     }

     fclose(fp);
}

//
void write_graph(GraphType *g, char *filename)
{
     int i, j;
     FILE *fp;
     
     if (filename == NULL) fp = stdout;
     else {
          fp = fopen(filename, "wt");
         if (fp == NULL)
         {
             printf("file %s open error!\n", filename);
              return;
         }
     }
    
    fprintf(fp, "%d\n", g->n);

    // 구현: fprintf(fp, "%d\n", ...)을 사용한다
    for(i=0; i<(g->n); i++){
        for(j=0; j<(g->n); j++){
            if(g->adj_mat[i][j] && i < j)
                fprintf(fp, "%d %d\n", i, j);
        }
    }
    
     if (filename != NULL) fclose(fp);
}


int main(void)
{
    GraphType g;
    graph_init(&g);
    read_graph(&g, "input.txt");
    write_graph(&g, NULL);    // to stdout

    insert_edge(&g, 1, 3);
    write_graph(&g, NULL);    // to stdout

    delete_edge(&g, 2, 0);
    write_graph(&g, NULL);    // to stdout

    write_graph(&g, "output.txt");
}

     

 

 

 

 

 

 

2) 인접리스트 방법

: 각 정점에 인접한 정점들을 인접리스트로 표현한다.

 

 

 

 

#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 50
typedef int element;
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_edge(GraphType *g, int u, int v)
{
    GraphNode *node_u, *node_v;
    if( u >= g->n || v >= g->n ){
        fprintf(stderr, "그래프 : 정점 번호 오류");
        return;
    }
    node_u = (GraphNode *)malloc(sizeof(GraphNode));
    if (node_u == NULL) {
        fprintf(stderr, "malloc 오류"); return;
    }
    
    node_u->vertex = v;
    node_u->link = g->adj_list[u];
    g->adj_list[u] = node_u;
    
    
    node_v = (GraphNode *)malloc(sizeof(GraphNode));
    if (node_v == NULL) {
        fprintf(stderr, "malloc 오류"); return;
    }
    
    node_v->vertex = u;
    node_v->link = g->adj_list[v];
    g->adj_list[v] = node_v;
}

void remove_node(GraphNode **phead, element item) {     // 포인터에 대한 포인터를 꼭 써야하는 경우 !
    
     GraphNode *p, *prevp;
     
     if (*phead == NULL)
          printf("그래프가 비었습니다\n");
     else if ((*phead)->vertex == item) {
          p = *phead;
          *phead = (*phead)->link;
          free(p);
     }
     else {
          p = *phead;
          do {
               prevp = p;
               p = p->link;
          }while (p != NULL && p->vertex != item);
          if (p != NULL) {
               prevp->link = p->link;
               free(p);
          }
          else
               printf("삭제하려는 %d 값이 없습니다\n", item);
     }
}

void delete_edge(GraphType *g, int u, int v)
{
    
    if( u >= g->n || v >= g->n ){
        fprintf(stderr, "그래프 : 정점 번호 오류");
        return;
    }
    
    remove_node(&g->adj_list[u], v);
    remove_node(&g->adj_list[v], u);
}

void read_graph(GraphType *g, char *filename)
{
     int number, u, v;
     FILE *fp;
     fp = fopen(filename, "rt");
    if (fp == NULL)
    {
        printf("file open error!\n");
        return;
    }

    fscanf(fp, "%d\n", &number);
    g->n = number;
    
    // 구현: while (fscanf(fp, "%d\n", &n) != EOF) {...} 을 사용한다.
    while(fscanf(fp, "%d\n", &u) != EOF && fscanf(fp, "%d\n", &v) != EOF){
       
        insert_edge(g, u, v);
    }

     fclose(fp);
}


void write_graph(GraphType *g, char *filename)
{
     int u;
     FILE *fp;
     GraphNode *ptr;
     
     if (filename == NULL) fp = stdout;
     else {
          fp = fopen(filename, "w");
         if (fp == NULL)
         {
             printf("file %s open error!\n", filename);
              return;
         }
     }

    fprintf(fp, "%d\n", g->n);
    
    for(u=0; u<(g->n); u++){
        ptr = g->adj_list[u];
        while (ptr != NULL){
            if(u < ptr->vertex)
                fprintf(fp, "%d %d\n", u, ptr->vertex);
            ptr = ptr->link;
        }
    }

    if (filename != NULL) fclose(fp);
}

int main(void)
{
    GraphType g;
    graph_init(&g);

    read_graph(&g, "input.txt");
    write_graph(&g, NULL);    // to stdout

    insert_edge(&g, 1, 3);
    write_graph(&g, NULL);    // to stdout

    delete_edge(&g, 2, 0);
    write_graph(&g, NULL);    // to stdout

    write_graph(&g, "output.txt");
}

 

: g->adj_list[u] 는 링크로서 vertex가 있는 첫 번째 노드를 가리킴

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

1. 우선순위 큐 란?

: 우선 순위를 가진 항목들을 저장하는 큐이다. 

우선 순위가 높은 데이터가 먼저 삭제된다.

 

 

 

 

 

1) 구현 방법

- 배열 이용

- 연결 리스트 이용

- 힙(Heap) 이용 -> 가장 효율적이다.(트리 구조)

 

 

 

 

 

 

 

 

 

2. 힙(Heap) 이란?

: 노드들이 아래의 식을 만족하는 완전이진트리이다.

key(부모 노드) >= key(자식 노드)

 

 

* 완전이진트리 : 왼쪽부터 차례대로 이진트리를 꽉 채운 트리이다. 

 

1) 힙의 종류

- 최대 힙 : key(부모 노드) >= key(자식 노드)

- 최소 힙 : key(부모 노드) <= key(자식 노드)

 

 

 

2) 힙의 높이

: N 개의 노드를 가지고 있는 힙의 높이는 O(logN) 이다.

 

 

3) 힙의 구현방법

: 배열로 구현한다. 완전이진트리이므로 각 노드에 번호를 붙일 수 있다. 이 번호를 배열의 인덱스로 생각한다. 

 

* 배열로 트리구조 구현할 때 장점 - 부모노드를 쉽게 찾을 수 있다.

왼쪽 자식 인덱스 = (부모의 인덱스) * 2

오른쪽 자식 인덱스 = (부모의 인덱스) * 2 + 1

부모의 인덱스 = (자식의 인덱스) / 2

 

 

 

 

4) 기본 코드

#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENT 200
typedef struct {
    int key;
} element;
typedef struct {
    element heap[MAX_ELEMENT];
    int heap_size;
} HeapType;


HeapType* create()
{
    return (HeapType*)malloc(sizeof(HeapType));
}

void init(HeapType* h)
{
    h->heap_size = 0;
}

void insert_max_heap(HeapType* h, element item)
{
    int i;
    i = ++(h->heap_size);

    while ((i != 1) && (item.key > h->heap[i / 2].key)) {
        h->heap[i] = h->heap[i / 2];
        i /= 2;
    }
    h->heap[i] = item;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
}

element delete_max_heap(HeapType* h)
{
    int parent, child;
    element item, temp;

    item = h->heap[1];
    temp = h->heap[(h->heap_size)--];
    parent = 1;
    child = 2;
    while (child <= h->heap_size) {
        
        if ((child < h->heap_size) &&
            (h->heap[child].key) < h->heap[child + 1].key)
            child++;
        if (temp.key >= h->heap[child].key) break;
        
        h->heap[parent] = h->heap[child];
        parent = child;
        child *= 2;
    }
    h->heap[parent] = temp;
    return item;
}

void preorder1(HeapType *h, int root){
    if(root > h->heap_size)
        return;
    
    printf("%d ", h->heap[root].key);
    preorder1(h, root * 2);
    preorder1(h, root * 2 + 1);
}

void print_heap(HeapType *h){
    int num = 4;
    
    printf("%d\n", h->heap[1].key);
    
    for(int i=2; i<=h->heap_size; i++){
        printf("%d ", h->heap[i].key);
        if((i+1) == num){
            printf("\n");
            num *= 2;
        }
    }
    printf("\n");
}

int max(int a, int b){
    if (a >= b)
        return a;
    else
        return b;
}

int find(HeapType *h, int root, int key){
    if(root > h->heap_size)
        return 0;
    if(h->heap[root].key == key)
        return root;
    else if (h->heap[root].key < key)       // 중요
        return 0;
    
    return max(find(h, root * 2, key), find(h, root * 2 + 1, key));     // 중요
}

void print_sorted_value(HeapType *h){
    int size = h->heap_size;
    element e[10];
    
    for(int i=0; i< size; i++){
        e[i] = delete_max_heap(h);
        printf("%d ", e[i].key);
    }
    for(int i=0; i< size; i++){
        insert_max_heap(h, e[i]);
    }
    printf("\n");
}

void modify_priority(HeapType *h, int oldKey, int newKey){
    
    int i, child;
    
    if(oldKey == newKey)
        return;
    
    i = find(h, 1, oldKey);
    if(i == 0)
        return;
    
    if(newKey > oldKey){
        
        while ((i != 1) && (newKey > h->heap[i / 2].key)) {
            h->heap[i] = h->heap[i / 2];
            i /= 2;
        }
        h->heap[i].key = newKey;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
        
    } else {
        element temp = h->heap[(h->heap_size)];
        
        child = i * 2;
        
        while (child <= h->heap_size) {
            
            if ((child < h->heap_size) &&
                (h->heap[child].key) < h->heap[child + 1].key)
                child++;
            if (temp.key >= h->heap[child].key) break;
            
            h->heap[i] = h->heap[child];
            i = child;
            child *= 2;
        }
        h->heap[i] = temp;
    }
}

int main(void)
{
    element e1 = { 10 }, e2 = { 5 }, e3 = { 30 };
    element eA = { 9 }, eB = { 19 }, eC = { 39 };
    element e4, e5, e6;
    int index;
    int key, oldKey, newKey;
    HeapType* heap;
    
    heap = create();
    init(heap);    // √ ±‚»≠

    printf("Step1 : 삽입된 10, 5, 30에 추가적으로 9, 19, 39를 삽입한다\n");
    insert_max_heap(heap, e1);
    insert_max_heap(heap, e2);
    insert_max_heap(heap, e3);
    insert_max_heap(heap, eA);
    insert_max_heap(heap, eB);
    insert_max_heap(heap, eC);
    
    printf("\nStep2 : preorder, print_heap 함수 테스트\n");
    preorder1(heap, 1);
    printf("\n");
    print_heap(heap);
    
    
    e4 = delete_max_heap(heap);
    printf("삭제 : %d 루트가 삭제됨\n", e4.key);
    print_heap(heap);
    
    printf("\nStep3 : find 함수 테스트\n");
    printf("찾을 key 입력(-1 for exit) : ");
    scanf("%d", &key);
    while(key != -1){
        if((index = find(heap, 1, key)) == 0)
            printf("%d는 없음\n", key);
        else
            printf("%d은 [%d]에 있음\n", key, index);
        printf("찾을 key 입력(-1 for index) : ");
        scanf("%d", &key);
    }
    
    printf("\nStep4 : print_sorted_value 함수 테스트\n");
    print_sorted_value(heap);
    
    
    printf("\nStep5 : modify_priority 함수 테스트\n");
    printf("바꿀 key 입력(-1 for exit) : ");
    scanf("%d", &oldKey);
    while(oldKey != -1){
        printf("새 key 입력 : ");
        scanf("%d", &newKey);
        modify_priority(heap, oldKey, newKey);
        print_heap(heap);
        
        printf("바꿀 key 입력(-1 for exit) : ");
        scanf("%d", &oldKey);
    }

    free(heap);
    return 0;
}

 

 

 

 

 

 

5) 삽입 연산

- 힙의 마지막 노드에 이어서 삽입한다.

- 삽입 후에 새로운 노드를 부모 노드들과 교환해서 힙의 성질을 만족시킨다.

 

void insert_max_heap(HeapType* h, element item)
{
    int i;
    i = ++(h->heap_size);

    while ((i != 1) && (item.key > h->heap[i / 2].key)) {
        h->heap[i] = h->heap[i / 2];
        i /= 2;
    }
    h->heap[i] = item;     // ªı∑ŒøÓ ≥εÂ∏¶ ª¿‘
}

 

 

 

 

 

 

6) 삭제 연산

- 루트 노드를 삭제한다.

- 마지막 노드를 루트 노드로 이동시킨다.

- 루트에서부터 단말 노드까지의 경로에 있는 노드들을 교환하여 힙의 성질을 만족시킨다.

 

element delete_max_heap(HeapType* h)
{
    int parent, child;
    element item, temp;

    item = h->heap[1];
    temp = h->heap[(h->heap_size)--];
    parent = 1;
    child = 2;
    while (child <= h->heap_size) {
        // 현재 노드의 자식 중 더 큰 자식 노드를 찾는다.
        if ((child < h->heap_size) &&
            (h->heap[child].key) < h->heap[child + 1].key)
            child++;
        if (temp.key >= h->heap[child].key) break;
        
        // 한 단계 아래로 이동한다.
        h->heap[parent] = h->heap[child];
        parent = child;
        child *= 2;
    }
    h->heap[parent] = temp;
    return item;
}

 

 

 

 

 

 

 

 

7) Find 연산

int find(HeapType *h, int root, int key){
    if(root > h->heap_size)
        return 0;
    if(h->heap[root].key == key)
        return root;
    else if (h->heap[root].key < key)       // 중요
        return 0;
    
    return max(find(h, root * 2, key), find(h, root * 2 + 1, key));     // 중요
}

 

- 기준 인덱스가 힙의 크기보다 크면 이미 힙을 초과한 것이므로 0을 반환한다.

- 키 값이 동일하면 기준 인덱스를 반환한다.

- 기준 노드의 값이 찾는 값보다 작으면 그 밑으로는 더 작은 값들밖에 없기 때문에 더 이상 값을 찾을 수 없으므로 0을 반환한다.

- 서브트리의 왼쪽과 오른쪽 노드에 find 연산을 수행하고 둘 중 큰 값을 반환한다.

값을 찾을 수 없는 경우 0을 반환하기 때문에 둘 중 큰 값을 반환하는 것이다.

 

 

 

 

 

 

 

3. 힙 정렬

- 하나의 요소를 힙에 삽입하거나 삭제할 때 시간이 O(logN)만큼 소요된다. 요소의 개수가 n개이므로 전체적으로 O(nlogN) 시간이 걸린다. (빠른 편)

- 힙 정렬이 최대로 유용한 경우는 가장 큰 값 몇 개만 필요할 때이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

1. 이진 탐색 트리란?

 

모든 노드가 아래의 조건을 만족하는 트리 자료구조이다.

key(왼쪽서브트리)≤key(루트노드)≤key(오른쪽서브트리)

- 따라서 이진 탐색 트리를 중위 순회하면 오름차순으로 정렬된 값을 얻을 수 있다.

 

 

 

1) 기본 코드

#include <stdio.h>
#include <stdlib.h>

typedef int element;
typedef struct TreeNode {
    element key;
    struct TreeNode *left, *right;
} TreeNode;

TreeNode * search(TreeNode * node, element key)
{
    if (node == NULL) return NULL;
    if (key == node->key) return node;
    else if (key < node->key)
        return search(node->left, key);
    else
        return search(node->right, key);
}

TreeNode * new_node(element item)
{
    TreeNode * temp = (TreeNode *)malloc(sizeof(TreeNode));
    temp->key = item;
    temp->left = temp->right = NULL;
    return temp;
}

TreeNode * insert_node(TreeNode * node, element key)
{
    if (node == NULL) return new_node(key);

    if (key < node->key)
        node->left = insert_node(node->left, key);
    else if (key > node->key)
        node->right = insert_node(node->right, key);

    return node;
}

TreeNode * min_value_node(TreeNode * node)
{
    TreeNode * current = node;

    while (current->left != NULL)
        current = current->left;

    return current;
}

TreeNode * delete_node(TreeNode * root, element key)
{
    if (root == NULL) return root;

    if (key < root->key)
        root->left = delete_node(root->left, key);
    else if (key > root->key)
        root->right = delete_node(root->right, key);
    else {
        if (root->left == NULL) {
            TreeNode * temp = root->right;
            free(root);
            return temp;
        }
        else if (root->right == NULL) {
            TreeNode * temp = root->left;
            free(root);
            return temp;
        }
        TreeNode * temp = min_value_node(root->right);

        root->key = temp->key;
        root->right = delete_node(root->right, temp->key);
    }
    return root;
}

void preorder1(TreeNode * root) {
    if (root) {
        printf("%d ", root->key);
        preorder1(root->left);
        preorder1(root->right);
    }
}

int max(int a, int b){
    if(a >= b)
        return a;
    else
        return b;
}

int get_height(TreeNode *root){
    int height = 0;

    if(root != NULL){
        height = 1 + max(get_height(root->left), get_height(root->right));}
    
    return height;
}

int get_node(TreeNode *root){
    int count=0;
    if(root != NULL){
        count = 1 + get_node(root->left) + get_node(root->right);
    }
    return count;
}

int get_max(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->right != NULL){
        tmp = tmp->right;
    }
    return tmp->key;
}

int get_min(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->left != NULL){
        tmp = tmp->left;
    }
    return tmp->key;
}

int main(void)
{
    TreeNode * root = NULL;
    char res;
    int value;
    
    printf("Enter i<nsert>,d<elete>,s<earch>,p<rint>,h<eight>,c<ount>,m<ax>,n<min>,q<uit>:");
    scanf("%c", &res);
    
    while(res != 'q'){
        switch (res) {
            case 'i':
                printf("삽입할 값 입력:");
                scanf("%d", &value);
                root = insert_node(root, value);
                break;
            case 'd':
                printf("삭제할 값 입력:");
                scanf("%d", &value);
                root = delete_node(root, value);
                break;
            case 's':
                printf("탐색할 key값 입력:");
                scanf("%d", &value);
                if(search(root, value) == NULL)
                    printf("없음\n");
                else
                    printf("있음\n");
                break;
            case 'p':
                preorder1(root);
                printf("\n");
                break;
            case 'h':
                printf("트리의 높이는 %d\n", get_height(root));
                break;
            case 'c':
                printf("노드의 개수는 %d\n", get_node(root));
                break;
            case 'm':
                printf("가장 큰 값은 %d\n", get_max(root));
                break;
            case 'n':
                printf("가장 작은 값은 %d\n", get_min(root));
                break;
            default:
                break;
        }
        
        fflush(stdin);
        printf("Enter i<nsert>,d<elete>,s<earch>,p<rint>,h<eight>,c<ount>,m<ax>,n<min>,q<uit>:");
        scanf("%c", &res);
    }
    
    return 0;
}

 

 

 

2) 삽입 연산

TreeNode * new_node(element item)
{
    TreeNode * temp = (TreeNode *)malloc(sizeof(TreeNode));
    temp->key = item;
    temp->left = temp->right = NULL;
    return temp;
}

TreeNode * insert_node(TreeNode * node, element key)
{
    if (node == NULL) return new_node(key);

    if (key < node->key)
        node->left = insert_node(node->left, key);
    else if (key > node->key)
        node->right = insert_node(node->right, key);

    return node;
}

 

- 삽입하려는 값이 기준 노드의 값보다 작으면 기준 노드를 왼쪽으로, 크면 기준 노드를 오른쪽 노드로 이동한다. 

- 그러다가 트리의 끝에 도달하면 new_node(); 함수를 사용하여 노드를 삽입해준다.

- 만약, 이미 똑같은 값이 있다면 기존의 트리를 반환할 것이다.

 

 

 

 

 

 

 

 

3) 삭제 연산

 

삭제하려는 노드의 상황은 세 가지가 있다.

① 삭제하려는 노드가 단말 노드인 경우

② 삭제하려는 노드가 하나의 왼쪽 혹은 오른쪽 서브 트리 중 하나만 갖고 있는 경우

③ 삭제하려는 노드가 두 개의 서브 트리를 모두 갖고 있는 경우

 

 

CASE

: 단말 노드이기 때문에 자기 자신과 부모 트리에만 조치를 해 주면 된다.

 

 

 

 

CASE 

: 삭제되는 노드가 왼쪽이나 오른쪽 서브 트리 하나만 가지고 있을 때, 그 노드는 삭제하고 서브 트리를 부모 노드에 붙여준다.

 

 

case  

: 삭제 노드와 가장 비슷한 값을 가진 노드를 삭제 위치로 가져온다. 여기서 비슷한 값은 아래 트리에서 12나 22처럼 왼쪽 서브트리에서 가장큰 수이거나 오른쪽 서브트리에서 가장 작은 수이다.

 

 

 

TreeNode * delete_node(TreeNode * root, element key)
{
    if (root == NULL) return root;

    if (key < root->key)
        root->left = delete_node(root->left, key);
    else if (key > root->key)
        root->right = delete_node(root->right, key);
    else {
        if (root->left == NULL) {	// 단말노드, 오른쪽만 있는 노드
            TreeNode * temp = root->right;
            free(root);
            return temp;
        }
        else if (root->right == NULL) {		// 왼쪽만 있는 노드
            TreeNode * temp = root->left;
            free(root);
            return temp;
        }
        TreeNode * temp = min_value_node(root->right);

        root->key = temp->key;
        root->right = delete_node(root->right, temp->key);
    }
    return root;
}

 

- 기준 노드의 값보다 작으면 왼쪽 노드를, 크면 오른쪽 노드를 반환한다.

- 그러다가 기준 노드의 값과 삭제할 값이 같으면 !

단말노드, 오른쪽 서브트리만 있는 노드는

해당 노드는 삭제하고 오른쪽 서브트리를 부모에게 전달한다.

 

 왼쪽 서브트리만 있는 노드는

해당 노드는 삭제하고 왼쪽 서브트리를 부모에게 전달한다.

 

 왼쪽, 오른쪽 서브트리가 모두 있는 노드는

오른쪽 서브트리에서의 제일 작은 값을 현재 노드 값으로 설정하고, 

오른쪽 서브트리에 내려가서 제일 작은 값을 가지는 노드를 삭제한다.

 

 

 

 

 

 

 

4) 검색 연산

 

TreeNode * search(TreeNode * node, element key)
{
    if (node == NULL) return NULL;
    if (key == node->key) return node;
    else if (key < node->key)
        return search(node->left, key);
    else
        return search(node->right, key);
}

 

- 기준 노드의 값보다 찾는 값이 작으면 왼쪽 서브트리를 반환, 크면 오른쪽 서브트리를 반환한다.

- 값이 같으면 해당 노드를 반환한다.

 

 

 

 

 

5) 기타 연산

: 전위 순회, 트리의 높이 구하기, 노드의 개수, 제일 큰 값, 제일 작은 값

void preorder1(TreeNode * root) {
    if (root) {
        printf("%d ", root->key);
        preorder1(root->left);
        preorder1(root->right);
    }
}

int get_height(TreeNode *root){
    int height = 0;

    if(root != NULL){
        height = 1 + max(get_height(root->left), get_height(root->right));}
    
    return height;
}

int get_node(TreeNode *root){
    int count=0;
    if(root != NULL){
        count = 1 + get_node(root->left) + get_node(root->right);
    }
    return count;
}

int get_max(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->right != NULL){
        tmp = tmp->right;
    }
    return tmp->key;
}

int get_min(TreeNode *root){
    TreeNode * tmp = root;
    
    while(tmp->left != NULL){
        tmp = tmp->left;
    }
    return tmp->key;
}

- 트리의 높이 구하기 : 왼쪽 서브트리와 오른쪽 서브트리 중 더 높이가 높은 길이를 반환한다.

- 노드의 총 개수 구하기 : 루트에서 오른쪽, 왼쪽 내려가면서 NULL 이 아니면 1씩 더해서 총 노드의 개수를 구한다.

 

 

 

 

 

2. 이진 탐색 트리 성능 분석

 

1) 연산의 속도는 트리의 높이 h에 비례한다.

2) 최선의 경우 시간 복잡도 : O(log n)

 

 

3) 최악의 경우 시간 복잡도 : O(n)

 

 

 

 

 

3. 이진 트리의 성질

1) 노드의 개수가 n개이면, 간선의 개수는 n-1개 이다.

2) 높이가 h인 이진트리의 경우,

최소 h개의 노드를 가지며, 최대 2^h - 1 개의 노드를 가진다.

 

3) n개의 노드를 가지는 트리의 높이는

최대 n이고, 최소 [log(n+1)] 이다. -> 올림 기호

 

4) 포화 이진 트리(각 레벨에 노드가 꽉 차있는 이진트리)의 전체 노드 개수는

2^k - 1

-> k 는 높이

 

 

 

 

 

4. 트리의 용어

1) 단말 노드 : 자식이 없는 노드

2) 비단말 노드 : 적어도 하나의 자식을 가진 노드

3) 레벨 : 트리 각 층의 번호

4) 높이 : 트리의 최대 레벨

5) 차수 : 노드가 가지고 있는 자식 노드의 개수 

 

 

 

 

 

 

 

5. 쓰레드 이진 트리란?

: NULL 링크에 중위 순회 시에 후속 노드인 중위 후속자를 저장시켜 놓은 트리이다.

 

 

-> 중위순회 순서 ACBGDFE

 

 

#include <stdio.h>
#define TRUE 1
#define FALSE 0

typedef struct TreeNode {
	int data;
	struct TreeNode *left, *right;
	int is_thread;	// 스레드이면 TRUE
} TreeNode;


//		     G
//	     C		  F
//    A	   B   D     E
TreeNode n1 = { 'A',  NULL, NULL, 1 };
TreeNode n2 = { 'B',  NULL,  NULL, 1 };
TreeNode n3 = { 'C',  &n1,  &n2, 0 };
TreeNode n4 = { 'D', NULL, NULL, 1 };
TreeNode n5 = { 'E', NULL, NULL, 0 };
TreeNode n6 = { 'F', &n4,  &n5, 0 };
TreeNode n7 = { 'G', &n3,  &n6, 0 };
TreeNode * exp = &n7;

TreeNode * find_successor(TreeNode * p)
{
	// q는 p의 오른쪽 포인터
	TreeNode * q = p->right;
	// 만약 오른쪽 포인터가 NULL이거나 p가 스레드이면 오른쪽 포인터를 반환
	if (q == NULL || p->is_thread == TRUE)
		return q;

	// 쓰레드가 아니면, 오른쪽 노드 -> 다시 가장 왼쪽 노드로 이동
	while (q->left != NULL) q = q->left;
	return q;
}

void thread_inorder(TreeNode * t)
{
	TreeNode * q;

	q = t;
	while (q->left) q = q->left;// 가장 왼쪽 노드로 간다.
	do {
		printf("%c -> ", q->data);// 데이터 출력
		q = find_successor(q); // 후속자 함수 호출
	} while (q);			// NULL이 아니면
}
int main(void)
{
	// 스레드 설정 
	n1.right = &n3;
	n2.right = &n7;
	n4.right = &n6;
	// 중위 순회
	thread_inorder(exp);
	printf("\n");
	return 0;
}

 

 

 

- thread_inorder(); 에서 가장 왼쪽 노드로 간다.

- 노드의 값을 프린트하고

- find_successor(); 함수로 쓰레드이면 그 다음 포인터를 반환, 쓰레드가 아니면 오른쪽 -> 왼쪽 가장 밑으로 간다.

 

 

 

 

 

 

 

 

 

 

6. 완전 이진트리

: 레벨 1부터 k-1까지는 노드가 모두 채워져 있고, k레벨에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워져 있는 이진 트리이다.

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts