컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
ds · 20

그래프 색칠 문제

백트래킹색칠그래프

그래프 색칠 문제는 그래프의 각 정점에 색을 하나씩 배정하되, 간선으로 이어진 두 정점은 서로 다른 색을 갖게 하는 문제입니다. 가장 적은 색으로 칠하는 것이 목표이고, 필요한 최소 색의 수를 색칠 수(chromatic number)라고 부릅니다. 지도에서 이웃한 나라를 다른 색으로 칠하는 놀이가 이 문제의 출발점입니다.

"서로 충돌하는 것들을 겹치지 않게 나누기"가 필요한 곳이면 어디서나 이 문제로 바꿔 풀 수 있습니다. 같은 학생이 듣는 과목을 서로 다른 시험 시간대에 배정하거나, 동시에 살아 있는 변수를 서로 다른 레지스터에 넣을 때가 그렇습니다. 문제는 최소 색을 구하는 일반적인 빠른 방법이 알려져 있지 않다는 점이라서, 실제로는 그리디 같은 휴리스틱과 백트래킹을 씁니다. 이 토픽에서는 같은 그래프를 세 가지 방식으로 칠해 봅니다.

핵심 개념

정점 색칠 조건

간선으로 이어진 두 정점은 같은 색을 가질 수 없습니다. 이어지지 않은 정점끼리는 같은 색을 써도 됩니다.

핵심 개념

색칠 수 χ(G)

조건을 지키며 칠하는 데 필요한 최소 색의 개수입니다. 서로 모두 이웃한 정점 k개(클리크)가 있으면 색칠 수는 적어도 k입니다.

핵심 개념

그리디 색칠

정해진 순서로 정점을 보며, 이미 칠해진 이웃이 쓰지 않는 가장 작은 번호의 색을 칠합니다. 빠르지만 최소 색을 보장하지는 않습니다.

핵심 개념

정점 순서의 영향

같은 그래프도 칠하는 순서에 따라 쓰는 색의 수가 달라집니다. 이웃이 많은 정점부터 칠하면 대체로 색이 적게 듭니다.

핵심 개념

백트래킹

m가지 색으로 칠할 수 있는지 정점마다 색을 시도하고, 막히면 직전 선택으로 되돌아가 다른 색을 고릅니다. 답이 있으면 반드시 찾지만 최악에는 느립니다.

핵심 개념

최대 차수 + 1

정점의 이웃이 d개이면 이웃이 막는 색은 많아야 d개라 색 d + 1개 중 하나는 남습니다. 그래서 그리디는 어떤 순서로도 최대 차수 + 1개 이하의 색을 씁니다.

그리디는 순서가 주어지면 O(V + E)입니다 — 정점마다 이웃의 색을 한 번씩 살펴보므로 정점과 간선을 한 번씩 훑는 비용입니다. 대신 정점 순서에 따라 결과가 달라집니다.
최소 색 보장은 순서에 달려 있습니다 — 색칠 수를 만드는 어떤 최적 칠하기가 있다면, 색 1인 정점, 색 2인 정점, … 순으로 늘어놓았을 때 그리디도 색칠 수와 같은 개수의 색으로 칠합니다. 하지만 그 순서를 미리 아는 것이 곧 문제를 푸는 일입니다.
백트래킹은 최악에 지수 시간입니다 — 정점이 V개, 색이 m개이면 살펴볼 배정이 최대 mV가지입니다. 이웃과 충돌하는 가지를 일찍 잘라 내서 실제로는 훨씬 적게 시도하지만 최악의 경우는 그대로입니다.
2색이면 쉽습니다 — 2색으로 칠할 수 있는지는 너비 우선 탐색으로 선형 시간에 판별합니다. 이분 그래프라는 것, 곧 홀수 길이의 사이클이 없다는 것과 같은 뜻입니다.
이 시각화에서 확인할 것 — "그리디 색칠" 탭에서 이웃의 색을 피해 가장 작은 색을 고르는 과정을, "정점 순서의 영향" 탭에서 같은 그래프가 4색과 3색으로 갈리는 모습을, "백트래킹" 탭에서 막힌 뒤 되돌아가 다른 색을 고르는 과정을 STEP으로 확인하세요.

그래프 색칠의 배경

그래프 색칠은 지도 색칠에서 시작했습니다. 지도의 어떤 모양이든 이웃한 나라가 다른 색이 되도록 칠하는 데 네 가지 색이면 충분하다는 사색정리는 오랫동안 추측으로 남아 있다가 1976년 아펠(Appel)과 하켄(Haken)이 컴퓨터를 이용해 증명했습니다. 지도는 평면 그래프로 바꿀 수 있으므로 이는 모든 평면 그래프가 4색으로 칠해진다는 뜻입니다.

일반 그래프에서는 사정이 다릅니다. 1972년 카프(Karp)가 NP-완전 문제 21개를 정리한 논문에 색칠 수 문제가 들어 있고, 지금은 색 k개로 칠할 수 있는지를 묻는 결정 문제가 k가 3 이상일 때 NP-완전이라고 알려져 있습니다. 한편 컴퓨터 쪽의 쓰임도 일찍 시작되어, 1967년 웰시(Welsh)와 파월(Powell)은 시간표 문제에 그래프 색칠을 적용하며 이웃이 많은 정점부터 칠하는 순서와 색의 수의 상한을 다룬 논문을 냈습니다. 이웃이 많은 순서로 칠하는 방법을 흔히 웰시-파월 알고리즘이라고 부릅니다.

숫자로 확인하기

예시 그래프 — 정점 A부터 F까지 6개와 간선 8개(A-B, A-C, B-C, B-D, B-F, C-E, C-F, E-F)입니다. 차수는 A 2, B 4, C 4, D 1, E 2, F 3이고 최대 차수는 4이므로 그리디는 5색 이하를 씁니다.
알파벳 순서 — A, B, C, D, E, F 순서로 칠하면 A는 1, B는 2, C는 3, D는 1, E는 1이고, F의 이웃 B, C, E가 색 2, 3, 1을 쓰고 있어 색 4가 필요합니다. 모두 4색입니다.
차수 순서 — 이웃이 많은 B, C, F, A, E, D 순서로 칠하면 B는 1, C는 2, F는 3, A는 3, E는 1, D는 2로 3색에 끝납니다.
색칠 수는 3 — A, B, C가 서로 이웃인 삼각형이라 색이 2개 이하로는 칠할 수 없고, 위의 차수 순서가 3색으로 칠했으므로 이 그래프의 색칠 수는 3입니다.
백트래킹 — 색 3개로 A부터 F까지 칠하면 시도 13번에 되돌아가기 1번(E를 색 1로 칠하면 F가 막혀 E를 색 2로 바꿉니다)으로 성공합니다. 색 2개로는 시도 10번 만에 불가능으로 끝납니다.
시험 시간표 — 수학, 물리, 화학, 영어, 국어 다섯 과목을 겹치는 과목끼리 이으면 3개의 시간대로 배정되고, 물리와 국어, 수학과 영어는 같은 시간대에 칠 수 있습니다.

자주 하는 오해

그리디가 최소 색을 구해 준다 — 그리디는 순서가 나쁘면 필요 이상으로 색을 씁니다. 위의 그래프도 알파벳 순서로는 4색이 필요한데 3색으로 충분합니다. 최대 차수 + 1이라는 상한을 지킬 뿐입니다.
차수가 큰 정점부터 칠하면 항상 최적이다 — 대체로 좋은 휴리스틱이지만 최소 색을 보장하지는 않습니다. 최소 색을 구하는 일반적인 다항 시간 알고리즘은 알려져 있지 않습니다.
색칠 수는 최대 차수와 같다 — 완전 그래프나 홀수 사이클이 아닌 연결 그래프는 색칠 수가 최대 차수 이하라는 브룩스 정리가 있지만, 같다는 뜻은 아닙니다. 위의 그래프는 최대 차수가 4이고 색칠 수가 3입니다.
사색정리가 일반 그래프에도 적용된다 — 4색이면 충분하다는 보장은 평면 그래프에 대한 것입니다. 일반 그래프는 정점 5개가 모두 이어진 완전 그래프처럼 5색이 필요할 수 있습니다.
색의 수가 늘수록 점점 더 어려워진다 — 2색은 선형 시간에 풀리지만 3색부터 NP-완전입니다. 어려움은 색이 2개에서 3개로 늘어나는 순간에 갈리고, 3색 이상에서는 모두 NP-완전으로 같은 급입니다.

면접에서 이렇게 물어요

면접 질문

그래프 색칠 문제란?

간선으로 이어진 정점이 서로 다른 색이 되도록 정점에 색을 배정하는 문제입니다. 필요한 최소 색의 수가 색칠 수이고, 시간표 배정이나 레지스터 할당 같은 충돌 회피 문제를 이 형태로 바꿔 풉니다.

면접 질문

그리디 색칠의 한계는?

정점 순서에 따라 색의 수가 달라지고 최소를 보장하지 않습니다. 대신 순서가 주어지면 O(V + E)로 빠르고 최대 차수 + 1개 이하의 색을 씁니다. 이웃이 많은 정점부터 칠하는 휴리스틱이 자주 쓰입니다.

면접 질문

왜 NP-완전인가요?

k색으로 칠할 수 있는지를 묻는 결정 문제는 k가 3 이상이면 NP-완전이고, 색칠 수를 구하는 최적화 문제는 그만큼 어렵습니다. 정답의 검증은 간선을 훑으면 되지만 정답을 찾는 빠른 방법은 알려져 있지 않습니다.

면접 질문

2색 칠하기는 어떻게 판별하나요?

너비 우선 탐색으로 시작 정점에 한 색을 주고 이웃에게 반대 색을 줍니다. 이웃과 같은 색이 나오면 불가능이며, 이는 그래프가 이분 그래프가 아니라는 뜻이라 홀수 사이클이 있다는 말과 같습니다.

실제 시스템에서는

시험·수업 시간표 — 같은 학생이나 같은 강사가 겹치는 항목을 간선으로 이으면 색이 시간대가 됩니다. 웰시와 파월의 연구가 이 응용을 다뤘습니다.
컴파일러의 레지스터 할당 — 동시에 살아 있는 변수끼리 간선으로 잇는 간섭 그래프를 만들고, 색을 레지스터로 보아 칠합니다. 1981년 채이틴(Chaitin) 등이 이 방식을 제안했고, 이 방식에서는 색이 모자라면 일부 변수를 메모리로 내보내는 처리를 합니다.
스도쿠 — 81개 칸을 정점으로 보고 같은 행, 열, 3×3 블록에 있는 칸끼리 간선으로 이으면, 9색으로 칠하는 문제이며 이미 채워진 숫자는 미리 칠해진 정점입니다.
주파수·채널 배정 — 서로 간섭하는 기지국이나 무선 채널을 간선으로 이으면, 색을 주파수 채널로 보아 같은 채널을 간섭 없이 나눠 쓰게 하는 문제가 됩니다.

함께 보면 좋은 토픽

그래프 — 정점과 간선, 인접 리스트로 그래프를 표현하는 방법을 봅니다.
백트래킹 — 막히면 되돌아가는 탐색의 원리를 봅니다.
그리디 — 매 순간 가장 좋아 보이는 선택을 하는 방식과 그 한계를 봅니다.
BFS와 DFS — 2색 판별에 쓰는 너비 우선 탐색을 봅니다.

참고 자료

논문 — Welsh, D. J. A. and Powell, M. B., "An upper bound for the chromatic number of a graph and its application to timetabling problems", The Computer Journal 10(1), 1967
논문 — Karp, R. M., "Reducibility among combinatorial problems", Complexity of Computer Computations, 1972
논문 — Chaitin, G. J. et al., "Register allocation via coloring", Computer Languages 6(1), 1981
논문 — Appel, K. and Haken, W., "Every planar map is four colorable. Part I: Discharging", Illinois Journal of Mathematics 21(3), 1977 (Part II: Reducibility는 Appel, Haken, Koch)
논문 — Aardal, K. I. et al., "Models and solution techniques for frequency assignment problems", 4OR 1(4), 2003
논문 — Herzberg, A. M. and Murty, M. R., "Sudoku Squares and Chromatic Polynomials", Notices of the AMS 54(6), 2007
교재 — Cormen, Leiserson, Rivest, Stein, "Introduction to Algorithms", 3rd ed. (Elementary Graph Algorithms, NP-Completeness 장)

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)는?

"최소한 몇 가지 색이면 충분한가"를 묻는 값입니다.
색칠 수는 조건을 만족하며 그래프를 칠하는 데 필요한 가장 적은 색의 수입니다. 이 값을 구하는 것은 일반 그래프에서 NP-난해이며, k색으로 칠할 수 있는지 묻는 결정 문제는 k가 3 이상이면 NP-완전입니다.

Q3

그래프 색칠 문제를 응용할 수 있는 상황은?

서로 충돌하는 것들을 다른 "칸"에 넣어야 합니다.
충돌하는 항목(같은 학생이 듣는 과목)을 정점 간 간선으로 보고, 색을 시간대로 보면 시간표 배정이 그래프 색칠 문제가 됩니다. 컴파일러의 레지스터 할당에도 응용됩니다.