Eulerian cirtuit(오일러 서킷)
Eulerian trail(오일러 트레일)
Eulerian Circuit(오일러 서킷)
오일러 서킷이라는 것은 우리들에게 한붓그리기 문제로 더 알려져 있습니다.
오일러 서킷
그래프가 주어졌을 때 그래프의 한 시작점으로부터 모든 간선을 한번씩만 지나 다시 시작점으로 돌아오는 경로를 말합니다.
이러한 경로가 있으려면 어떠한 조건을 만족해야 할까요?
가장 많이 사용되고 있는 방법으로는 단순하게 정점의 degree를 사용하는 것입니다.
degree라는 것은 위상정렬 할 때 잠시 살펴보았죠?
그 때는 indegree를 이용하였고, 여기서 degree라는 것은 정점에 연결되어 있는 간선의 수를 말합니다.
그런데 도대체 degree가 갑자기 왜나올까요?
1) 무향그래프인 경우
한붓으로 모든 간선을 그리고 돌아오는 경로를 한번 생각해 봅시다.
(여기서 모든 간선은 단 하나의 그래프로 모두 연결되어 있다고 합시다.)
붓은 시작점에서 부터 나가서, 모든 간선을 그리고 다시 시작점으로 돌아오겠죠.
그렇다면 여기서 시작점의 degree는 짝수일까요 홀수일까요?
degree는 짝수여야 합니다! 왜냐하면 degree가 홀수이면 나갔다가 마지막에 돌아올 수 없기 때문이죠.
자 이제 시작점이 아닌 다른점을 생각해 봅시다.
다른점의 degree는 짝수일까요 홀수일까요?
당연히 다른점의 degree또한 짝수여야 합니다.
왜 그런 것인지는 시작점이 짝수일 수 밖에 없었던 이유를 생각해 보시길 바랍니다!
그러면 지금까지 보아온 것들로 결론을 낼 수 있습니다.
우선 degree가 홀수 인 점이 있다면, 오일러 서킷은 존재하지 않습니다.
따라서 모든 정점의 degree는 짝수여야 합니다.
그리고 오일러 서킷의 존재성 보장을 위해 모든 간선이 한 그래프로 연결이 되어있어야 합니다.
무향 그래프에서 오일러 서킷이 존재하기 위한 조건
1) 모든 간선이 단 하나의 그래프에 연결되어 있을 때 (단일 컴포넌트)
2) 모든 정점의 degree가 짝수일 때
이제 무향그래프에서 이런 오일러 서킷을 찾는 방법에 대해서 생각해 봅시다.
우선 오일러 서킷이 존재하는 그래프가 있다고 가정합시다.
그렇다면 다음과 같은 순서로 오일러 서킷을 찾을 수 있습니다.
오일러 서킷을 찾는 방법
1. 임의의 그래프 내의 시작점 u를 정합니다.
2. 시작점 u에 대해 방문하지 않은 간선(u, v)이 있다면 그 간선을 따라 이동합니다.
3. 이동하여 도착한 정점 v에 대해서 또 다시 2번과 같이 방문하지 않은 간선이 있다면 따라서 이동합니다.
4. 3번을 반복하다가 더 이상 이동 할 간선이 없다면 이때까지 이동하였던 경로를 반환합니다.
5. 아직 방문하지 않은 간선에 대해 3번을 반복 수행하고 얻어지는 경로를 모두 합칩니다.
그런데 위와 같은 방식은 5번과 같이 여러개의 경로를 어떻게 합쳐야 하는가에 대한 이슈가 남게 됩니다.
나머지 번호는 dfs와 같이 재귀적으로 호출함으로서 해결이 가능합니다.
5번의 문제를 어떻게 해결 할 수 있을까요?
실제로 우리가 어떻게 경로를 찾아내는지 한번 붓을 들고 그려봅시다.
1번 노드를 시작점으로 하고, 연결된 간선 중 정점의 번호가 작은 것 부터 선택한다고 생각해 봅시다.
위에서 제시한 방법대로라면 아래와 같이 첫번 째 경로가 찾아집니다.
이제 정점 5번과 연결된 방문하지 않은 간선을 보면 다음과 같이 되겠죠.
우리가 사실 원하는 경로는 1-2-5-6-7-5-4-3-1 입니다.
여기서 DFS를 재귀적으로 함수를 구현했을 때 DFS함수의 호출이 종료되는 순서대로 정점을 한번 살펴봅시다.
정점1
정점3
정점4
정점5
정점7
정점6
정점5
정점2
정점1
순으로 함수가 종료가 될 것입니다.
지금 이 과정을 눈으로 잘 따라오셨다면 느낌이 살짝 오게 됩니다.
종료하는 시점에서 경로를 만들어 가면 자동적으로 모든 과정이 해결이 됩니다!
바로 DFS한번에 말이죠!
자 그럼 구체적으로 방법을 들여다 봅시다.
위의 그래프를 가지고 코드를 작성해 보았습니다.
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector< vector<int> > adj;
vector<int> euler;
/* DFS와 같은 방식으로 경로를 찾아갑니다. */
void findEulerianCircuit(int here) {
for (int there = 1; there < adj.size(); there++) {
while (adj[here][there] > 0) {
adj[here][there]--;
adj[there][here]--;
findEulerianCircuit(there);
}
}
euler.push_back(here);
}
int main() {
adj = vector< vector<int> >(8, vector<int>(8, 0));
euler.clear();
adj[1][2] = adj[2][1] = 1;
adj[2][5] = adj[5][2] = 1;
adj[4][5] = adj[5][4] = 1;
adj[3][4] = adj[4][3] = 1;
adj[3][1] = adj[1][3] = 1;
adj[5][7] = adj[7][5] = 1;
adj[6][7] = adj[7][6] = 1;
adj[5][6] = adj[6][5] = 1;
findEulerianCircuit(1);
reverse(euler.begin(), euler.end());
for (int v : euler) {
cout << v << " ";
}
cout << '\n';
}
13번째 line이 while인 경우는 두 정점간에 연결된 간선이 여러개 일수도 있기 때문입니다.
14-15 line에서는 연결된 간선이 방향이 없으므로 두쪽 모두 지워주어야 합니다.
문제 상황에서 두 정점간의 간선이 무조건 하나라면 while문이 없어도 됩니다!
findEulerianCircuit() 함수의 시간복잡도를 살펴봅시다.
간선마다 함수가 실행이 되고, 정점으로부터 간선을 찾는데 |V|만큼의 시간이 걸리므로
가 됩니다.
2) 유향그래프인 경우
이제 간선에 방향성분이 있는 경우를 생각해 봅시다.
아까 무향그래프에서 각 정점들의 degree가 짝수여야 하는 이유에 대해서 생각해 보면,
유향 그래프에서 오일러 서킷이 존재 할 조건 또한 쉽게 나오게 됩니다.
유향 그래프에서 오일러 서킷이 존재하기 위한 조건
1) 모든 간선이 단 하나의 그래프에 연결되어 있을 때 (단일 컴포넌트)
2) 모든 정점의 in-degree와 out-degree가 같을 때
유향 그래프에서 오일러 서킷을 찾는 방법은 무향그래프와 동일합니다!
간선을 지울때 양방향이 아니라 단방향을 지우는 것만 차이가 납니다.
간단한 문제 하나 풀고 다음 내용도 살펴봅시다
Eulerian trail(오일러 트레일)
오일러 트레일은 오일러 서킷과 달리 시작점과 끝점이 다른 경우를 말합니다.
오일러 트레일
모든 간선을 단 한번씩 지나지만 시작점과 끝점이 다른 경로를 말합니다.
그렇다면 오일러 트레일이 존재할 조건을 한번 생각해 봅시다.
오일러 서킷 달리 시작점과 끝점이 다른데, 모든 간선은 한번씩 모두 지나야 합니다.
그렇게 되기 위해서는 시작점과 끝점의 degree는 홀수여야 겠고, 다른 점의 degree는 모두 짝수여야 하겠죠!
방향 그래프의 경우 시작점의 in-degree + 1 = out-degree가 되고, 끝점의 in-degree = out-degree + 1가 되야겠죠!
시작점에서는 최종적으로 나가야하고, 끝점에서는 최종적으로 들어와야 하기 때문이죠.
오일러 트레일이 존재 할 조건
1) 유향그래프
시작점의 indegree + 1= outdegree
끝점의 indegree = outdegree + 1
나머지 점의 indegree = outdegree
2) 무향그래프
시작점과 끝점의 degree가 홀수
나머지 점의 degree는 짝수
자 이제 위의 조건이 만족되어서 오일러 트레일이 존재하는 그래프가 있다고 합시다.
그렇다면 오일러 트레일은 어떻게 찾을 수 있을까요?
방법은 생각보다 간단합니다.
시작점과 끝점을 이어버리면 해당 그래프는 오일러 서킷이 존재하게 됩니다.
(모든 degree가 짝수가 되거나, 방향 그래프의 경우 모든 점에 대해 indegree=outdegree를 만족하도록 간선을 만듭니다.)
그럼 만들어진 그래프는 오일러 서킷 가지고 있습니다.
따라서 이 오일러 서킷 찾은 다음에 처음에 추가한 간선을 지우면 오일러 트레일이 만들어 지게 되는 것이죠.
어렵지 않죠?
오일러 트레일을 찾는 방법
1. 시작점과 끝점을 오일러 서킷이 존재하게끔 간선으로 이어준다.
2. 오일러 서킷을 찾는다
3. 찾은 경로에서 1에서 추가한 간선을 삭제한다.
그럼 위의 경우는 어떻게 코드로 표현할 수 있을까요?
오일러 서킷을 찾는 매커니즘을 잘 생각해 봅시다.
1) 우선 시작점과 끝점은 우리가 indegree, outdegree를 보고나면 알아낼 수 있습니다.
나머지는 경로를 찾는 일인데, 코드는 완전히 오일러 서킷을 찾는 것과 동일합니다.
2) 시작점과 끝점을 이어서 오일러 서킷을 찾는다면 결국 서킷을 처음으로 기록하게 되는 시점은
끝점에서 시작점으로 함수를 호출 후 시작점에서 더이상 방문 할 간선이 없어서 재귀 호출이 종료되는 부분입니다.
그런데 우리가 애초에 그 과정에 해당 간선을 추가하지 않는다면 그 간선이 자동으로 제거되어 나오기 때문에
간선을 추가할 필요도 없이 우리는 시작점만 찾아서 앞에서 구현한 findEulerianCircuit() 함수에 넣으면 됩니다.
'Algorithm > Algorithm' 카테고리의 다른 글
[자료구조/알고리즘] 비트연산을 통한 순열 (0) | 2021.01.11 |
---|---|
[자료구조/알고리즘] 재귀 함수를 이용한 부분 집합 생성 알고리즘 (0) | 2021.01.06 |
[자료구조/알고리즘] 해시(Hash) 란? (0) | 2021.01.06 |