그래프 색칠 문제
그래프 색칠 문제는 그래프의 각 정점에 색을 하나씩 배정하되, 간선으로 이어진 두 정점은 서로 다른 색을 갖게 하는 문제입니다. 가장 적은 색으로 칠하는 것이 목표이고, 필요한 최소 색의 수를 색칠 수(chromatic number)라고 부릅니다. 지도에서 이웃한 나라를 다른 색으로 칠하는 놀이가 이 문제의 출발점입니다.
"서로 충돌하는 것들을 겹치지 않게 나누기"가 필요한 곳이면 어디서나 이 문제로 바꿔 풀 수 있습니다. 같은 학생이 듣는 과목을 서로 다른 시험 시간대에 배정하거나, 동시에 살아 있는 변수를 서로 다른 레지스터에 넣을 때가 그렇습니다. 문제는 최소 색을 구하는 일반적인 빠른 방법이 알려져 있지 않다는 점이라서, 실제로는 그리디 같은 휴리스틱과 백트래킹을 씁니다. 이 토픽에서는 같은 그래프를 세 가지 방식으로 칠해 봅니다.
핵심 개념
정점 색칠 조건
간선으로 이어진 두 정점은 같은 색을 가질 수 없습니다. 이어지지 않은 정점끼리는 같은 색을 써도 됩니다.
핵심 개념
색칠 수 χ(G)
조건을 지키며 칠하는 데 필요한 최소 색의 개수입니다. 서로 모두 이웃한 정점 k개(클리크)가 있으면 색칠 수는 적어도 k입니다.
핵심 개념
그리디 색칠
정해진 순서로 정점을 보며, 이미 칠해진 이웃이 쓰지 않는 가장 작은 번호의 색을 칠합니다. 빠르지만 최소 색을 보장하지는 않습니다.
핵심 개념
정점 순서의 영향
같은 그래프도 칠하는 순서에 따라 쓰는 색의 수가 달라집니다. 이웃이 많은 정점부터 칠하면 대체로 색이 적게 듭니다.
핵심 개념
백트래킹
m가지 색으로 칠할 수 있는지 정점마다 색을 시도하고, 막히면 직전 선택으로 되돌아가 다른 색을 고릅니다. 답이 있으면 반드시 찾지만 최악에는 느립니다.
핵심 개념
최대 차수 + 1
정점의 이웃이 d개이면 이웃이 막는 색은 많아야 d개라 색 d + 1개 중 하나는 남습니다. 그래서 그리디는 어떤 순서로도 최대 차수 + 1개 이하의 색을 씁니다.
그래프 색칠의 배경
그래프 색칠은 지도 색칠에서 시작했습니다. 지도의 어떤 모양이든 이웃한 나라가 다른 색이 되도록 칠하는 데 네 가지 색이면 충분하다는 사색정리는 오랫동안 추측으로 남아 있다가 1976년 아펠(Appel)과 하켄(Haken)이 컴퓨터를 이용해 증명했습니다. 지도는 평면 그래프로 바꿀 수 있으므로 이는 모든 평면 그래프가 4색으로 칠해진다는 뜻입니다.
일반 그래프에서는 사정이 다릅니다. 1972년 카프(Karp)가 NP-완전 문제 21개를 정리한 논문에 색칠 수 문제가 들어 있고, 지금은 색 k개로 칠할 수 있는지를 묻는 결정 문제가 k가 3 이상일 때 NP-완전이라고 알려져 있습니다. 한편 컴퓨터 쪽의 쓰임도 일찍 시작되어, 1967년 웰시(Welsh)와 파월(Powell)은 시간표 문제에 그래프 색칠을 적용하며 이웃이 많은 정점부터 칠하는 순서와 색의 수의 상한을 다룬 논문을 냈습니다. 이웃이 많은 순서로 칠하는 방법을 흔히 웰시-파월 알고리즘이라고 부릅니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
그래프 색칠 문제란?
간선으로 이어진 정점이 서로 다른 색이 되도록 정점에 색을 배정하는 문제입니다. 필요한 최소 색의 수가 색칠 수이고, 시간표 배정이나 레지스터 할당 같은 충돌 회피 문제를 이 형태로 바꿔 풉니다.
면접 질문
그리디 색칠의 한계는?
정점 순서에 따라 색의 수가 달라지고 최소를 보장하지 않습니다. 대신 순서가 주어지면 O(V + E)로 빠르고 최대 차수 + 1개 이하의 색을 씁니다. 이웃이 많은 정점부터 칠하는 휴리스틱이 자주 쓰입니다.
면접 질문
왜 NP-완전인가요?
k색으로 칠할 수 있는지를 묻는 결정 문제는 k가 3 이상이면 NP-완전이고, 색칠 수를 구하는 최적화 문제는 그만큼 어렵습니다. 정답의 검증은 간선을 훑으면 되지만 정답을 찾는 빠른 방법은 알려져 있지 않습니다.
면접 질문
2색 칠하기는 어떻게 판별하나요?
너비 우선 탐색으로 시작 정점에 한 색을 주고 이웃에게 반대 색을 줍니다. 이웃과 같은 색이 나오면 불가능이며, 이는 그래프가 이분 그래프가 아니라는 뜻이라 홀수 사이클이 있다는 말과 같습니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
Python — 그리디 색칠과 정점 순서
graph = {
'A': ['B', 'C'],
'B': ['A', 'C', 'D', 'F'],
'C': ['A', 'B', 'E', 'F'],
'D': ['B'],
'E': ['C', 'F'],
'F': ['B', 'C', 'E'],
}
def greedy(graph, order):
color = {}
for v in order:
used = {color[u] for u in graph[v] if u in color} # 이웃이 쓰는 색
c = 1
while c in used:
c += 1
color[v] = c # 남은 가장 작은 번호
return color
c1 = greedy(graph, 'ABCDEF')
print(c1, max(c1.values()))
# {'A': 1, 'B': 2, 'C': 3, 'D': 1, 'E': 1, 'F': 4} 4
order = sorted(graph, key=lambda v: -len(graph[v])) # 이웃이 많은 정점부터
c2 = greedy(graph, order)
print(order, c2, max(c2.values()))
# ['B', 'C', 'F', 'A', 'E', 'D'] {'B': 1, 'C': 2, 'F': 3, 'A': 3, 'E': 1, 'D': 2} 3
그리디 색칠은 정점을 정해진 순서로 보면서, 이미 칠해진 이웃이 쓰는 색을 모은 뒤 그 집합에 없는 가장 작은 번호의 색을 칠합니다. 이웃 하나가 막는 색은 하나뿐이므로 정점의 이웃이 d개면 색 d + 1개 안에서 항상 칠할 수 있고, 그래서 그리디는 최대 차수 + 1개 이하의 색을 씁니다. 이 그래프의 최대 차수는 4(B와 C)라서 어떤 순서로도 색 5개를 넘지 않습니다. 같은 그래프라도 순서에 따라 결과가 달라집니다. 알파벳 순서 A, B, C, D, E, F로 칠하면 F의 이웃 B, C, E가 색 2, 3, 1을 쓰고 있어 색 4가 필요해 색 4개가 됩니다. 이웃이 많은 정점부터 B, C, F, A, E, D로 칠하면 색 3개로 끝납니다. A, B, C가 서로 이웃이라 어떤 방법으로도 색 3개 미만으로는 칠할 수 없으므로 이 결과가 최소입니다. 시각화의 그리디 색칠 탭과 정점 순서의 영향 탭이 보여 주는 결과와 같습니다.
Python — 백트래킹으로 m가지 색으로 칠할 수 있는지 판단하기
def can_color(graph, m, order): color = {} tries = 0 def solve(i): nonlocal tries if i == len(order): return True v = order[i] for c in range(1, m + 1): tries += 1 if all(color.get(u) != c for u in graph[v]): # 이웃과 충돌하지 않으면 color[v] = c if solve(i + 1): return True del color[v] # 막히면 되돌아간다 return False ok = solve(0) return ok, color, tries print(can_color(graph, 2, 'ABCDEF')) # (False, {}, 10) print(can_color(graph, 3, 'ABCDEF')) # (True, {'A': 1, 'B': 2, 'C': 3, 'D': 1, 'E': 2, 'F': 1}, 13)
백트래킹은 정점을 순서대로 보며 색 1부터 m까지 차례로 시도합니다. 칠해진 이웃과 충돌하지 않으면 그 색을 배정하고 다음 정점으로 넘어가고, 다음 정점에서 어떤 색도 쓸 수 없으면 현재 정점의 색을 지우고(del) 다음 색을 시도합니다. 이것이 되돌아가기입니다. 색 3개로는 A부터 F까지 13번 시도해서 칠할 수 있습니다. 중간에 E에 색 1을 칠하면 F가 쓸 색이 없어 E로 되돌아가 색 2로 바꾸는 한 번의 되돌아가기가 일어납니다. 시각화의 백트래킹 탭이 보여 주는 시도 횟수 13번, 되돌아간 횟수 1번과 같습니다. 색 2개로는 A, B, C가 서로 이웃인 삼각형이 있어 칠할 수 없고, 모든 경우를 따져 본 뒤 10번의 시도 끝에 False를 돌려줍니다. 최악에는 정점 수 V에 대해 m^V가지 배정을 모두 살펴봐야 하므로 정점이 많아지면 느려집니다.
Python — 시험 시간표 배정과 2색 판별
conflicts = { # 같은 학생이 함께 듣는 과목
'수학': ['물리', '화학'],
'물리': ['수학', '화학', '영어'],
'화학': ['수학', '물리'],
'영어': ['물리', '국어'],
'국어': ['영어'],
}
slots = greedy(conflicts, sorted(conflicts, key=lambda v: -len(conflicts[v])))
by = {}
for k, s in slots.items():
by.setdefault(s, []).append(k)
print(by) # {1: ['물리', '국어'], 2: ['수학', '영어'], 3: ['화학']}
from collections import deque
def two_colorable(graph):
color = {}
for s in graph:
if s in color:
continue
color[s] = 0
q = deque([s])
while q:
v = q.popleft()
for u in graph[v]:
if u not in color:
color[u] = 1 - color[v] # 이웃은 반대 색
q.append(u)
elif color[u] == color[v]:
return False
return True
square = {'a': ['b', 'd'], 'b': ['a', 'c'], 'c': ['b', 'd'], 'd': ['c', 'a']}
tri = {'a': ['b', 'c'], 'b': ['a', 'c'], 'c': ['a', 'b']}
print(two_colorable(square), two_colorable(tri), two_colorable(graph)) # True False False
시험 시간표는 그래프 색칠의 대표적인 응용입니다. 같은 학생이 함께 듣는 과목을 간선으로 이으면, 색은 시험 시간대가 되고 같은 색의 과목은 겹치는 학생이 없어 같은 시간대에 칠 수 있습니다. 다섯 과목은 그리디로 시간대 3개에 배정됩니다. 수학, 물리, 화학이 서로 겹쳐 시간대가 3개 미만일 수는 없으므로 3개가 최소입니다. 색을 두 개만 쓸 수 있는지 판별하는 문제는 너비 우선 탐색으로 선형 시간에 풀립니다. 시작 정점에 색 0을 주고 이웃에게 반대 색을 주면서 퍼져 나가다 이웃과 같은 색이 나오면 불가능입니다. 네 정점이 사각형으로 이어진 그래프는 True, 삼각형은 False이고, 위의 그래프도 삼각형을 포함해 False입니다. 색 3개 이상으로 칠할 수 있는지는 일반 그래프에서 NP-완전 문제라서 이런 빠른 방법이 알려져 있지 않습니다. 그래서 위의 그리디나 백트래킹처럼 실용적인 방법을 씁니다.
Q1
그래프 색칠 문제의 기본 조건은?
Q2
그래프의 색칠 수(chromatic number)는?
Q3
그래프 색칠 문제를 응용할 수 있는 상황은?