반응형

 

 

 

 

 

 

 

 

1. 트랜잭션이란?

: 하나의 논리적인 작업 단위를 구성하는 연산들의 집합이다.

: 실행 중 멈추거나 중단되지 않는 최소 작업 단위

: 데이터베이스 응용 프로그램은 트랜잭션의 집합이다.

 

 

 

 

 

1) 트랜잭션의 예 1

 

 

-> 만약 이체 업무 중간에 멈춘다면?

계좌에 잘못된 정보가 저장되므로 

모든 연산을 완벽히 수행하던지, 아예 처음상태로 돌아가던지 해야 한다. -> all or nothing

 

 

 

2) 트랜잭션의 예 2

: 다중 사용자 환경에서 트랜잭션이 동시에 실행되는 다른 트랜잭션에 의해 영향을 받는다.

 

- 예금 인출이 동시에 두 지점에서 이루어진다면 오류가 발생한다.

 

 

 

3) 따라서,

앞의 상황이 발생하지 않도록 사전에 DBMS에서 트랜잭션 처리 기능을 갖춰야 한다.

 

 

 

 

 

 

 

 

 

 

2. 트랜잭션이 지켜야 할 조건

- ACID 특성

Atomicity(원자성)

:  트랜잭션에서 정의된 연산들은 모두 성공적으로 실행되던지, 아니면 실행 전 상태로 되돌아가야 한다.(all-or-nothing)

 

Consistency(일관성)

: 트랜잭션 실행 전과 후에 데이터베이스 내용이 일관성이 있어야 한다.

 

Isolation(고립성)

: 트랜잭션이 실행되는 과정에서 갱신된 데이터는 그 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조할 수 없다.

 

Durability(지속성/영속성)

: 트랜잭션이 성공적으로 완료되면 그 갱신된 내용은 영구적으로 

 

 

 

 

 

 

 

 

 

1) 원자성 : 트랜잭션은 성공하거나 실패하거나 둘 중 하나!

-> 실패하더라도 원래 상태로 되돌려놔야 한다.

 

2) 일관성 : 계좌이체 전과 후의 두 계좌의 잔액의 합은 동일해야 한다.

 

3) 고립성 : 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못하게 하는 특성이다.

 

 

 

* 고립성이 만족되는지 확인하려면? 

- 동시에 실행하는 트랜잭션들의 실행 결과가 순차적으로 실행된 결과와 동일한지 확인하면 된다.

 

 

* 해결 방법 *

- 각 트랜잭션을 순차적으로 실행한다. 다만, 다중 프로그래밍 환경에서 순차적으로 하는 것은 성능 면에서 문제 발생한다.

- 상호 간의 간섭이 일어나지 않도록 하는 기법이 필요하다.

 

 

 

 

4) 지속성 : 트랜잭션이 성공적으로 완료되면 그 갱신된 내용은 영구적으로

 

 

 

 

 

 

3. 트랜잭션 상태 전이

 

 

- 동작 : 트랜잭션이 시작되고 연산들이 정상적으로 실행중인 상태

- 부분완료 : 트랜잭션에 정의된 모든 연산의 실행이 끝난 상태

- 완료 : 트랜잭션이 성공적으로 종료된 상태

- 실패 : 트랜잭션이 성공적으로 완료되지 않고 더 이상 실행되지 못 하는 상태

- 중단 : 실행 이전으로 복귀된 상태

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

 

 

1. B+ tree 인덱스

: 관계형 데이터베이스에서 가장 널리 사용되는 인덱스 구조이다.

 

 

 

 

1) 차수가 n인 B+tree

차수 : 한 노드에서 하위 자식 노드를 가리키는 주소의 개수이다.

 

중간 노드 구조

 

P1 ~ Pn : 자식 노드를 가리키는 포인터

Key1 ~ Keyn-1 : 검색 키

Pi+1이 가리키는 하위 노드의 모든 검색 키는 Keyi 보다 크고 Keyi+1보다 작거나 같다.

 

 

 

 

 

 

 

 

2) B+tree 의 특징(차수가 n이라고 가정)

: 단말 노드에 레코드의 주소를 저장한다.

- 레코드의 검색 키 값에 따라 인덱스 엔트리들이 정렬된 형태이다.

- 단말 노드들이 검색 키 순서에 따라 연결된다.(다음 노드의 주소를 포함)

 

: 루트 노드에서 단말 노드까지의 모든 경로의 길이가 같다. (balanced tree)

: 루트 노드는 최소 2개, 최대 n개의 자식 노드를 가진다.

: 루트와 단말 노드를 제외한 중간 노드들은 최소 [n/2] (올림기호), 최대 n개의 자식 노드를 가진다.

 

K개의 검색 키 값을 포함하는 B+tree의 높이 = [lognK]

 

 

 

 

 

3) B+tree 의 성능

: 차수가 10이고 검색 키의 값이 10,000개 이면 B+tree의 높이는 4를 넘지 않는다.

 

 

 

 

 

 

 

 

 

2. 복합 인덱스

: 두 개 이상의 필드에 대해 정의되는 인덱스이다.

 

 

- 인덱스 엔트리들은 검색키의 값은 순서대로 정렬된다.

- 효율적인 검색을 위해 B+ tree 구성이 가능하다.

 

 

 

1) 복합 인덱스 정렬 방법

: 검색 키 값이 숫자인 경우 크기로 정렬 가능하다.

: 문자열인 경우 사전식 순서에 따라 정렬한다.

: 문자열 / 숫자가 아닌 경우, 엔트리들 간에 대소 관계 정의가 필요하다.

 

 

 

 

 

 

 

 

 

 

 

2) 복합 인덱스의 효과

: 복합키 필드들에 대한 동등 조건을 포함하는 질의 처리 성능을 향상시킨다.

 

SELECT *
FROM TAKES
WHERE STU_ID = '1292001' AND CLASS_ID = 'C101-01';

 

: 복합 인덱스가 정의된 필드만을 이용하는 질의는 테이블 조회 없이 복합 인덱스 조회만으로 처리 가능하다.

 

 

 

 

 

 

3) 복합 인덱스 주의사항

: 관련 필드의 순서에 따라 인덱스의 효과가 달라진다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3. 해시 인덱스

: 해시 함수를 기반으로 인덱스 엔트리를 저장한 인덱스이다.

 

버켓(bucket) 

: 인덱스 엔트리가 저장되는 공간이다. 블락보다는 큰 개념

: 해시함수 값을 버켓 번호로 사용한다.

 

 

 

 

 

 

 

1) 인덱스 구성 방법

: 인덱스 구성에 사용될 검색 키 필드를 설정한다.

: 레코드의 필드 값에 해시 함수를 적용하여 버켓 번호를 생성한다.

 

 

2) 인덱스 사용 방법

: 검색할 필드 값을 해시 함수에 적용하여 버켓 번호를 생성한다.

해당 버켓 안에 들어있는 인덱스 엔트리들 중 원하는 필드 값을 포함한 엔트리를 검색한다.

엔트리에 포함된 레코드 주소를 이용한다.

 

 

 

3) 해시 함수

: 해시 값은 입력값에 상관없이 균등하게 분포되어야 한다.

해시 함수의 예

H(x) = x % N

 

 

 

 

 

4) 버켓 오버플로우

: 일반적으로 버켓의 크기는 고정되어 있다.

: 버켓에 엔트리가 다 차는 경우 오버플로우가 발생한다.

: 새로운 버켓을 생성하여 기존 버켓과 연결해야 한다. (bucket chaining)

: 오버플로우가 많이 발생할수록 인덱스 성능이 저하된다.

 

 

 

 

 

 

 

 

 

4. B+ tree 인덱스 vs 해시 인덱스

1) 해시 인덱스

: 동등 비교 조건을 갖는 검색에 유리하다. (=)

 

 

2) B+ tree 인덱스

: 범위 조건을 포함한 검색에 유리하다. (<=, >=)

-> 검색 키 순서대로 정렬 및 연결되어 있기 때문이다.

 

 

 

 

5. 인덱스 분류

- 대응 밀집도에 따라

1) 희소 인덱스

: 테이블의 일부 코드에 대해서만 인덱스 엔트리를 생성한다,

 

2) 밀집 인덱스

: 테이블의 모든 코드에 대해서 인덱스 엔트리를 생성한다,

 

 

 

- 클러스터링 유무에 따라

1) 클러스터 인덱스

: 인덱스가 설정된 필드 값의 순서대로 레코드들이 인접하여 저장된다.

- 주로 기본 키에 대해 설정된다.

- 희소 인덱스 또는 밀집 인덱스 형태 모두 가능

 

2) 비클러스터 인덱스

: 인덱스 필드 값의 순서가 레코드들의 물리적 저장 위치와 무관하다.

: 반드시 밀집 인덱스 구조를 가진다.

 

 

 

 

 

 

6. 클러스터 인덱스

: 범위 조건 검색에 효과적이다.

 

 

 

: 정렬을 포함하는 질의에 효과적

 

 

 

 

 

 

 

 

 

 

 

 

 

7. 오라클의 인덱스 생성 구문

 

1) 일반 인덱스

 

 

예)

CREATE INDEX DEPT_INDEX ON DEPARTMENT(DEPT_NAME)

 

 

- dept_name의 값이 중복되지 않은 경우

CREATE UNIQUE INDEX DEPT_INDEX ON DEPARTMENT (DEPT_NAME)

 

 

 

 

2) 복합 인덱스

 

1) 

CREATE INDEX STU_INDEX2 ON STUDENT(NAME, ADDRESS)

 

 

 

 

 

 

 

3) 인덱스 삭제

 

DROP INDEX DEPT_INDEX

 

 

 

 

 

 

 

 

 

 

8. 언제, 어디서 인덱스를 만들어야 할까?

 

<유리한 경우>

- 테이블 레코드 수가 많을 때

- where 절에 자주 사용되는 필드일 때

- 조인 연산에 참여하거나 널이 많은 필드일 때

 

<불리한 경우>

- 테이블 레코드 수가 적을 때

- where 절에 거의 사용되지 않는 필드

- 삽입, 삭제, 수정이 자주 발생하는 테이블

- 서로 구별되는 유일 값의 개수가 적은 필드

: 여러 레코드들이 동일한 값을 가지므로 질의의 선택도가 낮음

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

 

 

 

1. B+ tree 인덱스

: 관계형 데이터베이스에서 가장 널리 사용되는 인덱스 구조이다.

 

 

 

 

1) 차수가 n인 B+tree

차수 : 한 노드에서 하위 자식 노드를 가리키는 주소의 개수이다.

 

중간 노드 구조

 

P1 ~ Pn : 자식 노드를 가리키는 포인터

Key1 ~ Keyn-1 : 검색 키

Pi+1이 가리키는 하위 노드의 모든 검색 키는 Keyi 보다 크고 Keyi+1보다 작거나 같다.

 

 

 

 

 

 

 

 

2) B+tree 의 특징(차수가 n이라고 가정)

: 단말 노드에 레코드의 주소를 저장한다.

- 레코드의 검색 키 값에 따라 인덱스 엔트리들이 정렬된 형태이다.

- 단말 노드들이 검색 키 순서에 따라 연결된다.(다음 노드의 주소를 포함)

 

: 루트 노드에서 단말 노드까지의 모든 경로의 길이가 같다. (balanced tree)

: 루트 노드는 최소 2개, 최대 n개의 자식 노드를 가진다.

: 루트와 단말 노드를 제외한 중간 노드들은 최소 [n/2] (올림기호), 최대 n개의 자식 노드를 가진다.

 

K개의 검색 키 값을 포함하는 B+tree의 높이 = [lognK]

 

 

 

 

 

3) B+tree 의 성능

: 차수가 10이고 검색 키의 값이 10,000개 이면 B+tree의 높이는 4를 넘지 않는다.

 

 

 

 

 

 

 

 

 

2. 복합 인덱스

: 두 개 이상의 필드에 대해 정의되는 인덱스이다.

 

 

- 인덱스 엔트리들은 검색키의 값은 순서대로 정렬된다.

- 효율적인 검색을 위해 B+ tree 구성이 가능하다.

 

 

 

1) 복합 인덱스 정렬 방법

: 검색 키 값이 숫자인 경우 크기로 정렬 가능하다.

: 문자열인 경우 사전식 순서에 따라 정렬한다.

: 문자열 / 숫자가 아닌 경우, 엔트리들 간에 대소 관계 정의가 필요하다.

 

 

 

 

 

 

 

 

 

 

 

2) 복합 인덱스의 효과

: 복합키 필드들에 대한 동등 조건을 포함하는 질의 처리 성능을 향상시킨다.

 

SELECT *
FROM TAKES
WHERE STU_ID = '1292001' AND CLASS_ID = 'C101-01';

 

: 복합 인덱스가 정의된 필드만을 이용하는 질의는 테이블 조회 없이 복합 인덱스 조회만으로 처리 가능하다.

 

 

 

 

 

 

3) 복합 인덱스 주의사항

: 관련 필드의 순서에 따라 인덱스의 효과가 달라진다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

 

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가 있는 첫 번째 노드를 가리킴

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts