[자료구조] 그래프 (Graph) - 인접행렬 vs 인접리스트, DFS, BFS, Connected Component, Spanning Tree
IT/Data Structure

[자료구조] 그래프 (Graph) - 인접행렬 vs 인접리스트, DFS, BFS, Connected Component, Spanning Tree

728x90
반응형

1. 그래프

1.1 그래프란?

그래프(Graph)란 요소들이 서로 복잡하게 연결되어 있는 관계를 표현하는 자료구조이다.

그래프는 정점(vertex)과 간선(edge)들의 집합으로 구성된다.

 

G = (V, E)

수학적으로 그래프를 표시하는 방법이다.

V(G)는 그래프 G의 정점들의 집합을, E(G)는 그래프 G의 간선들의 집합을 의미한다.

 

그래프는 수학자 오일러(Euler)에 의해 창안되어 수학의 한 분야로서 수백 년 간 연구되어 왔지만, 컴퓨터 과학 분야에서 그래프 알고리즘을 다루기 시작한 것은 오래되지 않았다.

지하철 노선도, 도심의 도로 등 실생활의 다양한 예를 그래프로 표현하고 활용할 수 있으며, 특히 알고리즘에서 굉장히 많이 사용되므로 그래프에 대해 반드시 공부해야 한다.

 

※ 오일러 경로 (Eulerian Tour)

  • 그래프에 존재하는 모든 간선을 한번만 통과하면서 처음 정점으로 되돌아오는 경로
  • 그래프의 모든 정점에 연결된 간선의 개수가 짝수일 때만 오일러 경로가 존재 (오일러의 정리)

 

1.2 그래프 용어

  • 정점 (vertex)

    노드(node)라고도 하며 데이터가 저장되는 그래프의 기본 원소이다.

  • 간선 (edge)

    링크(link)라고도 하며 정점 간의 관계를 나타낸다.

  • 인접 정점 (adjacent vertex)

    하나의 정점에서 간선에 의해 직접 연결되어 있는 정점을 뜻한다.
    위 그래프에서 정점 C의 인접 정점은 A, D 이다.

  • 차수(degree)

    정점에 연결된 간선의 수를 말한다.
    위 그래프에서 정점 A의 차수는 3이고, 모든 정점의 차수를 합하면 8이다.
    무방향 그래프에서 하나의 간선은 두 개의 정점에 인접하기 때문에 간선 수에 2배를 해주면 된다.

    방향 그래프의 경우 외부에서 오는 간선의 수를 진입 차수(in-degree)라고 하며, 외부로 향하는 간선의 수를 진출 차수(out-degree)라고 한다.

  • 경로 (path)

    간선을 따라갈 수 있는 길을 말하며, 정점을 나열하여 표시한다.
    정점 s로부터 정점 e까지의 경로는 s, v₁, v₂ ... e로 표현하며, 반드시 정점들 간의 간선이 존재해야 한다.
    너무나도 당연하게도 간선이 존재하지 않는 길은 경로가 될 수 없다.

  • 경로의 길이 (length)

    경로를 구성하는 데 사용된 간선의 수를 뜻한다.

  • 단순 경로 (simple path)

    경로 중에서 반복되는 간선이 없는 경로이다.

  • 사이클 (cycle)

    시작 정점과 종료 정점이 같은 단순 경로를 뜻한다.
    위 그래프에서 A, C, D, A 경로를 사이클이라고 한다.

 

추가로 그래프를 표현하는 방법에 대해서도 알아보자.

  • V(G1) = {0, 1, 2, 3}, E(G1) = {(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)}
  • V(G2) = {0, 1, 2, 3}, E(G2) = {(0, 1), (0, 2)}
  • V(G3) = (0, 1, 3}, E(G3) = {<0, 1>, <1, 0>, <1, 2>}

 

1.3 그래프의 종류

그래프는 간선(edge)의 종류에 따라 구분된다.

 

무방향 그래프 (Undirected Graph)

두 정점을 연결하는 간선에 방향이 없는 그래프이며, 양 방향으로 이동이 가능하다.

위 그래프의 경우 (A, B)로 표현한다.

 

방향 그래프 (Directed Graph)

두 정점을 연결하는 간선에 방향이 존재하는 그래프이며, 간선의 방향으로만 이동할 수 있다.

위 그래프의 경우 <A, B>로 표현한다.

 

가중치 그래프 (Weighted Graph)

간선에 비용(cost) 또는 가중치(weight)가 할당된 그래프이다. 네트워크(network)라고 불리기도 한다.

 

완전 그래프 (Complete Graph)

모든 정점 간에 간선이 존재하는 그래프이다.

무방향 완전 그래프의 정점의 수가 n이라면, 전체 간선의 수는 n * (n-1) / 2 가 된다.

위 그래프의 경우 n=4이며 간선의 수는 (4*3)/2=6 이다.

 

1.4 그래프 ADT

객체

  • 정점의 집합과 간선의 집합

연산

  • create() : 그래프 생성
  • insertVertex(v) : 그래프에 정점 v 삽입
  • insertEdge(u, v) : 그래프에 u정점과 v정점을 연결하는 간선 삽입
  • deleteVertex(v) : 그래프에서 정점 v 삭제 (v에 연결된 모든 간선도 함께 삭제)
  • deleteEdge(u, v) : 그래프에서 u정점과 v정점을 연결하는 간선 삭제
  • adjacent(v) : 정점 v에 인접한 모든 정점을 반환

 

1.5 그래프 구현 - 인접 행렬 (Adjacency Materix)

컴퓨터에서 그래프를 구현하는 방법에는 배열(Array)을 사용하는 방법과 연결리스트(Linked List)를 사용하는 방법이 있다.

 

인접 행렬을 이용한 그래프 구현

그래프의 정점을 2차원 배열로 만든 것이다.

 

정점의 개수가 n이라면 n*n 형태의 2차원 배열이 인접 행렬로 사용된다.

인접 행렬에서 행과 열은 정점을 의미하며, 각각의 원소들은 정점 간의 간선을 나타낸다.

 

무방향 그래프는 (a), (b)에서 볼 수 있듯이 인접 행렬이 대칭적 구조를 가진다.
(두 개의 정점에서 간선이 동시에 연결되어 있기 때문)

가중치 그래프의 경우 행렬에서 0과 1이 아니라 각 간선의 가중치 값이 저장된다.
(이 경우 가중치가 0인 것과 간선이 없는 것이 구별돼야 함)

 

장점

  • 2차원 배열에 모든 정점들의 간선 정보가 있기 때문에, 두 정점을 연결하는 간선을 조회할 때 O(1) 시간복잡도로 가능하다.

  • 정점(i)의 차수를 구할 때는 다음과 같이 인접행렬(M)의 i번째 행의 값을 모두 더하면 되므로 O(n)의 시간복잡도를 가진다.

    ( $degree(i) = \sum_{k=0}^{n-1}{M[i][k]}$ )

  • 구현이 비교적 간단하다.

 

단점

  • 간선의 수와 무관하게 항상 n² 크기의 2차원 배열이 필요하므로 메모리 공간이 낭비된다.
  • 그래프의 모든 간선의 수를 알아내려면 인접행렬 전체를 확인해야 하므로 O(n²)의 시간이 소요된다.

 

C++ 구현

/*
 * 9.1
 * 인접 행렬(Adjacency Matrix)를 이용한 Graph 구현
 */
#include <iostream>
using namespace std;

#define MAX_VTXS 256    // 최대 정점 개수

class AdjMatGraph{
private:
    int size;                       // 정점의 개수
    char vertices[MAX_VTXS];        // 정점의 이름
    int adjMat[MAX_VTXS][MAX_VTXS];    // 인접 행렬

public:
    AdjMatGraph(){
        reset();
    }
    ~AdjMatGraph(){}

    char getVertex(int i){
        return vertices[i];
    }
    int getEdge(int i, int j){
        return adjMat[i][j];
    }
    void setEdge(int i, int j, int val){
        adjMat[i][j] = val;
    }

    // 그래프 초기화
    void reset(){
        for(int i=0; i<MAX_VTXS; i++){
            for(int j=0; j<MAX_VTXS; j++){
                setEdge(i, j, 0);
            }
        }
        size = 0;
    }

    // 정점 삽입
    void insertVertex(char name){
        if(isFull()){
            cout << "Graph vertex full error" << endl;
            return;
        }

        vertices[size++] = name;
    }

    // 간선 삽입 (무방향 그래프)
    void insertEdge(int u, int v){
        setEdge(u, v, 1);       // 가중치 그래프에서는 1이 아닌 가중치 삽입
        setEdge(v, u, 1);       // 방향 그래프에서는 삭제 (<u,v>만 존재)
    }

    // 그래프 정보 출력
    void display(){
        cout << "vertex size : " << size << endl;
        cout << "    ";
        for(int i=0; i<size; i++){
            cout << getVertex(i) << " ";
        }
        cout << endl;

        for(int i=0; i<size; i++){
            cout << getVertex(i) << " : ";
            for(int j=0; j<size; j++){
                cout << getEdge(i, j) << " ";
            }
            cout << endl;
        }
    }

    bool isEmpty(){
        return size == 0;
    }
    bool isFull(){
        return size >= MAX_VTXS;
    }
};

int main(){
    AdjMatGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3

    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(0, 3);     // A->D
    graph.insertEdge(2, 3);     // C->D

    graph.display();

    return 0;
}

인접행렬을 이용하여 아래와 같은 그래프를 구현하였다.

 

1.6 그래프 구현 - 인접 리스트 (Adjacency List)

인접 리스트를 이용한 그래프 구현

그래프의 각 정점에 인접한 정점들을 연결리스트(Linked List)로 표현하는 방법이다.

 

즉 정점의 개수만큼 인접리스트가 존재하며, 각각의 인접리스트에는 인접한 정점 정보가 저장되는 것이다.

그래프는 각 인접리스트에 대한 헤드포인터를 배열로 갖는다.

 

무방향 그래프의 경우 간선이 추가되면 각각의 정점의 인접리스트에 반대편 정점의 노드를 추가해야 한다.

 

장점

  • 존재하는 간선만 관리하면 되므로 메모리 사용 측면에서 보다 효율적이다.
  • 그래프의 모든 간선의 수를 알아내려면 각 정점의 헤더 노드부터 모든 인접리스트를 탐색해야 하므로 O(n+e)의 시간이 소요된다.

 

단점

  • 두 정점을 연결하는 간선을 조회하거나 정점의 차수를 알기 위해서는 정점의 인접 리스트를 탐색해야 하므로 정점의 차수만큼의 시간이 필요하다. O(degree(v))
  • 구현이 비교적 어렵다.

 

C++ 구현

/*
 * 9.2
 * 인접 리스트(Adjacency List)를 이용한 Graph 구현 (무방향 그래프)
 */
#include <iostream>
using namespace std;

#define MAX_VTXS 256    // 최대 정점 개수

struct Node{
private:
    int id;         // vertex id
    Node* link;     // next node's pointer

public:
    Node(int _id, Node* _link){
        id = _id;
        link = _link;
    }
    ~Node(){};

    // getter/setter
    int getId(){
        return id;
    }
    void setId(int _id){
        id = _id;
    }
    Node* getLink(){
        return link;
    }
    void setLink(Node* _link){
        link = _link;
    }
};

class AdjListGraph{
private:
    int size;                   // 정점의 개수
    char vertices[MAX_VTXS];    // 정점의 이름
    Node* adjList[MAX_VTXS];    // 인접 리스트

public:
    AdjListGraph(){
        size = 0;
    }
    ~AdjListGraph(){}

    char getVertex(int i){
        return vertices[i];
    }

    // 그래프 초기화
    void reset(){
        for(int i=0; i<size; i++){
            if(adjList[i] != NULL){
                delete adjList[i];
            }
        }
        size = 0;
    }

    // 정점 삽입
    void insertVertex(char name){
        if(isFull()){
            cout << "Graph vertex full error" << endl;
            return;
        }

        vertices[size] = name;
        adjList[size++] = NULL;
    }

    // 간선 삽입 (무방향 그래프)
    void insertEdge(int u, int v){
        // 인접리스트에 추가
        adjList[u] = new Node(v, adjList[u]);   // 새로운 노드로 head pointer가 바뀜 (새로운 node는 기존의 head pointer를 link로 함)
        adjList[v] = new Node(u, adjList[v]);   // 방향 그래프에서는 삭제 (<u,v>만 존재)
    }

    // 그래프 정보 출력
    void display(){
        cout << "vertex size : " << size << endl;
        for(int i=0; i<size; i++){
            cout << getVertex(i) << " : ";
            Node* head = adjList[i];
            while(head != NULL){
                cout << getVertex(head->getId()) << " ";
                head = head->getLink();
            }
            cout << endl;
        }
    }

    // 'v'번째 정점의 인접 정점 리스트 반환
    Node* adjacent(int v){
        return adjList[v];
    }

    bool isEmpty(){
        return size == 0;
    }
    bool isFull(){
        return size >= MAX_VTXS;
    }
};

int main(){
    AdjListGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3

    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(0, 3);     // A->D
    graph.insertEdge(2, 3);     // C->D

    graph.display();

    return 0;
}

동일하게 아래의 그래프를 인접 리스트로 구현한 것이다.

 

1.7 인접 행렬 vs 인접 리스트

마치 어떻게 활용될지에 따라 ArrayList와 LinkedList를 고민하는 것처럼, 이 또한 상황에 따라 알맞은 방법을 선택해야 한다.

 

만약 10억 개의 노드가 있고 각 노드가 2개씩의 간선만 있는 상황이다. 인접행렬로 구현한 그래프에서는 한 정점의 차수를 구할 때 10억번의 연산을 수행할 것이다. 반면, 인접리스트로 구현한 그래프에서는 2번의 연산만 수행하면 된다.

정점의 개수에 비해 간선의 개수가 매우 적은 희소 그래프(sparse graph)에서는 인접리스트가 유리할 수 있고, 모든 정점간에 간선이 존재하는 완전 그래프(Complete Graph)에서는 인접행렬이 유리할 수 있다.

그래프에서 주로 어떤 연산이 수행되는지도 매우 중요하게 고려되어야 할 것이다.

 

답은 없다. 상황에 따라 최선의 방법을 선택하는 것이 프로그래머의 덕목이다.

 


 

2. 그래프 활용

2.1 깊이우선 탐색 (DFS)

깊이우선 탐색(DFS: Depth First Search)이란 특정 노드에서 시작하여 다음 분기(branch)로 넘어가기 전에 해당 분기를 완벽하게(끝까지) 탐색하는 방법을 뜻한다.

 

아래 그래프를 깊이우선 탐색해보자

 

인접행렬을 이용한 DFS 구현 (C++)

// DFS 탐색 기능이 추가된 그래프
class SearchAdjMatGraph : public AdjMatGraph{
private:
    bool visited[MAX_VTXS];     // 방문 기록

public:
    // 모든 정점의 방문기록을 false로 초기화
    void resetVisited(){
        for(int i=0; i<size; i++){
            visited[i] = false;
        }
    }

    // 깊이우선탐색 (순환)
    void dfs(int v){
        resetVisited();
        dfsRecur(v);
    }
    void dfsRecur(int v){
        visited[v] = true;      // 현재 vertex 방문처리
        cout << getVertex(v) << " ";

        for(int n=0; n<size; n++){
            if(isLinked(v, n) && visited[n]==false){
                dfsRecur(n);
            }
        }
    }
};

int main(){
    SearchAdjMatGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3
    graph.insertVertex('E');    // 4
    graph.insertVertex('F');    // 5
    graph.insertVertex('G');    // 6
    graph.insertVertex('H');    // 7
    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(1, 3);     // B->D
    graph.insertEdge(2, 3);     // C->D
    graph.insertEdge(2, 4);     // C->E
    graph.insertEdge(3, 5);     // D->F
    graph.insertEdge(4, 6);     // E->G
    graph.insertEdge(4, 7);     // E->H
    graph.insertEdge(6, 7);     // G->H

    cout << "== Display Graph == " << endl;
    graph.display();

    cout << "-DFS => ";
    graph.dfs(0);

    return 0;
}

 

인접리스트를 이용한 DFS 구현 (C++)

// DFS 탐색 기능이 추가된 그래프
class SearchAdjListGraph : public AdjListGraph{
private:
    bool visited[MAX_VTXS];     // 방문 기록

public:
    // 모든 정점의 방문기록을 false로 초기화
    void resetVisited(){
        for(int i=0; i<size; i++){
            visited[i] = false;
        }
    }

    // 깊이우선탐색 (순환)
    void dfs(int v){
        resetVisited();
        dfsRecur(v);
    }
    void dfsRecur(int v){
        visited[v] = true;      // 현재 vertex 방문처리
        cout << getVertex(v) << " ";

        // v 정점의 인접리스트를 모두 방문하며 탐색
        for(Node *p=adjList[v]; p!=NULL; p=p->getLink()){
            if(visited[p->getId()] == false){
                dfsRecur(p->getId());
            }
        }
    }
};

int main(){
    SearchAdjListGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3
    graph.insertVertex('E');    // 4
    graph.insertVertex('F');    // 5
    graph.insertVertex('G');    // 6
    graph.insertVertex('H');    // 7
    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(1, 3);     // B->D
    graph.insertEdge(2, 3);     // C->D
    graph.insertEdge(2, 4);     // C->E
    graph.insertEdge(3, 5);     // D->F
    graph.insertEdge(4, 6);     // E->G
    graph.insertEdge(4, 7);     // E->H
    graph.insertEdge(6, 7);     // G->H

    cout << "== Display Graph == " << endl;
    graph.display();

    cout << "-DFS => ";
    graph.dfs(0);

    return 0;
}

 

DFS 결과 순서가 다른 이유는 인접 리스트의 노드 순서가 역순으로 들어있기 때문이다.

 

 시간복잡도

정점의 수가 n이고 간선의 수가 e인 그래프를 인접 행렬로 구현하는 경우 DFS 시간복잡도O(n²) 이며, 인접 리스트로 구현하는 경우 DFS 시간복잡도O(n+e) 이다.

 

2.2 너비우선 탐색

너비우선 탐색(BFS: Breadth First Search)이란 특정 노드에서 시작하여 인접한 노드를 먼저 탐색해나가는 방법을 뜻한다.

 

이번에는 아래 그래프를 너비우선 탐색해보자

 

인접행렬을 이용한 BFS 구현 (C++)

// BFS 탐색 기능이 추가된 그래프
class SearchAdjMatGraph : public AdjMatGraph{
private:
    bool visited[MAX_VTXS];     // 방문 기록

public:
    // 모든 정점의 방문기록을 false로 초기화
    void resetVisited(){
        for(int i=0; i<size; i++){
            visited[i] = false;
        }
    }

    // 너비우선탐색 (큐 이용)
    void bfs(int v){
        resetVisited();

        visited[v] = true;      // 현재 vertex 방문처리
        cout << getVertex(v) << " ";

        queue<int> queue;
        queue.push(v);          // 시작 vertex enqueue

        while(!queue.empty()){
            v = queue.front();
            queue.pop();        // v = queue.dequeue()

            // 인접 정점 탐색
            for(int n=0; n<size; n++){
                if(isLinked(v, n) && visited[n]==false){
                    cout << getVertex(n) << " ";
                    visited[n] = true;  // 방문 처리
                    queue.push(n);
                }
            }
        }

    }
};

int main(){
    SearchAdjMatGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3
    graph.insertVertex('E');    // 4
    graph.insertVertex('F');    // 5
    graph.insertVertex('G');    // 6
    graph.insertVertex('H');    // 7
    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(1, 3);     // B->D
    graph.insertEdge(2, 3);     // C->D
    graph.insertEdge(2, 4);     // C->E
    graph.insertEdge(3, 5);     // D->F
    graph.insertEdge(4, 6);     // E->G
    graph.insertEdge(4, 7);     // E->H
    graph.insertEdge(6, 7);     // G->H

    cout << "== Display Graph == " << endl;
    graph.display();

    cout << "-BFS => ";
    graph.bfs(0);

    return 0;
}

 

인접리스트를 이용한 BFS 구현 (C++)

// BFS 탐색 기능이 추가된 그래프
class SearchAdjListGraph : public AdjListGraph{
private:
    bool visited[MAX_VTXS];     // 방문 기록

public:
    // 모든 정점의 방문기록을 false로 초기화
    void resetVisited(){
        for(int i=0; i<size; i++){
            visited[i] = false;
        }
    }

    // 너비우선탐색 (큐 사용)
    void bfs(int v){
        resetVisited();

        visited[v] = true;      // 현재 vertex 방문처리
        cout << getVertex(v) << " ";

        queue<int> queue;
        queue.push(v);          // 시작 vertex enqueue

        while(!queue.empty()){
            v = queue.front();
            queue.pop();        // v = queue.dequeue()

            // 인접 정점 탐색
            for(Node *n=adjList[v]; n!=NULL; n=n->getLink()){
                int nId = n->getId();       // 인접 노드의 정점 id
                if(!visited[nId]){
                    cout << getVertex(nId) << " ";
                    visited[nId] = true;  // 방문 처리
                    queue.push(nId);
                }
            }
        }
    }
};

int main(){
    SearchAdjListGraph graph;

    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3
    graph.insertVertex('E');    // 4
    graph.insertVertex('F');    // 5
    graph.insertVertex('G');    // 6
    graph.insertVertex('H');    // 7
    // 간선 삽입
    graph.insertEdge(0, 1);     // A->B
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(1, 3);     // B->D
    graph.insertEdge(2, 3);     // C->D
    graph.insertEdge(2, 4);     // C->E
    graph.insertEdge(3, 5);     // D->F
    graph.insertEdge(4, 6);     // E->G
    graph.insertEdge(4, 7);     // E->H
    graph.insertEdge(6, 7);     // G->H

    cout << "== Display Graph == " << endl;
    graph.display();

    cout << "-BFS => ";
    graph.bfs(0);

    return 0;
}

 

※ 시간복잡도

정점의 수가 n이고 간선의 수가 e인 그래프를 인접 행렬로 구현하는 경우 BFS 시간복잡도O(n²) 이며, 인접 리스트로 구현하는 경우 BFS 시간복잡도O(n+e) 이다.

 

2.3 Connected Component

Connected Component(연결 성분)이란 여러 개의 노드의 집합에서 간선으로 연결된 각각의 그래프를 의미한다.

 

연결 성분을 찾는 방법

  1. 그래프 상의 임의의 노드를 선택해 DFS 또는 BFS 탐색을 수행한다.
    (방문한 노드는 같은 성분으로 labeling 한다.)
  2. 남은 노드 중 방문하지 않은 노드를 선택하여 다시 1번을 수행한다.
  3. 그래프의 모든 노드가 방문처리될 때까지 1, 2번을 반복한다.

 

C++ 구현

/*
 * 9.9
 * 인접 행렬(Adjacency Matrix)과 DFS를 이용한 Connected Component (연결 성분) 탐색 프로그램 구현해보기
 */
#include <iostream>
using namespace std;

#define MAX_VTXS 256    // 최대 정점 개수

class AdjMatGraph{
protected:
    int size;                           // 정점의 개수
    char vertices[MAX_VTXS];            // 정점의 이름
    int adjMat[MAX_VTXS][MAX_VTXS];     // 인접 행렬

public:
    AdjMatGraph(){
        reset();
    }
    ~AdjMatGraph(){}

    char getVertex(int i){
        return vertices[i];
    }
    int getEdge(int i, int j){
        return adjMat[i][j];
    }
    void setEdge(int i, int j, int val){
        adjMat[i][j] = val;
    }

    // 그래프 초기화
    void reset(){
        for(int i=0; i<MAX_VTXS; i++){
            for(int j=0; j<MAX_VTXS; j++){
                setEdge(i, j, 0);
            }
        }
        size = 0;
    }

    // 정점 삽입
    void insertVertex(char name){
        if(isFull()){
            cout << "Graph vertex full error" << endl;
            return;
        }

        vertices[size++] = name;
    }

    // 간선 삽입 (무방향 그래프)
    void insertEdge(int u, int v){
        setEdge(u, v, 1);       // 가중치 그래프에서는 1이 아닌 가중치 삽입
        setEdge(v, u, 1);       // 방향 그래프에서는 삭제 (<u,v>만 존재)
    }

    // 그래프 정보 출력
    void display(){
        cout << "vertex size : " << size << endl;
        cout << "    ";
        for(int i=0; i<size; i++){
            cout << getVertex(i) << " ";
        }
        cout << endl;

        for(int i=0; i<size; i++){
            cout << getVertex(i) << " : ";
            for(int j=0; j<size; j++){
                cout << getEdge(i, j) << " ";
            }
            cout << endl;
        }
    }

    // 두개의 정점이 연결되어 있는지 검사
    bool isLinked(int u, int v){
        return getEdge(u, v) != 0;
    }

    bool isEmpty(){
        return size == 0;
    }
    bool isFull(){
        return size >= MAX_VTXS;
    }
};

// DFS를 이용하여 그래프의 연결성분 탐색
class ConnectedComponentGraph : public AdjMatGraph{
private:
    int labelValue = 0;
    int label[MAX_VTXS];     // component label;

public:
    // 모든 node의 label을 0으로 초기화
    void resetVisited(){
        for(int i=0; i<size; i++){
            label[i] = 0;
        }
    }

    // 깊이우선탐색 (순환)
    void labelDfsRecur(int v){
        label[v] = labelValue;

        for(int n=0; n<size; n++){
            if(isLinked(v, n) && label[n]==0){
                labelDfsRecur(n);
            }
        }
    }

    void findConnectedComponent(){
        cout << "== Connected Component ==" << endl;
        int cnt = 0;        // component 개수
        labelValue = 0;     // labelValue 초기화
        resetVisited();     // label array 초기화

        for(int i=0; i<size; i++){
            if(label[i]==0){        // 방문하지 않은 node의 경우
                labelValue++;       // label 값 증가
                labelDfsRecur(i);
                cnt++;
            }
        }

        cout << "- component 개수 : " << cnt << endl;
        for(int i=1; i<=labelValue; i++){
            cout << "  " << i << " : ";
            for(int j=0; j<size; j++){
                if(label[j]==i){
                    cout << "[" << getVertex(j) << "]";
                }
            }
            cout << endl;
        }

    }
};

int main(){
    // 그래프 모양
    cout << "== Graph Shape ==" << endl;
    cout << "A - C   E - G" << endl;
    cout << "          \\ " << endl;
    cout << "B - D - F   H" << endl;
    cout << endl;

    // 인접 행렬 그래프
    ConnectedComponentGraph graph;

    cout << "== Adjacency Matrix Graph == " << endl;
    // 정점 삽입 (A, B, C, D)
    graph.insertVertex('A');    // 0
    graph.insertVertex('B');    // 1
    graph.insertVertex('C');    // 2
    graph.insertVertex('D');    // 3
    graph.insertVertex('E');    // 4
    graph.insertVertex('F');    // 5
    graph.insertVertex('G');    // 6
    graph.insertVertex('H');    // 7
    // 간선 삽입
    graph.insertEdge(0, 2);     // A->C
    graph.insertEdge(1, 3);     // B->D
    graph.insertEdge(3, 5);     // D->F
    graph.insertEdge(4, 6);     // E->G
    graph.insertEdge(4, 7);     // E->H

    graph.display();

    graph.findConnectedComponent();

    return 0;
}

 

2.4 신장 트리 (Spanning Tree)

신장 트리(spanning tree)란 그래프 내의 모든 정점이 연결되어 있으면서 사이클이 없는 트리를 의미한다.

신장 트리는 그래프의 n개의 정점을 정확하게 n-1개의 간선으로 연결하게 된다.

 

신장 트리를 구현하기 위해서는 DFS 또는 BFS 도중에 사용된 간선들만 남겨두면 된다.

즉, 이를 그림으로 표현하면 아래와 같다.

 

 

 

728x90
반응형