Computer >> 컴퓨터 >  >> 프로그래밍 >> 프로그래밍

오일러 경로와 오일러 회로의 개념 및 판별 알고리즘

오일러 경로(Euler Path)는 그래프의 모든 간선을 정확히 한 번씩만 지나가면서 탐색할 수 있는 경로를 의미합니다. 이때 정점(vertex)은 여러 번 방문해도 무방합니다. 오일러 회로(Euler Circuit)는 오일러 경로의 특수한 형태로, 경로의 시작 정점과 끝 정점이 동일한 경우를 말합니다. 즉, 출발점으로 다시 돌아오는 닫힌 경로가 오일러 회로입니다.

오일러 경로와 오일러 회로의 개념 및 판별 알고리즘

오일러 경로와 회로의 판별 조건

그래프에 오일러 경로나 오일러 회로가 존재하는지 확인하려면 다음 조건들을 만족해야 합니다.

  • 연결 그래프여야 합니다. 그래프가 두 개 이상의 연결 요소로 나뉘어 있으면 오일러 경로나 회로는 존재할 수 없습니다.
  • 차수(degree)가 홀수인 정점이 정확히 2개라면, 해당 그래프에는 오일러 경로가 존재합니다. 이때 홀수 차수를 가진 두 정점이 각각 경로의 시작점과 끝점이 됩니다.
  • 홀수 차수를 가진 정점이 하나도 없다면, 해당 그래프에는 오일러 회로가 존재합니다.

입력과 출력

입력:
그래프의 인접 행렬
0 1 1 1 0
1 0 1 0 0
1 1 0 0 0
1 0 0 0 1
0 0 0 1 0

출력:
이 그래프에는 오일러 경로가 존재합니다.

알고리즘

오일러 경로/회로 판별은 크게 세 단계로 진행됩니다. 먼저 깊이 우선 탐색(DFS)으로 그래프의 연결성을 확인하고, 이후 각 정점의 차수를 계산하여 홀수 차수 정점의 개수를 세는 방식입니다.

1. traverse(u, visited) — 그래프 순회

입력: 시작 노드 u와 방문 여부를 표시하는 visited 배열

출력: 시작 노드에서 도달 가능한 모든 정점을 순회

Begin
    mark u as visited
    for all vertex v, if it is adjacent with u, do
        if v is not visited, then
            traverse(v, visited)
    done
End

2. isConnected(graph) — 연결성 검사

입력: 그래프

출력: 그래프가 연결되어 있으면 true, 아니면 false

Begin
    define visited array
    for all vertices u in the graph, do
        make all nodes unvisited
        traverse(u, visited)
        if any unvisited node is still remaining, then
            return false
    done
    return true
End

3. isEulerian(Graph) — 오일러성 판별

입력: 주어진 그래프

출력: 오일러 그래프가 아니면 0, 오일러 경로만 존재하면 1, 오일러 회로가 존재하면 2

Begin
    if isConnected() is false, then
        return false
    define list of degree for each node
    oddDegree := 0

    for all vertex i in the graph, do
        for all vertex j which are connected with i, do
            increase degree
        done
        if degree of vertex i is odd, then
            increase oddDegree
    done

    if oddDegree > 2, then
        return 0
    if oddDegree = 0, then
        return 2
    else
        return 1
End

C++ 구현 예제

#include<iostream>
#include<vector>
#define NODE 5
using namespace std;

int graph[NODE][NODE] = {
    {0, 1, 1, 1, 0},
    {1, 0, 1, 0, 0},
    {1, 1, 0, 0, 0},
    {1, 0, 0, 0, 1},
    {0, 0, 0, 1, 0}
};

/* int graph[NODE][NODE] = {
    {0, 1, 1, 1, 1},
    {1, 0, 1, 0, 0},
    {1, 1, 0, 0, 0},
    {1, 0, 0, 0, 1},
    {1, 0, 0, 1, 0}
};
*/      //주석 해제 시 오일러 회로 확인 가능

/* int graph[NODE][NODE] = {
    {0, 1, 1, 1, 0},
    {1, 0, 1, 1, 0},
    {1, 1, 0, 0, 0},
    {1, 1, 0, 0, 1},
    {0, 0, 0, 1, 0}
};
*/      //주석 해제 시 비오일러 그래프 확인 가능

void traverse(int u, bool visited[]) {
    visited[u] = true;     //현재 정점을 방문 처리

    for(int v = 0; v<NODE; v++) {
        if(graph[u][v]) {
            if(!visited[v])
                traverse(v, visited);
        }
    }
}

bool isConnected() {
    bool *vis = new bool[NODE];
    //모든 정점을 시작점으로 삼아 전체 노드 방문 여부 확인
    for(int u = 0; u < NODE; u++) {
        for(int i = 0; i<NODE; i++)
            vis[i] = false;    //방문 배열 초기화

        traverse(u, vis);

        for(int i = 0; i<NODE; i++) {
            if(!vis[i])    //순회 후에도 방문되지 않은 노드가 있다면 연결 그래프가 아님
                return false;
        }
    }
    return true;
}

int isEulerian() {
    if(isConnected() == false)     //그래프가 연결되어 있지 않은 경우
        return 0;
    vector<int> degree(NODE, 0);
    int oddDegree = 0;

    for(int i = 0; i<NODE; i++) {
        for(int j = 0; j<NODE; j++) {
            if(graph[i][j])
                degree[i]++;    //간선이 발견되면 차수 증가
        }

        if(degree[i] % 2 != 0)    //정점의 차수가 홀수인 경우
            oddDegree++;          //홀수 차수 정점 개수 카운트
    }

    if(oddDegree > 2)    //홀수 차수 정점이 2개보다 많으면 오일러 그래프가 아님
        return 0;

    return (oddDegree)?1:2;    //홀수 차수가 0개면 오일러 회로, 2개면 오일러 경로
}

int main() {
    int check;
    check = isEulerian();

    switch(check) {
        case 0: cout << "이 그래프는 오일러 그래프가 아닙니다.";
            break;
        case 1: cout << "이 그래프에는 오일러 경로가 존재합니다.";
            break;
        case 2: cout << "이 그래프에는 오일러 회로가 존재합니다.";
            break;
    }
}

실행 결과

이 그래프에는 오일러 경로가 존재합니다.

정리

오일러 경로와 회로 문제는 1736년 레온하르트 오일러가 쾨니히스베르크의 다리 문제를 해결하면서 처음 등장한 그래프 이론의 고전적인 주제입니다. 판별 알고리즘의 시간 복잡도는 인접 행렬 기준으로 O(V²)이며, 연결성 검사와 차수 계산 두 단계로 구성됩니다. 핵심은 홀수 차수 정점의 개수만 확인하면 되므로 매우 효율적이라는 점입니다.