근사 알고리즘으로 NP-난해 문제의 해를 구하는 방법

근사 알고리즘의 근사비 개념과 Vertex Cover, Metric TSP, 최근접 이웃 전략을 바탕으로 NP-난해 문제의 실무 적용 조건을 정리한다.

2026-08-14 · 최초 발행 2024-04-29

최적해 대신 보장 가능한 해를 택할 때

NP-난해 문제에서는 최적해 탐색 비용이 실무 제약을 넘기기 쉽다. 근사 알고리즘은 다항시간 안에 최적값과 일정 비율 이내의 해를 산출하는 방식으로 이 간극을 다룬다.

최소화 문제에서 α-근사는 알고리즘의 해값이 α·OPT 이하임을 뜻한다. 최대화 문제에서는 해값이 OPT/α 이상이어야 한다. Vertex Cover와 Traveling Salesman Problem(TSP)은 이 기준과 탐욕적 전략을 함께 살펴보기 좋은 문제다.

Vertex Cover는 그래프의 모든 간선을 덮는 최소 정점 집합을 찾는 문제이며, 무가중과 가중 버전 모두 NP-난해다. TSP는 모든 도시를 정확히 한 번 방문한 뒤 출발점으로 되돌아오는 최소 비용 순회를 찾는다. 삼각 부등식이 성립하는 Metric TSP에서는 강한 근사 보장을 기대할 수 있지만, 일반 TSP는 다항 근사가 불가하다(최신 정보 확인 필요).

보장 조건은 문제 구조에서 나온다

근사비를 분석할 때는 charging argument, 교환 논법, 삼각 부등식 같은 증명 기법을 활용한다. Vertex Cover의 2-근사와 Metric TSP의 2-근사, Christofides의 1.5-근사는 이런 분석으로 알려진 표준 결과다.

구현에서는 Greedy 선택 규칙, MST 기반 배가(doubling), Primal-Dual 방식이 자주 쓰인다. 서브모듈성, 메트릭 성질, 이분성처럼 문제에 내재한 구조에 맞춰 전략을 골라야 한다.

단순 Greedy는 O(|E|) 또는 O(n^2) 수준에서 동작하며, MST+DFS는 O(E log V) 또는 O(n^2)다. 대규모 입력에서도 선형·준선형 성능을 달성하기 쉽다.

TSP 근사 보장을 적용하려면 대칭성, 삼각 부등식, 연결성을 확인해야 한다. 가중 Vertex Cover에서는 Primal-Dual이 2-근사를 보장하지만 단순 Greedy에는 같은 보장이 없다.

Vertex Cover에서 미덮개 간선을 처리하는 방식

무가중 단순 그래프 G=(V, E)에서 아직 덮이지 않은 간선 (u, v)를 하나 고른다. 두 끝점 {u, v}를 해 집합에 넣고, u 또는 v에 닿는 모든 간선을 제거한다. 남은 간선이 없어질 때까지 반복하면 근사 해 C를 얻으며 |C| ≤ 2·OPT가 보장된다.

Metric TSP의 MST 기반 순회

대칭 거리 행렬과 연결 그래프를 입력으로 받고 삼각 부등식이 성립한다고 가정한다. 먼저 MST를 만든 뒤 DFS 전순회로 방문 순서를 생성한다. 방문 순서의 중복 노드는 shortcutting으로 건너뛰어 순회를 완성한다.

이 방식으로 얻은 경로 Πcost(Π) ≤ 2·OPT를 만족한다.

빠른 초안을 위한 최근접 이웃

거리 행렬만 주어졌을 때는 임의의 시작점에서 출발해, 아직 방문하지 않은 노드 가운데 가장 가까운 곳으로 이동하는 과정을 반복할 수 있다. 마지막에는 시작점으로 복귀한다.

최근접 이웃은 일반적으로 근사비를 보장하지 않지만 경험적 성능은 양호하다. 보장 조건이 충족되지 않거나 빠르게 초안을 만들어야 할 때 사용할 수 있다.

Vertex CoverTSP연결·대칭·삼각 부등식 성립불충족불연결입력그래프 G(V,E) 또는 거리 행렬D문제 유형Greedy 2-근사미덮개 간선 선택 끝점 추가전제 확인MST 배가 2-근사MST→DFS→Shortcut최근접 이웃보장 약함, 빠른 휴리스틱출력: 2-근사 정점 집합출력: 경로, 근사비 2출력: 경로, 근사비 미보장오류/불능 보고컴포넌트별 경로 또는 입력정합성 재검증

검증 가능한 구현 예시

Python 3.10+와 입력 검증을 전제로 한 소규모 예시다.

무가중 Vertex Cover의 2-근사 구현은 다음과 같다.

def vertex_cover_2approx(n, edges):
    # n: 정점 수(0..n-1), edges: (u,v) 리스트
    uncovered = set(tuple(sorted(e)) for e in edges)
    cover = set()
    while uncovered:
        u, v = next(iter(uncovered))
        cover.add(u); cover.add(v)
        to_remove = [e for e in uncovered if u in e or v in e]
        for e in to_remove:
            uncovered.discard(e)
    return sorted(cover)

# 예시
n = 5
edges = [(0,1),(1,2),(2,3),(3,4)]
print(vertex_cover_2approx(n, edges))  # 예: [1,2,3]

다음 구현은 Prim 알고리즘으로 MST를 만든 뒤 DFS 전순회와 shortcutting을 적용한다.

def tsp_mst_2approx(dist):
    # dist: 대칭 거리 행렬, dist[i][i]=0, 삼각 부등식 가정
    n = len(dist)
    if any(dist[i][i] != 0 for i in range(n)):
        raise ValueError("대각 원소 0 요구")
    # Prim MST
    in_mst = [False]*n
    key = [float('inf')]*n
    parent = [-1]*n
    key[0] = 0
    for _ in range(n):
        u = min((key[i], i) for i in range(n) if not in_mst[i])[1]
        in_mst[u] = True
        for v in range(n):
            if not in_mst[v] and dist[u][v] < key[v]:
                key[v], parent[v] = dist[u][v], u
    adj = [[] for _ in range(n)]
    for v in range(1, n):
        u = parent[v]
        if u == -1:
            raise ValueError("비연결 그래프")
        adj[u].append(v); adj[v].append(u)
    # DFS preorder
    visited = [False]*n
    order = []
    def dfs(x):
        visited[x] = True
        order.append(x)
        for y in adj[x]:
            if not visited[y]:
                dfs(y)
    dfs(0)
    # Shortcutting
    seen, tour = set(), []
    for v in order:
        if v not in seen:
            tour.append(v); seen.add(v)
    tour.append(tour[0])
    return tour

# 예시
dist = [
    [0, 2, 3, 3],
    [2, 0, 4, 3],
    [3, 4, 0, 2],
    [3, 3, 2, 0],
]
print(tsp_mst_2approx(dist))  # 예: [0,1,3,2,0]

최근접 이웃 Greedy는 다음처럼 경로를 만든다.

def tsp_nearest_neighbor(dist, start=0):
    n = len(dist)
    visited = [False]*n
    path = [start]; visited[start] = True
    cur = start
    for _ in range(n-1):
        nxt = min((dist[cur][v], v) for v in range(n) if not visited[v])[1]
        path.append(nxt); visited[nxt] = True
        cur = nxt
    path.append(start)
    return path

네트워크·라우팅·검증 업무에 적용하기

네트워크 모니터링과 점검 지점 선정에서는 링크 커버리지를 만족하는 최소 수의 포인트 선택을 Vertex Cover로 모델링할 수 있다. 장비 수와 비용 제약이 있을 때 2-근사 해로 빠른 배치안을 구한다.

라스트마일 배송이나 설비 점검 라우팅은 거리 행렬이 메트릭에 가까울 경우 MST 2-근사 또는 Christofides로 고품질 경로를 산출할 수 있다. 실시간 재라우팅에서는 최근접 이웃으로 초안을 만든 뒤 개선 휴리스틱을 적용한다.

테스트 케이스와 데이터 검증 커버리지 역시 제약 위반 엣지를 덮는 문제로 볼 수 있다. 최소 테스트 포인트를 선정하는 데 Greedy 2-근사를 사용하면 시간을 단축할 수 있다.

Vertex Cover는 최적 대비 최대 2배 내 해를 보장하며, 수십만 엣지 규모에서 수 초 내 해를 산출할 가능성이 있다. Metric TSP는 2-근사로 최적 대비 ≤ 2배 경로 길이를 보장하고, Christofides를 사용하면 추가 구현 비용을 수반하는 1.5-근사를 적용할 수 있다. 최근접 이웃은 O(n^2) 시간으로 실시간 의사결정을 지원하며, 로컬 서치와 결합하면 초기 해 대비 20~40% 개선을 기대할 수 있다(경험치).

해 품질에 대한 이론적 신뢰성을 확보하면서 구현 단순성과 운영 편의를 높여 시스템 유지보수를 수월하게 만드는 것이 이 접근의 장점이다.

선택 기준을 비교하면

알고리즘 성능(시간복잡도) 확장성 일관성(근사비) 안정성 운영 편의
Vertex Cover Greedy 2-근사 O(E) 매우 높음 2-근사 보장 입력 순서에 둔감 매우 단순
Metric TSP MST 2-근사 O(E log V) 또는 O(n^2) 높음 2-근사 보장(삼각 부등식) 전제 충족 시 안정 단순(MST+DFS)
TSP 최근접 이웃 O(n^2) 중간 보장 미약(최악 비유계) 시작점 민감 매우 단순

적용 전에는 전제 조건을 확인하고, 품질과 시간의 트레이드오프에 맞춰 선택해야 한다. 전처리로 조건을 점검한 뒤 빠른 Greedy 초안을 만들고, MST나 Christofides 같은 구조적 근사를 적용하며, 필요하면 국소 개선을 옵션으로 더하는 흐름을 사용할 수 있다.

근사 알고리즘정점 덮개외판원 문제탐욕 알고리즘최소 신장 트리