Workspace IndexAlgorithms › Day 14

Randomized and Approximation Algorithms TODO

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

Concept

Many combinatorial optimization problems are exactly expressed as integer programs (IP) with 0/1 variables, but IP itself is NP-hard. LP relaxation loosens that integrality constraint to the real interval [0, 1] so an optimal solution can be found in polynomial time, and the relaxed optimum gives a bound (a lower or upper bound) on the true problem's optimum. Randomized rounding takes the fractional solution x_i obtained that way and interprets it as "the probability of selecting i," then flips independent coins to round it back to an integer solution. Working out the expectation and applying concentration inequalities to show the result lands within some factor of the LP optimum is what proves the approximation ratio. The integrality gap — the worst-case ratio between the relaxed optimum and the true integer optimum — sets the ceiling on the approximation ratio this approach can ever reach.

Real-world scheduling, batching, and matching problems are mostly NP-hard, so insisting on the exact optimum simply doesn't scale — you need a basis for deciding when an approximate solution with a performance guarantee is acceptable. The LP relaxation value also gives you a free baseline for measuring how far a heuristic solution is from optimal.

Code & Formula

# Day 14: 랜덤화·근사 알고리즘 — LP 완화 + 랜덤 라운딩으로 Set Cover 근사
# 정수계획을 LP로 완화해 얻은 분수해를 확률로 삼아 반복 라운딩하면 근사 정수해를 얻는다.

import random

universe = set(range(1, 7))
sets = {"S1": {1, 2, 3}, "S2": {3, 4, 5}, "S3": {5, 6, 1}}
# 대칭 인스턴스: 원소마다 정확히 2개 집합이 덮으므로 x_S=0.5는 LP 완화의 실행가능(대칭적 최적) 분수해
x = {name: 0.5 for name in sets}
lp_value = sum(x.values())

def greedy_cover(sets, universe):
    remaining, chosen = set(universe), []
    while remaining:
        best = max(sets, key=lambda s: len(sets[s] & remaining))
        chosen.append(best)
        remaining -= sets[best]
    return chosen

def randomized_round(sets, x, universe, rng, max_rounds=30):
    covered, chosen, rounds = set(), set(), 0
    while covered != universe and rounds < max_rounds:
        rounds += 1
        for name in sets:
            if rng.random() < x[name]:
                chosen.add(name)
        covered = set().union(*(sets[s] for s in chosen)) if chosen else set()
    return chosen, rounds

rng = random.Random(7)
greedy = greedy_cover(sets, universe)
rounded, rounds = randomized_round(sets, x, universe, rng)

print(f"LP 완화 하한(분수 비용) = {lp_value}")
print(f"그리디 정수해 = {greedy} (비용 {len(greedy)})")
print(f"랜덤 라운딩 결과 = {sorted(rounded)} (비용 {len(rounded)}, {rounds}회 반복 후 커버 완료)")
print(f"전체 원소 커버 확인 = {set().union(*(sets[s] for s in rounded)) == universe}")

Exercise

Solve a set cover instance's LP relaxation with a solver, use the fractional solution as probabilities to run randomized rounding O(log n) times, and compare the resulting cost against a greedy solution and the LP lower bound.

Practical Connection

Backend problems involving discrete choices — batch-auction matching, node/relay placement, indexer shard allocation — reuse exactly this pattern: compute an LP lower bound and use it to evaluate a heuristic.

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/.


한국어

랜덤화·근사 알고리즘 TODO

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

LP 완화와 랜덤 라운딩

개념

많은 조합 최적화 문제는 변수에 0 또는 1만 허용하는 정수계획(IP)으로 정확히 표현되지만 IP 자체는 NP-hard다. LP 완화는 이 정수 제약을 0 이상 1 이하의 실수 구간으로 느슨하게 풀어 다항 시간에 최적해를 구하는 기법이며, 완화한 최적값은 원 문제 최적값의 한계(하한 또는 상한)를 준다. 랜덤 라운딩은 이렇게 얻은 분수해 x_i를 "i를 선택할 확률"로 해석해 독립적으로 동전을 던져 정수해로 되돌린다. 기댓값 계산과 집중 부등식으로 결과가 LP 최적값의 일정 배 안에 들어옴을 보이면 근사비가 증명된다. 완화 최적값과 진짜 정수 최적값의 최악 비율인 integrality gap이 이 방식으로 도달 가능한 근사비의 한계를 정한다.

실무의 스케줄링·배치·매칭 문제는 대부분 NP-hard라 최적해를 고집하면 풀리지 않고, 성능 보장이 있는 근사해를 언제 받아들일지 판단하는 근거가 필요하다. 또 LP 완화값은 휴리스틱 해가 최적에서 얼마나 떨어졌는지 재는 무료 기준선이 된다.

코드 · 수식

# Day 14: 랜덤화·근사 알고리즘 — LP 완화 + 랜덤 라운딩으로 Set Cover 근사
# 정수계획을 LP로 완화해 얻은 분수해를 확률로 삼아 반복 라운딩하면 근사 정수해를 얻는다.

import random

universe = set(range(1, 7))
sets = {"S1": {1, 2, 3}, "S2": {3, 4, 5}, "S3": {5, 6, 1}}
# 대칭 인스턴스: 원소마다 정확히 2개 집합이 덮으므로 x_S=0.5는 LP 완화의 실행가능(대칭적 최적) 분수해
x = {name: 0.5 for name in sets}
lp_value = sum(x.values())

def greedy_cover(sets, universe):
    remaining, chosen = set(universe), []
    while remaining:
        best = max(sets, key=lambda s: len(sets[s] & remaining))
        chosen.append(best)
        remaining -= sets[best]
    return chosen

def randomized_round(sets, x, universe, rng, max_rounds=30):
    covered, chosen, rounds = set(), set(), 0
    while covered != universe and rounds < max_rounds:
        rounds += 1
        for name in sets:
            if rng.random() < x[name]:
                chosen.add(name)
        covered = set().union(*(sets[s] for s in chosen)) if chosen else set()
    return chosen, rounds

rng = random.Random(7)
greedy = greedy_cover(sets, universe)
rounded, rounds = randomized_round(sets, x, universe, rng)

print(f"LP 완화 하한(분수 비용) = {lp_value}")
print(f"그리디 정수해 = {greedy} (비용 {len(greedy)})")
print(f"랜덤 라운딩 결과 = {sorted(rounded)} (비용 {len(rounded)}, {rounds}회 반복 후 커버 완료)")
print(f"전체 원소 커버 확인 = {set().union(*(sets[s] for s in rounded)) == universe}")

연습

set cover 인스턴스를 LP 솔버로 완화해 풀고, 분수해를 확률로 삼아 O(log n)회 반복 랜덤 라운딩한 결과의 비용을 그리디 해 및 LP 하한과 비교하라.

실무 · Verex 연결

배치 경매식 체결, 노드·릴레이 배치, 인덱서 샤드 할당처럼 이산 선택이 들어가는 백엔드 문제에서 "LP로 하한을 구해 휴리스틱을 평가한다"는 패턴이 그대로 쓰인다.

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

← 13. 선형계획과 쌍대성 직관15. 온라인 알고리즘과 경쟁비 →