Workspace IndexAlgorithms › Day 16

NP-Hardness and Reductions TODO

Algorithms · Day 16 / 100 · A. Advanced Algorithms & Data Structures (Day 1-19)

Concept

A reduction is a polynomial-time transformation of instances of problem A into instances of problem B; if A reduces to B, then B is no easier than A. NP-complete means a problem is in NP and every problem in NP reduces to it; NP-hard means a problem is at least that hard, regardless of whether it's in NP itself. The standard way to show a new problem is hard is to reduce a known NP-complete problem to it. If P != NP, no NP-complete problem has an exact polynomial-time algorithm. So in practice the choices become an algorithm with a proven approximation ratio, a heuristic, or an exact solver (ILP, SAT) that's exponential in the worst case.

Without knowing the source of the difficulty, you either keep tuning an optimization that's fundamentally impossible, or give up too early on a special structure that's actually easy to solve.

Code & Formula

# NP-난해와 환원 — subset-sum을 완전탐색(지수)과 그리디 근사로 풀어 오차를 비교한다.
# "환원" 감각: 정확한 다항 시간 해가 없다고 판단되면 근사/휴리스틱으로 전환하는 실무 지점을 보여준다.

def brute_force_subset_sum(nums, target):
    n = len(nums)
    best = []
    for mask in range(1 << n):                      # 2^n 가지 부분집합을 전부 확인
        subset = [nums[i] for i in range(n) if mask & (1 << i)]
        s = sum(subset)
        if s <= target and s > sum(best):
            best = subset
    return best

def greedy_approx_subset_sum(nums, target):
    # 큰 값부터 넣을 수 있는 만큼 채우는 O(n log n) 휴리스틱 — 최적 보장은 없다
    remaining = target
    chosen = []
    for x in sorted(nums, reverse=True):
        if x <= remaining:
            chosen.append(x)
            remaining -= x
    return chosen

nums = [23, 17, 41, 8, 15, 30, 4]
target = 60

exact = brute_force_subset_sum(nums, target)
approx = greedy_approx_subset_sum(nums, target)

print("입력:", nums, "목표:", target)
print(f"완전탐색(지수, 2^{len(nums)}={1 << len(nums)}가지 확인): {exact} 합={sum(exact)}")
print(f"그리디 근사(O(n log n)):                    {approx} 합={sum(approx)}")
print(f"근사 오차: {target - sum(approx)} (목표 대비 {sum(approx) / target:.1%} 달성)")

Exercise

Pick one optimization problem from your own work, sketch a reduction from subset-sum or 3-SAT to it, and note the approximation ratio of a fallback approximation algorithm.

Practical Connection

Selecting transactions within a block or assembling MEV bundles has the NP-hard structure of a knapsack problem, so real builders rely on heuristics — Verex's batch-matching and batch-settlement optimization calls for the same judgment.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

NP-난해와 환원 TODO

Algorithms · Day 16 / 100 · A. 고급 알고리즘·자료구조 (Day 1–19)

언제 포기하고 근사로 갈지 판단하기

개념

환원은 문제 A의 인스턴스를 다항 시간에 문제 B의 인스턴스로 바꾸는 변환이며, A가 B로 환원되면 B가 A보다 쉽지 않다는 뜻이다. NP-완전은 NP에 속하면서 NP의 모든 문제가 그 문제로 환원되는 문제이고, NP-난해는 NP 소속 여부와 무관하게 그만큼 어려운 문제를 가리킨다. 새로운 문제가 어렵다는 것을 보이는 표준 방법은 이미 알려진 NP-완전 문제를 그 문제로 환원하는 것이다. P와 NP가 다르다면 NP-완전 문제에 다항 시간 정확 알고리즘은 존재하지 않는다. 그래서 실무 선택지는 근사 보장이 있는 알고리즘, 휴리스틱, 또는 정확하지만 최악에는 지수 시간인 solver(ILP, SAT) 중 하나가 된다.

어려움의 근거를 모르면 본질적으로 불가능한 최적화를 계속 튜닝하며 시간을 태우거나, 반대로 쉽게 풀리는 특수 구조를 못 알아보고 성급히 포기한다.

코드 · 수식

# NP-난해와 환원 — subset-sum을 완전탐색(지수)과 그리디 근사로 풀어 오차를 비교한다.
# "환원" 감각: 정확한 다항 시간 해가 없다고 판단되면 근사/휴리스틱으로 전환하는 실무 지점을 보여준다.

def brute_force_subset_sum(nums, target):
    n = len(nums)
    best = []
    for mask in range(1 << n):                      # 2^n 가지 부분집합을 전부 확인
        subset = [nums[i] for i in range(n) if mask & (1 << i)]
        s = sum(subset)
        if s <= target and s > sum(best):
            best = subset
    return best

def greedy_approx_subset_sum(nums, target):
    # 큰 값부터 넣을 수 있는 만큼 채우는 O(n log n) 휴리스틱 — 최적 보장은 없다
    remaining = target
    chosen = []
    for x in sorted(nums, reverse=True):
        if x <= remaining:
            chosen.append(x)
            remaining -= x
    return chosen

nums = [23, 17, 41, 8, 15, 30, 4]
target = 60

exact = brute_force_subset_sum(nums, target)
approx = greedy_approx_subset_sum(nums, target)

print("입력:", nums, "목표:", target)
print(f"완전탐색(지수, 2^{len(nums)}={1 << len(nums)}가지 확인): {exact} 합={sum(exact)}")
print(f"그리디 근사(O(n log n)):                    {approx} 합={sum(approx)}")
print(f"근사 오차: {target - sum(approx)} (목표 대비 {sum(approx) / target:.1%} 달성)")

연습

업무에서 다루는 최적화 문제 하나를 골라 subset-sum이나 3-SAT로부터의 환원을 스케치하고, 대안으로 쓸 근사 알고리즘의 보장 비율을 함께 적어라.

실무 · Verex 연결

블록 내 트랜잭션 선택·MEV 번들 조합은 배낭 문제류의 NP-난해 구조라 실제 빌더도 휴리스틱을 쓰며, Verex의 배치 매칭·배치 정산 최적화도 같은 판단이 필요하다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 15. 온라인 알고리즘과 경쟁비17. 병렬 알고리즘 모델 →