구간트리


구간트리 (segment tree)

저장된 자료들을 적절히 전처리해 구간에 대한 질문에 빠르게 대답하는 트리

예를 들어, A = {1, 2, 1, 2, 3, 1, 2, 3, 4} 라면,

[2, 4] 구간의 최소치는 1 이다. [6, 8] 구간의 최소치는 2이다.

간단한 알고리즘으로 해당 배열을 순회하여 최소치를 찾아내면 O(n) 의 시간 소요된다.

반면, 해당 배열을 전처리해 구간 트리를 생성하면 같은 연산을 빠르게 구현할 수 있다.


구간트리의 기본 아이디어

구간트리의 기본 아이디어는 주어진 배열의 구간들을 표현하는 이진 트리를 만드는 것이다.

이 때, 루트는 항상 배열의 전체 구간[0, n-1]을 표현한다.

루트의 왼쪽 자식과 오른쪽 자식은 각각 해당 구간의 왼쪽 반과 오른쪽 반을 표현한다.

리프 노드는 길이가 1인 구간을 표현한다.


아래 그림은 배열 길이가 15일 때, 구간 트리의 각 노드가 표현하는 구간들을 보여준다.

구간트리 예시 종만북

각 노드는 해당 구간에 대한 계산 결과를 저장한다.

예를 들어, 구간의 최소치를 구하는 구간트리는 해당 구간의 최소치를 각 노드에 저장한다.

이러한 전처리 과정을 수행해두면 어떤 구간이 주어지더라도 이 구간을 구간들의 합집합으로 표현할 수 있다.

어떤 구간이 주어지건, 답을 찾기 위해 고려해야 하는 구간의 수는 O(logN)


구간트리의 표현

구간 트리를 다뤄보는 일반적인 예제로 '특정 구간의 최소치를 찾는 문제'가 있다.

구간 최소 쿼리(range minimum query, RMQ) 라고 한다.

  • 구간 트리는 보통 완전이진트리로 표현한다.

    (그래서 포인터로 연결된 리스트보다는 배열이 메모리 면에서 낫다.)

  • 루트 노드를 배열의 1번 원소로 표현한다.

  • 노드 i의 왼쪽 자식을 i * 2, 오른쪽 자식을 i * 2 + 1 번째 원소로 표현한다.

  • 배열의 길이 정하는 방법

    • 가장 가까운 2의 거듭제곱으로 n을 올림한 뒤, 2를 곱한다.

      (n=6이면, 그 이상의 거듭제곱은 8이다. 따라서 배열의 길이는 16으로 하는 식이다)

      귀찮다면 4n 으로 해도 된다. (4n은 모든 경우에 구간트리가 필요로 하는 배열크기보다 크다)

구간 트리의 초기화(init), 질의(query), 갱신(update) 연산을 알아보자.



1. 구간트리의 초기화 : init()

배열 array[] 가 주어지면, 각 노드마다 해당 구간의 최소치를 계산하는 함수 init()을 구현한다.

현재 구간을 2개로 나눠 재귀호출한 뒤, 두 구간의 최소치 중 더 작은 값을 선택해 해당 구간의 최소치를 계산한다.

// 배열의 구간 최소 쿼리 (RMQ) 문제를 해결하는 구간 트리의 초기화 
struct RMQ{
    // 배열 길이 
    int n;
    // 각 구간의 최소치 
    vector<int> rangeMin;
    RMQ(const vector<int>& array){

    }

    // node를 루트로 하는 서브트리를 초기화 하고, 이 구간의 최소치를 반환.
    int init(const vector<int>& array, int left, int right, int node){
        if(left == right)
            return rangeMin[node] = array[left];
        int mid = (left + right) / 2;
        int leftMin = init(array, left, mid, node * 2);
        int rightMin = init(array, mid +1, right, node * 2 + 1);
        return rangeMin[node] = min(leftMin, rightMin);
    }
};

노드의 수 만큼의 초기화 시간이 소요된다.

배열의 길이 O(n), 노드의 수 O(n), 초기화 시간 복잡도 O(n)


2. 구간트리의 질의 처리 : query()

구간 트리에서의 순회를 이용해 구현한다.

다음과 같이 질의 함수를 정의한다.

int query(left, right, node, nodeLeft, nodeRight) 

/* node가 표현하는 범위 [nodeLeft, nodeRight]와
우리가 최소치를 찾기 원하는 구간 [left, right]의 교집합의 
최소 원소를 반환한다. */

질의 연산은 아래의 3가지 경우를 처리한다.

  • 교집합이 공집합인 경우
    • 두 구간은 서로 겹치지 않으니까 반환 값도 없다.
    • 반환 값이 무시되도록 아주 큰 값을 반환하도록 하자.
  • 교집합이 [nodeLeft, nodeRight]인 경우
    • [left,right]가 노드가 표현하는 집합을 완전히 포함하는 경우.
    • 이 노드에 미리 계산해 둔 최소치를 곧장 반환하면 된다.
  • 이 외의 모든 경우
    • 양쪽 자식 노드에 대해 query()를 재귀 호출한 뒤, 이 두 값 중 더 작은 값을 택해 반환한다.

int query(left, right, node, nodeLeft, nodeRight);

/* node가 표현하는 범위 [nodeLeft, nodeRight]와
우리가 최소치를 찾기 원하는 구간 [left, right]의
교집합의 최소 원소를 반환한다. */

const int _INT_MAX = numeric_limits<int>::max();

// 배열의 구간 최소 쿼리를 해결하기 위한 구간 트리의 구현
struct RMQ{

    int query(int left, int right, int node, int nodeLeft, int nodeRight){
        // 두 구간이 겹치지 않으면 아주 큰 값을 반환한다: 무시됨
        if (right < nodeLeft || nodeRight < left) return _INT_MAX;
        // node가 표현하는 범위가 array[left .. right]에 완전히 포함되는 경우
        if (left <= nodeLeft && nodeRight <= right)
            return rangeMin[node];
        // 양쪽 구간을 나눠서 푼 뒤 결과를 합친다.
        int mid = (nodeLeft + nodeRight) / 2;
        return min(query(left, right, node * 2, nodeLeft, mid),
                   query(left, right, node * 2 +1, mid+1, nodeRight));
    }
    // query()를 외부에서 호출하기 위한 인터페이스
    int query(int left, int right){
        return query(left, right, 1, 0, n-1);
    }
};

아래 그림은 노드가 표현하는 구간과, 최소치를 찾기 원하는 구간이 겹치는 4가지 경우들이다.

노드가 표현하는 구간과 우리가 원하는 구간의 교집합은 짙은 색으로 칠해져 있다.

해당 구간이 노드에 완전히 포함되거나(전부 짙은 색) 아예 포함되지 않는다면(전부 옅은 색) 곧장 종료한다.

그림 (b), (c), (d)에서는 재귀 호출했을 때 양쪽 중 하나는 반드시 곧장 종료한다.

따라서, 그림 (a)처럼 두 구간이 겹쳐야만 양쪽 재귀 호출 둘 중 하나도 곧장 종료하지 않고 탐색을 계속하게 된다.

구간은 절반씩 쪼개지므로 질의 연산의 시간복잡도는 O(logN)이다.


3. 구간트리의 갱신 : update()

원래 배열의 index 위치의 값이 newValue로 바뀌었다고 하자.

이 위치를 포함하는 구간은 트리에 O(logN)개 있을것이다.

따라서 O(logN)개만 다시 계산하면 O(logN)시간에 구간트리를 갱신할 수 있다.

  • 갱신 과정은 query()와 init()을 합친 것처럼 구현된다.
  • 해당 노드가 표현하는 구간에 index가 포함되지 않는다면 그냥 무시한다.
  • 포함된다면 재귀 호출해서 두 자식 구간의 최소치를 계산한 뒤, 그 중에서 최소치를 구한다.

struct RMQ{
    // .. 생략 ..
    // array[index] = newValue로 바뀌었을 때 node를 루트로 하는
    // 구간 트리를 갱신하고 노드가 표현하는 구간의 최소치를 반환한다.
    int update(int index, int newValue, int node, int nodeLeft, int nodeRight){
        // index가 노드가 표현하는 구간과 상관없는 경우엔 무시한다.
        if (index < nodeLeft || nodeRight < index)
            return rangeMin[node];
        // 트리의 리프까지 내려온 경우
        if (nodeLeft == nodeRight) return rangeMin[node] = newValue;
        int mid = (nodeLeft + nodeRight) / 2;
        return rangeMin[node] = min(
                                    update(index, newValue, node*2, nodeLeft, mid),
                                    update(index, newValue, node*2+1,mid+1, nodeRight));
    }
    // update()를 외부에서 호출하기 위한 인터페이스
    int update(int index, int newValue){ 
        return update(index, newValue, 1, 0, n-1);
    }
};


참고

beakjoon 구간 트리

자세한 그림의 구간 트리 설명

간단 정리와 관련예제

구간 트리와 LIS

알고리즘 문제해결전략 (구종만 저)

728x90

'알고리즘 > 알고리즘 C' 카테고리의 다른 글

펜윅트리  (0) 2020.08.12
최소 스패닝 트리  (0) 2020.08.12
그래프의 정의와 표현방법  (2) 2020.08.02
우선순위 큐  (0) 2020.06.03
삽입 정렬 (중요)  (0) 2020.06.02

최소 스패닝 트리 (MST, Minimum cost spanning tree)

스패닝 트리 (spanning tree)

그래프에 있는 모든 정점을 포함하는 부분 그래프

N개의 정점(vertex)와 N-1개의 간선(edge)으로 구성

간선들이 트리 형태이며, 간선들이 모든 정점들을 연결해야 한다.

간선이 트리 형태여야 한다?

선택된 간선들이 사이클(cycle)을 이루지 않는다는 의미

연결 그래프에서 1개 이상의 스패닝 트리를 얻을 수 있다.

스패닝 트리


DFS, BFS로 얻을 수 있는 스패닝 트리

연결 그래프에서, DFS나 BFS는 암묵적으로 그래프 G의 간선(edge)을 두 개의 집합으로 분리한다.

  • T : 스패닝 트리의 edge 집합으로 사용되거나 search 도중에 방문
  • N : 남아있는 edge의 집합

아래 그림에서 V(정점)는 8개, E(간선)은 7개이다.

(만약 edge가 vertext의 개수와 같아지면 cycle이 생긴다. )

dfs bfs spanning tree

(a) DFS spanning tree

아직 방문하지 않은 정점으로 향하는 간선이 있으면 그 간선을 무조건 따라간다.

가능한 한 그래프 안으로 '깊이' 간선을 따라가며, 막힌 정점에 도달하면 뒤로 돌아간다.

(b) BFS spanning tree

각 정점을 방문할 때 마다 모든 인접 정점을 검사하므로 V0에서 V1과 V2 쪽으로 순차적으로 두 간선을 방문한다.


최소 스패닝 트리 (minimum cost spanning tree)

가중치 그래프의 스패닝 트리 중, 가중치의 합이 가장 작은 트리

즉, 그래프의 연결성은 유지하되 가장 '저렴한' 그래프

정점이 N개라면, 간선은 N-1개만을 포함한다.

사이클을 만드는 간선은 선택하지 않는다.



최소 스패닝 트리 알고리즘

크루스칼(Kruskal), 프림(Prim), 솔린(Sollin) 알고리즘이 대표적이다. 세 알고리즘 모두 탐욕 알고리즘이다.

탐욕 알고리즘(greedy method)

현재 상황에서 최선의 선택을 한다.

크루스칼과 프림은 상호 배타적 조합(Disjoint Set)을 기본 자료구조로 쓴다.


1. 크루스칼

모든 간선(edge)을 가중치의 오름차순으로 정렬한다.

가중치가 가장 작은 edge를 선택하여 스패닝 트리에 하나씩 추가해 간다.

이 때, 추가하면 사이클이 생기는 간선을 확인하여 제외시킨다.

이렇게 모든 간선을 한 번씩 검사하고 난 뒤 종료한다.

사이클이 발생했는지 판단하는 기준

Union-Find (유니온 파인드) 자료구조를 이용하여 O(1)에 확인 가능

트리에 추가된 정점, 그렇지 않은 정점. 이렇게 두 집합으로 정점들이 나뉜다.

사이클이 생기는 경우, 간선의 양 끝 정점은 같은 컴포넌트(집합)에 속한다.

두 정점이 주어 졌을 때, 이들이 같은 집합에 속하는지 확인(Find)하고,

다른 집합에 속하는 정점이라면 합치는 연산(Union)을 빠르게 지원한다.


크루스칼 알고리즘 탐색 예시

간선 가중치를 정렬한 결과 10, 12, 14, 16, 18, 22, 24, 25, 28

그림 (f)(g) 를 눈여겨보자.

(f) 10, 12, 14, 16을 추가한 트리

(g) 18을 추가하면 사이클이 발생하므로 18을 제외시키고, 22를 추가한다.


2. 프림

하나의 정점으로 구성된 트리에서 시작한다.

여기에 간선을 하나씩 추가하며 스패닝 트리가 될 때까지 키워 간다.

이미 만들어진 트리에 인접한 간선만 고려한다.

(인접한 간선만 고려한다는것 빼고는 크루스칼과 완전히 똑같다. )

시작 정점으로 부터 인접한 정점을 Priority Queue(pq)에 저장하는데, 이때 인접 정점과 간선의 가중치를 함께 저장한다.


프림 알고리즘 탐색 예시


크루스칼 vs 프림

크루스칼 프림
간선 선택 기반 정점 선택 기반
간선의 가중치를 기준으로 정렬하고 시작 인접 정점위주로 비교하므로 자료구조의 성능에 영향을 받음
정렬할 간선의 개수가 적은 경우에 유리 간선의 개수가 많은 경우에 유리

문제 : 최소 스패닝 트리

백준 1197번 최소 스패닝 트리


[참고]

유니온 파인드 개념

[출처]

크루스칼 알고리즘 그림

프림 알고리즘 그림

728x90

'알고리즘 > 알고리즘 C' 카테고리의 다른 글

펜윅트리  (0) 2020.08.12
구간트리 (세그먼트 트리)  (0) 2020.08.12
그래프의 정의와 표현방법  (2) 2020.08.02
우선순위 큐  (0) 2020.06.03
삽입 정렬 (중요)  (0) 2020.06.02

문제

2xn 타일링2 백준 11727번


리뷰

2xn 타일링 문제를 풀고 연이어 풀었다.

n이 1, 2, 3, 4 일 때 어떤 경우가 나오는지 그림을 그려보니까 규칙성을 발견할 수 있었다.

계속 숫자를 더하다보면 자료형의 크기를 넘을 수 있다는 힌트를 유념하면서 배열에 숫자를 저장하기 전에 mod 연산을 해줬다.


코드

#include <stdio.h>
using namespace std;

// 2xn 타일링2 (백준 11727) 
int arr[1001];

int main(void){

    int i = 0, n = 0;
    scanf("%d", &n);

    arr[1] = 1;
    arr[2] = 3;
    arr[3] = 5;

    for(i = 4; i <= n; i++){
        arr[i] = (arr[i-2] * 2 + arr[i-1]) % 10007;
    }

    printf("%d", arr[n]);

    return 0;
}
728x90

'알고리즘 > 백준' 카테고리의 다른 글

쉬운 계단 수 백준 10844  (0) 2020.08.14
카드 구매하기 백준 11052  (0) 2020.08.13
2xn 타일링 백준 11726번  (0) 2020.08.12
분산처리 백준 1009번 c++  (0) 2020.08.12
스택 백준 10828번  (0) 2020.08.12

문제

2xn 타일링 백준 11726번


리뷰

피보나치 수열과 비슷한 문제여서, 점화식은 만들었는데 계속 틀렸다.

문제 해설 포스팅을 구글링 해보니, 계속 숫자를 더하다보면 자료형의 크기를 넘을 수 있다는 힌트를 발견했다.

그래서 배열에 숫자를 저장하기 전에 mod 연산을 해줬다.

왜냐하면 결국 답은 mod 연산 결과이기 때문이다.


코드

#include <stdio.h>
using namespace std;

// 2xn 타일링 (백준 11726) 

int arr[1001]; 

int main(void){

    int n = 0;
    int i = 0;

    //freopen("input.txt", "rt", stdin);

    scanf("%d", &n);

    arr[1] = 1;
    arr[2] = 2;

    for(i = 3; i <= n; i++){

        arr[i] = (arr[i-1] + arr[i-2]) % 10007;

    }

    printf("%d", arr[n]);

    return 0;
}

참고 포스팅

728x90

'알고리즘 > 백준' 카테고리의 다른 글

카드 구매하기 백준 11052  (0) 2020.08.13
2xn 타일링2 백준 11727번  (0) 2020.08.12
분산처리 백준 1009번 c++  (0) 2020.08.12
스택 백준 10828번  (0) 2020.08.12
괄호 백준 2012  (0) 2019.07.21

문제

분산처리 백준 1009번

리뷰

제곱을 했을 때 가장 마지막 자리의 숫자가 뭔지 출력하면 되는 문제다.

b의 입력 조건이 큰 것을 감안해야 한다.

 

제곱을 하는 와중에도 '마지막 자리 수'만 잘라내서 제곱을 해야 int 범위를 넘지 않는다.

그래서 temp = temp * a 이렇게 제곱을 한 다음에 %10 나머지 연산을 통해 마지막 숫자를 잘라냈다.

for(i = 2; i <=b; i++){
    temp = temp * a % 10; // 마지막 자리 숫자 잘라내기 
}

이렇게 해도 자꾸 '틀렸습니다' 가 뜨길래 문제 해설 포스팅을 찾아봤다.

힌트는 제곱을 했을 때 4번 주기로 같은 숫자가 나온다는 것이었다.
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 6 (마지막 자리 숫자만 씀)
2^5 = 2
2^6 = 4

이렇게 2, 4, 6, 8 이 반복된다.

 

(4번째 제곱수마다 마지막 자리숫자들이 똑같이 반복되는 것을 이용한다. )

그래서 제곱 횟수 b를 % 4 연산을 했고, b가 0이 되는 것을 방지하기 위해 + 4를 해준 것이다.

b = b % 4 + 4; 

제곱횟수 b를 mod 연산을 하니까 그제서야 문제를 맞혔다. 아 개운하다 다음에 다시풀어봐야지 :)

코드

#include <stdio.h>
using namespace std;

// 분산처리 (BOJ 1009번) 

int main(void){

    //freopen("input.txt", "rt", stdin);

    int tc = 0, a = 0, b = 0;
    int temp = 0, i = 0; 

    scanf("%d", &tc);

    while(tc--){
        scanf("%d %d", &a, &b);

        temp = a;

        b = b % 4 + 4;  // b가 0이 되는 것을 방지하려고 +4 한다. 

        for(i = 2; i <= b; i++){ // 마지막 자리 숫자만 잘라낸다 
            temp = temp * a % 10;
        }

        if(temp == 0)
            temp=10;

        printf("%d\n", temp);
    }

    return 0;
}

참고 포스팅

크레이 님의 분산처리 문제 해설

크레이 님의 친절한 포스팅 감사합니다! :)

728x90

'알고리즘 > 백준' 카테고리의 다른 글

카드 구매하기 백준 11052  (0) 2020.08.13
2xn 타일링2 백준 11727번  (0) 2020.08.12
2xn 타일링 백준 11726번  (0) 2020.08.12
스택 백준 10828번  (0) 2020.08.12
괄호 백준 2012  (0) 2019.07.21

문제

스택 백준 10828번

리뷰

C++ STL stack에 구현된 함수들과 으로 금방 풀 수 있었다.

코드

#include <stdio.h>
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <string>
#include <algorithm>
using namespace std;

// 스택 (BOJ 10828번)  

int main(void){

    int order = 0, i = 0; 
    int num = 0;
    stack<int> st;
    string input = "";

    freopen("input.txt", "rt", stdin);

    scanf("%d", &order);

    for(i=0; i<order; i++){

        cin >> input;

        if(input == "push"){ 

            scanf("%d", &num);
            st.push(num);

        }else if(input == "pop"){

            if(!st.empty()){
                num = st.top();
                st.pop();
            }else{
                num = -1;
            }
            printf("%d\n", num);

        }else if(input == "top"){

            if(!st.empty()){
                num = st.top();
            }else{
                num = -1;
            }

            printf("%d\n", num);

        }else if(input == "size"){
            printf("%d\n", st.size());

        }else if(input == "empty"){
            printf("%d\n", st.empty());
        }

    }

    return 0;
}
728x90

'알고리즘 > 백준' 카테고리의 다른 글

카드 구매하기 백준 11052  (0) 2020.08.13
2xn 타일링2 백준 11727번  (0) 2020.08.12
2xn 타일링 백준 11726번  (0) 2020.08.12
분산처리 백준 1009번 c++  (0) 2020.08.12
괄호 백준 2012  (0) 2019.07.21

그래프의 정의와 표현

연결 관계를 표현하기 위한 자료구조

그래프의 정의

그래프 G(V, E)는 어떤 자료나 개념을 표현하는 정점(vertex)의 집합 V와 정점들을 연결하는 간선(edge)들의 집합 E로 구성된 자료구조이다.

그래프는 정점과 간선으로 정의된다. G(V, E)

(cf. 정점의 위치나 간선의 순서 등은 그래프 정의에 포함되지 않는다.)

관련 용어

각 정점에 연결된 간선의 갯수를 차수(degree)라고 한다.

유향 그래프에서는 간선에 방향성이 존재하므로 차수가 두 개로 나뉜다.

정점으로 들어오는 간선의 갯수를 진입차수(in-degree), 정점에서 나가는 간선의 갯수를 진출차수(out-degree) 라고 한다.

그래프의 종류

그래프는 표현하려는 대상에 따라 속성이나 형태의 제약을 추가할 수 있다.

ex) 간선에 방향 속성 추가, 간선에 가중치 속성 추가, 정점을 2개의 그룹으로 나누기 etc.

그래프의 종류

  • 방향 속성
    • 방향그래프(directed graph) 혹은 유향그래프두 정점 v와 u 가 있을 때, v->u 방향의 간선과 u->v 방향의 간선은 서로 다른 간선이다.
    • : 간선에 특정한 방향이 정해진 그래프
    • 무향그래프(undirected graph) : 간선에 방향이 없는 그래프
    • : 간선을 양방향으로 지날 수 있다.
  • 가중치 속성
    • 가중치 그래프(weighted graph) : 간선에 가중치(weight) 라는 실수 속성을 부여
  • 형태 속성
    • 다중 그래프(multigraph) : 두 정점 사이에 2개 이상의 간선이 존재할 수 있는 그래프
    • 단순 그래프(simple graph) : 두 정점 사이에 간선이 최대 1개만 있는 그래프
  • 트리 혹은 루트 없는 트리(unrooted tree)
  • : 부모 자식 관계는 없고, 두 정점간의 간선이 1개만 있는 그래프
  • 이분 그래프 (bipartite graph)
  • : 그래프의 정점들을 겹치지 않는 두 개의 그룹으로 나눈다. 서로 다른 그룹에 속한 정점 간에만 간선이 존재하도록 만든 그래프
  • DAG (사이클 없는 방향 그래프, directed acyclic graph): 상호 의존 관계 또는 순서가 정해진 작업들을 표현할 때 활용
  • cf) 위상정렬 : 순서가 정해진 작업들에서 순서를 정렬하는 알고리즘 (그래프의 정점을 정렬)
  • : 한 점에서 출발해 자기 자신으로 돌아오는 경로(사이클)이 없는 그래프

그래프의 경로

경로(path)란, 끝과 끝이 서로 연결된 간선들을 나열한 것이다.

  • 단순 경로(simple path) : 한 정점을 최대 한 번만 지나는 경로 (1-5-4-2)
  • 사이클 (cycle 혹은 회로) : 시작한 정점에서 끝나는 경로 (1-5-4-1)

특별한 명시가 없다면 ''경로''는 보통 ''단순 경로''를 의미한다.

  • 연결그래프(connected graph) : 모든 정점쌍들에 대해 ''경로''가 존재하는 그래프
  • 비연결그래프(disconnected graph) : 특정 정점쌍 사이에 ''경로''가 없는 그래프
  • 완전그래프(complete graph) : 모든 정점들이 연결되어 있는 그래프

그래프의 표현 방법

대부분 그래프는 트리에 비해 정적인 용도로 사용된다.

정적이라는 뜻은 새로운 정점이나 간선을 추가/삭제 하는 일이 드물다는 의미다.

따라서 대부분 그래프는 구조의 수정이 어렵더라도 간단하고 메모리를 적게 차지하는 방법으로 구현하는 편이다.

인접 리스트 (adjacency list)

그래프의 각 정점마다 해당 정점에서 나가는 간선의 목록(인접 노드)을 저장해서 그래프를 표현한다.

즉, 각 정점마다 하나의 연결 리스트를 갖는 방식으로 구현한다.

정점 간에 인접한지 list를 모두 확인하는 데에 오래 걸린다.

인접 리스트

  • 무향 그래프에서 (a, b) 간선은 2 번 저장된다. (a->b) (b->a)는 다르기 때문이다.
  • 유향 그래프에서는 (a, b)는 특정 방향이 존재하므로 한 번만 저장된다.
vector<list<int> > adjacent;
간선의 가중치 그래프를 표현하려면?
struct Edge{
    int vertex; // 간선의 반대쪽 끝 정점의 번호 
    int weight; // 간선의 가중치 
}

또는 단순하게 pair STL 이 자주 쓰인다.

pair<int, int>  // 도착 정점, 가중치

가중치 있는 무향 그래프에서 정점, 간선, 가중치 입력받기

무향그래프 가중치 있음

(pair 객체 사용)

시작 정점, 도착 정점, 가중치 순으로 입력받는다. 도착정점과 가중치를 pair로 묶는다.

  • 그래프 구조 : 정점 개수 5, 간선 개수 6
  • 입력 : 시작 정점, 도착 정점, 가중치
- 아래와 같이 입력이 주어진다 -
 5 6 // 정점 개수, 간선 개수
 1 2 1 // 시작 정점, 도착 정점, 가중치 순서
 4 1 5
 2 3 3
 5 2 2
 3 5 4
 4 5 6
  • 입력받는 코드
#include <vector>
#include <stdio.h>
using namespace std;

int ver, ed; // 정점, 간선
vector<pair<int,int> > vertex[200];

int main(void){
    scanf("%d %d", &ver, &ed); // 정점 수, 간선 수 입력받기

    for(int i = 0; i < ver; i++){
        int start, end, weight = 0;
        scanf("%d %d %d", &start, &end, &weight);

        vertex[start].push_back(make_pair(end, weight));
        vertex[end].push_back(make_pair(start, weight)); // 무방향 그래프니까 반대 경우도 저장 
    }

}

인접 행렬 (adjacency matrix)

V x V 크기의 2차원 배열로 간선 정보를 저장

  • 무향 그래프의 경우: 행렬에 대각선을 그어보면 대칭 모양이 나온다. 대칭 행렬(Symmetric Matrix)
  • : 정점 1에서 2, 정점 2에서 1 모두 연결되어 있으므로 1로 표시하고, 연결이 안 된 정점끼리 0으로 표시한다.

인접 행렬

  • 유향 그래프예를 들어, 정점 6은 가리키고 있는 정점이 5밖에 없다. 정점 6의 인접 정점은 5뿐이다.
  • 인접 행렬 유향 그래프
  • : 정점 자신이 가리키고 있는 정점에 대해서만 인접해 있다고 표현한다.
vector<vector<bool> > adjacent; // 유향 그래프
vector<vector<int> > adjacent_weight; // 가중치 유향 그래프
  • 가중치 가진 유향 그래프
    가중치 가진 유향 그래프
  • : 간선의 정보를 bool 값 대신 정수나 실수 변수로 쓰면 된다.

인접 리스트와 인접 행렬 표현의 비교

두 방식은 정반대의 특성을 가져서 한 방식의 단점이 다른 방식의 장점이 된다. 구현하려는 알고리즘이나 그래프의 종류에 맞게 사용하자.

     
  인접 리스트 인접 행렬
간선 접근 시간 정점 간에 간선이 존재하는지 순차 탐색 (정점의 차수) 검색 시간 O(1)
메모리 O V
그래프의 종류 희소 그래프의 경우 적합 밀집 그래프에 경우 적합
  • 희소 그래프 : 간선의 수가 V^2 에 비해 적은 그래프
  • 밀집 그래프 : 간선의 수가 V^2 에 비례하는 그래프.

그래프와 트리 비교

     
항목 그래프 트리
자료구조의 쓰임  연결 관계 표현 계층 구조 표현 
정의 정점 집합과 간선 집합으로 정의 부모 노드- 자식 노드로 구성
간선의 수 그래프에 따라 간선의 수가 다름.
N개의 노드가 있으면 간선은 N-1개
간선이 없을수도 있음. 두 노드 간의 경로는 유일
루트 개념 없음 한 개의 루트 노드 존재
루트를 제외한 모든 노드는 하나의 부모만을 가짐
부모 - 자식 개념 없음 부모 - 자식 관계 있음
사이클 사이클 가능 사이클 불가능
자체 간선 자체 간선 가능 (자신에서 시작해서 자신으로) 자체 간선 불가능

[참고]

그래프 인접행렬 인접리스트

알고리즘 문제해결전략(도서 종만북 요약)

도움이 되셨다면 '공감'을 눌러주세요 :)

728x90

'알고리즘 > 알고리즘 C' 카테고리의 다른 글

구간트리 (세그먼트 트리)  (0) 2020.08.12
최소 스패닝 트리  (0) 2020.08.12
우선순위 큐  (0) 2020.06.03
삽입 정렬 (중요)  (0) 2020.06.02
버블 정렬  (0) 2020.05.30

+ Recent posts