AYSTORY
algo_lec7 본문
Example 5: Fractional Knapsack
문제 정의
: fractional knapsack problem이란?
- 물건마다 가치(value)와 무게(weight)가 있음
- 배낭의 최대 수용 무게 W가 주어짐
- 각 물건은 조각내어 일부만 넣을 수 있음 (fractional 가능)
- 배낭 용량 W 안에서, 물건을 쪼개서 최대 가치를 채워 넣는 문제
Greedy 해법
- 단위 무게당 가치 (value/weight) 기준으로 내림차순 정렬
- 배낭에 넣을 수 있을 만큼 넣고, 남으면 일부만 비례하여 넣음
코드 핵심
class Item:
def __init__(self, value, weight):
self.value = value
self.weight = weight
def fractional_knapsack(W, items):
# value/weight 비율로 정렬 (내림차순)
items.sort(key=lambda x: x.value / x.weight, reverse=True)
total_value = 0.0 # 총 가치
remaining_capacity = W
for item in items:
if item.weight <= remaining_capacity:
# 전체 항목을 넣을 수 있으면 전부 넣음
total_value += item.value
remaining_capacity -= item.weight
else:
# 일부만 넣고 종료
fraction = remaining_capacity / item.weight
total_value += item.value * fraction
break
return total_value
- 정렬 후 반복문으로 가득 찰 때까지 item 추가
- 시간복잡도: O(n log n)
The greedy approach always gives an optimal solution for the fractional knapsack problem.
Example 6: Shortest Path – Dijkstra's Algorithm
문제 정의
- 단일 시작 정점에서 다른 정점까지의 최단 경로
핵심 아이디어
- dist[ ]: 시작점부터의 최단 거리
- sptSet[ ]: shortest path tree에 포함 여부
- 방문하지 않은 정점 중 가장 가까운 정점을 선택 → 인접 정점의 거리 갱신
코드 흐름
import sys # 무한대를 표현하기 위한 용도 (sys.maxsize 사용)
V = 5 # 그래프의 정점 수
# 현재까지 확정되지 않은 정점 중에서 최단 거리(dist)가 가장 짧은 정점을 선택
def minDistance(dist, sptSet):
min = sys.maxsize # 초기값은 무한대
for u in range(V):
if not sptSet[u] and dist[u] < min:
min = dist[u]
min_index = u
return min_index # 최단 거리 정점의 인덱스 반환
# Dijkstra 알고리즘 구현
def dijkstra(graph, src):
dist = [sys.maxsize] * V # 시작점으로부터 각 정점까지의 거리. 초기값은 무한대
dist[src] = 0 # 시작점은 거리 0
sptSet = [False] * V # 최단 경로가 확정된 정점 여부를 저장하는 배열
for _ in range(V):
u = minDistance(dist, sptSet) # 아직 확정되지 않은 정점 중 최단 거리인 정점 선택
sptSet[u] = True # 선택된 정점은 최단 경로 확정
# 선택된 정점 u에 인접한 모든 정점 v에 대해 거리 갱신
for v in range(V):
# 조건:
# 1. u와 v 사이에 간선이 존재해야 하고 (graph[u][v] > 0)
# 2. v는 아직 최단 경로가 확정되지 않았으며
# 3. u를 거쳐 v로 가는 경로가 기존보다 짧을 경우
if graph[u][v] > 0 and not sptSet[v] and \
dist[u] + graph[u][v] < dist[v]:
dist[v] = dist[u] + graph[u][v] # 거리 갱신
# 결과 출력: 시작점으로부터 각 정점까지의 최단 거리
print("Vertex \tDistance from Source")
for i in range(V):
print(i, "\t", dist[i])
- 초기 dist는 ∞, 시작점은 0
- 인접 정점 탐색하며 조건에 따라 dist 업데이트
예시

- 5개 정점의 가중치 그래프
- 각 단계에서 dist[] 값이 갱신됨
Time Complexity
- O(V²)
가중치가 음수가 아닌 그래프에 대해 항상 최적해 보장
6. Huffman Coding
목적: 문자 인코딩 압축
- 어떤 문자열에서 문자마다 고정된 길이의 코드 대신, 빈도에 따라 가변 길이 코드를 부여하면 전체 파일 크기를 줄일 수 있음
- 특히 자주 등장하는 문자는 짧게, 드물게 등장하는 문자는 길게 코딩하면 효율적
기본 용어
- prefix-free code:
어떤 코드도 다른 코드의 접두사(prefix)가 되어서는 안 됨
(예: "0", "01" → 접두사 관계 → X) - Huffman Code:
문자들의 등장 빈도(frequency)를 기반으로 prefix-free한 최적의 이진 코드를 만들어주는 알고리즘
→ Greedy 알고리즘 기반
정의
- 각 문자를 고유한 Binary cid(이진 코드)로 표현
- 빈도에 따라 가변 길이 코드를 할당해 전체 길이 최소화
예시
- fixed-length binary code
- a:00 b:01 c:11
- ababcbbbc 000100011101010111 (18 bits)
- variable-length binary code
- a:10 b:0 c:11
- ababcbbbc 1001001100011 (13bits)
- 허프만 코드 (가변 길이): 150비트 (약 25% 절약 가능)

→ 각 줄의 의미는 다음과 같아:
- freq: 각 문자가 등장하는 횟수 (총 60 + 5 + 30 + 5 = 100회 등장)
- fixed: 고정 길이 binary code(2비트씩)
- 4개의 문자니까 ⌈log2 4⌉=2bit 필요
- prefix: 가변 길이 코드 (Huffman coding 기반)
- 자주 등장하는 문자일수록 코드가 짧다 (예: a는 1비트)
(1) Fixed-length 코드의 경우:
- 문자 하나당 2bit 고정
- 문자 100개 전체 저장에 필요한 비트 수:100×2=200 bits
(2) Prefix code의 경우 (Huffman 기반):
- 각 문자의 비트 수 × 등장 횟수로 계산:
| 문자 | 빈도 | 코드 길이 | 총 bit 수 |
| a | 60 | 1 (code: 0) | 60 × 1 = 60 |
| b | 5 | 3 (code: 110) | 5 × 3 = 15 |
| c | 30 | 2 (code: 10) | 30 × 2 = 60 |
| d | 5 | 3 (code: 111) | 5 × 3 = 15 |
| 합계 | 100 | — | 150 bits |
얼마나 절약되었는가?
- Fixed-length: 200비트
- Prefix code: 150비트
- 절약된 비트 수: 200−150=50비트
- 절감률: 50200×100=25%
→ 25% 저장공간 절약

표 해석: Fixed-length vs Variable-length Huffman Codewords
| 문자 | 고정 길이 코드 (3비트) | Huffman 코드 (가변 길이) |
| a | 000 | 0 |
| b | 001 | 101 |
| c | 010 | 100 |
| d | 011 | 111 |
| e | 100 | 1101 |
| f | 101 | 1100 |
고정 길이 코드
- 6개의 문자를 표현해야 하므로 최소 ⌈log2 6⌉=3비트 필요
- 모든 문자가 3비트로 고정됨
Huffman 코드
- 빈도 기반 트리 구조에 따라 부여된 가변 길이 코드
- 자주 등장하는 a는 1비트, 드물게 등장하는 e, f는 4비트
아래 그림: 두 개의 Huffman Tree 구조
두 그림 모두 같은 문자 집합을 사용했지만,
Huffman Tree 구성 순서에 따라 구조가 달라질 수 있음을 보여줌.
하지만 중요한 건:

두 트리 모두 동일한 total bit 수 (224비트)를 만들어냄
(왜냐하면, Huffman coding은 최적 prefix-free 코드를 항상 생성하기 때문)
Huffman 인코딩의 총 비트 수 계산
bit(T) = ∑ freq(v_i) × depth(v_i)
- freq(v_i): 문자 v_i의 등장 횟수
- depth(v_i): Huffman tree에서 해당 문자가 위치한 깊이 (루트에서의 거리)
적용 예제
| 문자 | freq. | depth | 계산 |
| a | 45 | 1 | 45 |
| b | 13 | 3 | 39 |
| c | 12 | 3 | 36 |
| d | 16 | 3 | 48 |
| e | 9 | 4 | 36 |
| f | 5 | 4 | 20 |
| 합계 | 100 | 224비트 |
→ 실제로 슬라이드 전체 Huffman 예제에서 나온 총 비트 수와 정확히 일치.
Example


- 가장 작은 두 빈도 f:8, b:9 → 합쳐서 17
(이게 그림의 오른쪽 하단 트리로 표시됨) - 그 다음 작은 두 빈도 c:12, d:14 → 합쳐서 26
(왼쪽 하단 트리로 표시됨) - 다음 작은 두 빈도 a:15, 17 → 합쳐서 32
(오른쪽 상단에서 32가 생김) - 마지막으로 26과 32를 합쳐서 58
(중앙 루트 노드가 58인 트리 생성)
여기까지 병합한 결과:
- 왼쪽 서브트리 (26): c, d 포함
- 오른쪽 서브트리 (32): a, f, b 포함
- 루트는 58
아직 등장하지 않은 노드: e:42
- Step 4에서 생성된 트리의 루트가 58
- 남은 노드 e:42와 위에서 만든 노드 58을 합쳐서 루트 노드 100 생성
알고리즘 흐름
- 각 문자 빈도수로 우선순위 큐 생성 (min-heap)
- 빈도 낮은 두 노드를 병합
- 최종 트리에서 각 문자에 prefix-free codeword 부여
Time Complexity
- Heap 사용 시 O(n log n)
항상 최적의 prefix-free binary code를 생성함
