- 그리디 알고리즘은 최적화 문제를 해결하는 알고리즘
- 최적화 (optimization) 문제
- 가능한 해들 중에서 가장 좋은 (최대 또는 최소) 해를 찾는 문제
- 욕심쟁이 방법, 탐욕적 방법, 탐욕 알고리즘 등으로 불림
- 그리디 알고리즘은 (입력) 데이터 간의 관계를 고려하지 않고 수행 과정에서 ‘욕심내어’ 최소값 또는 최대값을 가진 데이터를 선택
- 이러한 선택을 ‘근시안적’인 선택이라고 말하기도 함
- 그리디 알고리즘은 근시안적인 선택으로 부분적인 최적해를 찾고, 이들을 모아서(축적하여) 문제의 최적해를 얻는다.
![image.png]()
- 그리디 알고리즘은 일단 한 번 선택하면, 이를 절대로 번복하지 않는다.
- 즉, 선택한 데이터를 버리고 다른 것을 취하지 않는다.
- 이러한 특성 때문에 대부분의 그리디 알고리즘들은 매우 단순하며, 또한 제한적인 문제만이 그리디 알고리즘으로 해결된다.
4.1 동전 거스름돈 (Coin Change) 문제
- 동전 거스름돈 (Coin Change) 문제를 해결하는 가장 간단하고 효율적인 방법
- 남은 액수를 초과하지 않는 조건하에 ‘욕심내어’ 가장 큰 액면의 동전을 취하는 것
- 동전 거스름돈 문제의 최소 동전 수를 찾는 그리디 알고리즘
- 동전의 액면은 500원, 100원, 50원, 10원, 1원
- 의사 코드
CoinChange
입력: 거스름돈 액수 W
출력: 거스름돈 액수에 대한 최소 동전 수
1. change=W, n500=n100=n50=n10=n1=0
2. while ( change ≥ 500 )
change = change-500, n500++
3. while ( change ≥ 100 )
change = change-100, n100++
4. while ( change ≥ 50 )
change = change-50, n50++
5. while ( change ≥ 10 )
change = change-10, n10++
6. while ( change ≥ 1 )
change = change-1, n1++
7. return (n500+n100+n50+n10+n1)
- 그리디 알고리즘의 근시안적인 특성
- CoinChange 알고리즘은 남아있는 거스름돈인 change에 대해 가장 높은 액면의 동전을 거스르며,
- 500원 동전을 처리하는 line 2에서는 100원, 50원, 10원, 1원 동전을 몇 개씩 거슬러 주어야 할 것인지에 대해서는 전혀 고려하지 않는다.
- 760원의 거스름돈에 대해
![image.png]()
- CoinChange 알고리즘의 문제점
- 한국은행에서 160원 동전을 추가로 발행한다면, CoinChange 알고리즘이 항상 최소 동전 수를 계산할 수 있을까?
- 거스름돈이 200원이라면, CoinChange 알고리즘은 160원 동전 1개와 10원 동전 4개로서 총 5개를 리턴
- 200원에 대한 최소 동전 수는 100원짜리 동전 2개
- CoinChange 알고리즘은 항상 최적의 답을 주지 못함
- 그러나 실제로는 거스름돈에 대한 그리디 알고리즘이 적용되도록 동전이 발행됨
![image.png]()
4.2 최소 신장 트리 (Minimum Spanning Tree)
- 주어진 가중치 그래프에서 사이클이 없이 모든 점들을 연결시킨 트리들 중 간선들의 가중치 합이 최소인 트리
![image.png]()
- 주어진 그래프의 신장 트리를 찾는 방법
- 사이클이 없도록 모든 점을 연결시킨다.
- 그래프의 점의 수 = n
- 신장 트리에는 정확히 (n-1)개의 간선이 있다.
- 트리에 간선을 하나 추가시키면, 반드시 사이클이 만들어진다.
![image.png]()
- 크러스컬(Kruskal) 알고리즘
- 가중치가 가장 작은 간선이 사이클을 만들지 않을 때에만 ‘욕심내어’ 그 간선을 추가시킨다.
- 프림(Prim) 알고리즘
- 임의의 점 하나를 선택한 후, (n-1)개의 간선을 하나씩 추가시켜 트리를 만든다.
- 알고리즘의 입력은 1개의 연결 성분(connected component)로 된 가중치 그래프
- 크러스컬(Kruskal) 의사 코드
KruskalMST(G)
입력: 가중치 그래프 G=(V,E), |V|=n , |E|=m
출력: 최소 신장 트리 T
1. 가중치의 오름차순으로 간선들을 정렬: L = 정렬된 간선 리스트
2. T=∅
3. while ( T의 간선 수 < n-1 )
4. L에서 가장 작은 가중치를 가진 간선 e를 가져오고, e를 L에서 제거
5. if (간선 e가 T에 추가되어 사이클을 만들지 않으면)
6. e를 T에 추가
7. else
8. e를 버린다.
9. return 트리 T
KruskalMST 알고리즘 수행 과정
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
시간 복잡도
- Line 1 : 간선 정렬하는데 O(mlogm) 시간
- 단, m은 입력 그래프에 있는 간선의 수
- Line 2 : T를 초기화하는 것이므로 O(1) 시간
- Line 3~8
- while-루프는 최대 m번 수행 → 그래프의 모든 간선이 while-루프 내에서 처리되는 경우
- while-루프 내에서는 L로부터 가져온 간선 e가 사이클을 만드는지를
검사하는데 거의 O(1) 시간
- Kruskal 알고리즘의 시간복잡도: O(mlogm)
- 프림 (Prim)의 MST 알고리즘
- 주어진 가중치 그래프에서 임의의 점 하나를 선택한 후, (n-1) 개의 간선을 하나씩 추가시켜 트리 생성
- 추가되는 간선은 현재까지 만들어진 트리에 연결시킬 때 ‘욕심 내어’ 항상 최소의 가중치로 연결되는 간선
- 프림 (Prim) 의사 코드
PrimMST(G)
입력: 가중치 그래프 G=(V,E), |V|=n, |E|=m
출력: 최소 신장 트리 T
1. G에서 임의의 점 p를 시작점으로 선택 D[p] = 0
장하기 위한 원소
2. for (점 p가 아닌 각 점 v에 대하여) {
3. if ( 간선 (p, v)가 그래프에 있으면 )
4. D[v] = 간선 (p, v)의 가중치
5. else
6. D[v]=∞
}
7. T= {p}
8. while (T에 있는 점의 수 < n) {
9. T에 속하지 않은 각 점 v에 대하여, D[v]가 최소인 점 vmin과 연
결된 간선 (u, vmin)을 T에 추가, 여기서 u는 T에 속한 점이고, 점
vmin도 T에 추가
10. for (T에 속하지 않은 각 점 w에 대해서) {
11. if (간선 (vmin, w)의 가중치 < D[w])
12. D[w] = 간선 (vmin, w)의 가중치
}
}
13. return T
- D[v] 설명
- Line 1
- 임의로 점 p를 선택하고, D[p]=0으로 놓는다.
- D[v]에는 점 v와 T에 속한 점들을 연결하는 간선들 중에서 최소 가중치를 가진 간선의 가중치를 저장
- 그림에서 D[v]에는 10, 7, 15 중에서 최소 가중치인 7이 저장
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
시간 복잡도
- while-루프가 (n-1)회 반복되고,
- 1회 반복될 때 line 9에서 T에 속하지 않은 각 점 v에 대하여, D[v]가 최소인 점 vmin을 찾는데 O(n) 시간 소요
- 배열 D에서 (현재 T에 속하지 않은 점들에 대해서) 최솟값을 찾는 것이고, 배열의 크기는 n이기 때문
- 프림 알고리즘의 시간 복잡도
- (n-1) x O(n) = O(n2)
- 최소 힙(Binary Heap)을 사용하여 vmin을 찾으면 O(mlogn), m은 간선 수. 따라서 간선 수가 O(n)이면 O(nlogn)
- Kruskal과 Prim 알고리즘의 수행 과정 비교
- 크러스컬 알고리즘
- 간선이 1개씩 T에 추가되는데, 이는 마치 n개의 점들이 각각의 트리인 상태에서 간선이 추가되면 2개의 트리가 1개의 트리로 합쳐지는 것과 같음
- 크러스컬 알고리즘은 이를 반복하여 1개의 트리인 T를 생성
- n개의 트리들이 점차 합쳐져서 1개의 신장 트리가 만들어진다.
- 프림 알고리즘
- T가 점 1개인 트리에서 시작되어 간선을 1개씩 추가
- 1개의 트리가 자라나서 신장 트리가 된다.
응용
- 최소 비용으로 선로 또는 파이프 네트워크 (인터넷 광 케이블 선로, 케이블 TV선로, 전화선로, 송유관로, 가스관로, 배수로 등) 를 설치하는데 활용
4.3 최단 경로 (Shortest Path) 찾기
- 주어진 가중치 그래프에서 어느 한 출발점에서 또 다른 도착점까지의 최단 경로를 찾는 문제
- 최단 경로를 찾는 가장 대표적인 알고리즘
- 다익스트라(Dijkstra) 최단 경로 알고리즘
- 주어진 출발점에서 시작
- 출발점으로부터 최단 거리가 확정되지 않은 점들 중에서 출발점으로부터 가장 가까운 점을 추가하고, 그 점의 최단 거리를 확정
- 의사 코드
ShortestPath(G, s)
입력: 가중치 그래프 G=(V,E), |V|=n , |E|=m
출력: 출발점 s로부터 (n-1)개의 점까지 각각 최단 거리를 저장한 배
열 D
1. 배열 D를 ∞로 초기화. 단, D[s]=0으로 초기화
2. while (s로부터의 최단 거리가 확정되지 않은 점이 있으면)
3. 현재까지 최단 거리가 확정되지 않은 각 점 v에 대해서 최소의
D[v]의 값을 가진 점 vmin을 선택하고, s로부터 점 vmin까지의
최단 거리 D[vmin]을 확정
4. s로부터 현재보다 짧은 거리로 점 vmin을 통해 우회 가능한 각
점 w에 대해서 D[w]를 갱신
5. return D
간선 완화(Edge Relaxation)
![image.png]()
![image.png]()
![image.png]()
![image.png]()
시간 복잡도
- while-루프가 (n-1)회 반복되고, 1회 반복될 때
- line 3에서 최소의 D[v]를 가진 점 vmin을 찾는데 O(n) 시간 소요 → 왜냐하면 배열 D에서 최솟값을 찾기 때문
- line 4에서도 vmin에 연결된 점의 수가 최대 (n-1)개이므로, 각 D[w]를 갱신하는데 걸리는 시간은 O(n)
- ShotestPath의 시간 복잡도는
- (n-1) x {O(n)+O(n)} = O(n2)
- 프림 알고리즘과 같이 최소 힙(Binary Heap)을 사용하면 O(mlogn), m은 간선 수. 따라서 간선 수가 O(n)이면 O(nlogn)
응용
- 맵퀘스트 (MapQuest)와 구글 (Google) 맵
- 자동차 네비게이션
- 네트워크와 통신 분야
- 모바일 네트워크
- 산업 공학/경영 공학의 운영 (Operation) 연구
- 로봇 공학
- 교통 공학
- VLSI 디자인 분야 등
4.4 부분 배낭 (Knapsack) 문제
- n개의 물건이 각각 1개씩 있고, 각 물건은 무게와 가치를 가지고 있으며, 배낭이 한정된 무게의 물건들을 담을 수 있을 때, 최대의 가치를 갖도록 배낭에 넣을 물건들을 정하는 문제
- 부분 배낭 (Fractional Knapsack) 문제
- 물건을 부분적으로 담는 것을 허용
- 그리디 알고리즘으로 해결
- 0-1 배낭 문제
- 부분 배낭 문제의 원형으로 물건을 통째로 배낭에 넣어야 한다.
- 동적 계획 알고리즘, 백트래킹 기법, 분기 한정 기법으로 해결
아이디어
- 부분 배낭 문제에서는 물건을 부분적으로 배낭에 담을 수 있으므로, 최적해를 위해서 ‘욕심을 내어’ 단위 무게 당 가장 값나가는 물건을 배낭에 넣고, 계속해서 그 다음으로 값나가는 물건을 넣는다.
- 만일 물건을 ‘통째로’ 배낭에 넣을 수 없으면, 배낭에 넣을 수 있을 만큼만 물건을 부분적으로 배낭에 담는다.
- 의사 코드
FractionalKnapsack
입력: n개의 물건, 각 물건의 무게와 가치, 배낭의 용량 C
출력: 배낭에 담은 물건 리스트 L과 배낭 속의 물건 가치의 합
v
1. 각 물건에 대해 단위 무게 당 가치를 계산한다.
2. 물건들을 단위 무게 당 가치를 기준으로 내림차순으로 정렬
하고, 정렬된 물건 리스트를 S라고 하자.
3. L=∅, w=0, v=0
의 합, v는 배낭에 담긴 물건들의 가치의 합
4. S에서 단위 무게 당 가치가 가장 큰 물건 x를 가져온다.
5. while w + (x의 무게) ≤ C
6. x를 L에 추가
7. w = w + (x의 무게)
8. v = v + (x의 가치)
9. x를 S에서 제거
10. S에서 단위 무게 당 가치가 가장 큰 물건 x를 가져온다.
11. If C-w > 0
12. 물건 x를 (C-w) 만큼만 L에 추가
13. v = v + (C-w) 만큼의 x의 가치
14. return L, v
수행 과정
- 배낭의 최대 용량 = 40그램
- 단위 무게 당 가치로 정렬: S=[백금, 금, 은, 주석]
- 물건 단위 그램당 가치
- 백금을 통째로 담는다.
- 배낭에 담긴 물건(들)의 무게
w = 10, 얻는 가치 v = 60
- 금을 통째로 담는다.
- 배낭에 담긴 물건(들)의 무게
w = 25, v = 60+ 75 = 135
- 은을 통째로 담으려 하지만
- 배낭에 담긴 물건(들)의 무게 w = 25 + 25 = 50이 되어 배낭 용량 초과
- 은을 40 -25 = 15 만큼만 담는다.
- 배낭에 담긴 물건(들)의 무게
w = 40, v = 135 + (0.4x15) = 141 만원
시간 복잡도
- Line 1: n개의 물건 각각의 단위 무게 당 가치를 계산하는 데는 O(n) 시간 소요
- Line 2: 물건의 단위 무게 당 가치에 대해서 정렬하기 위해 O(nlogn) 시간 소요
- Line 5~10: while-루프의 수행은 n번을 넘지 않으며, 루프 내부의 수행은 O(1) 시간 소요
- Line 11~14: 각각 O(1) 시간 소요
- 알고리즘의 시간 복잡도: O(n)+O(nlogn)+nxO(1)+O(1) = O(nlogn)
응용
- 0-1 배낭 문제는 최소의 비용으로 자원을 할당하는 문제로서, 조합론, 계산이론, 암호학, 응용 수학 분야에서 기본적인 문제로 다뤄진다.
- ‘버리는 부분 최소화하는’ 원자재 자르기
- 자산투자 및 금융 포트폴리오에서의 최선의 선택
- Merkle–Hellman 배낭 암호 시스템에 사용
4.5 집합 커버 (Set Cover) 문제
- n개의 원소를 가진 집합 U가 있고, U의 부분집합들을 원소로 하는 집합 F가 주어질 때, F의 원소들인 집합들 중에서 어떤 집합들을 선택하여 합집합하면 U와 같게 되는가?
- 집합 F에서 선택하는 집합들의 수를 최소화하는 문제
- 신도시 학교 배치
- 신도시를 계획하는 데 있어서 학교 배치의 예
- 10개의 마을이 신도시에 만들어질 계획이다.
- 다음 조건이 만족되도록 학교 위치를 선정해야 한다.
- 학교는 마을에 위치해야 한다.
- 등교 거리는 걸어서 15분 이내이어야 한다.
- 어느 마을에 학교를 신설해야 학교의 수가 최소가 되는가?
![image.png]()
최적해
- 어느 마을에 학교를 신설해야 학교의 수가 최소가 되는가?
-
2번 마을에 학교를 만들면
→ 1, 2, 3, 4, 8 마을의 학생들이 15분 이내에 등교 가능
→ 즉, 마을 1, 2, 3, 4, 8이 커버된다.
-
6번 마을에 학교를 만들면
→ 마을 5, 6, 7, 9, 10이 커버된다.
-
2번과 6번 마을에 학교를 배치하면 모든 마을이 커버된다.
→ 최소의 학교 수 = 2개
![image.png]()
집합 커버
- 신도시 계획 문제를 집합 커버 문제로 변환
U = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 신도시의 마을 10개
F = {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10} // Si는 마을 i에 학교를 배치했을 때 커버되는 마을의 집합
S1={1, 2, 3, 8} S5={4, 5, 6, 7} S9 ={6, 9}
S2={1, 2, 3, 4, 8} S6={5, 6, 7, 9, 10} S10={6, 10}
S3={1, 2, 3, 4} S7={4, 5, 6, 7}
S4={2, 3, 4, 5, 7, 8} S8={1, 2, 4, 8}
• Si 집합들 중에서 어떤 집합들을 선택해야 그들의 합집합이 U와 같은가?
• 단, 선택된 집합의 수는 최소이어야
- 최적해
• S2∪S6 = {1, 2, 3, 4, 8} ∪ {5, 6, 7, 9, 10}
= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} = U
- 단순한 해결 방법
- 집합 커버 문제의 최적해는 어떻게 찾아야 할까?
- 가장 단순한 방법
- F에 있는 집합들의 모든 조합을 1개씩 합집합하여 U가 되는지 확인하고,
- U가 되는 조합의 집합 수가 최소인 것을 찾는다.
- F={S1, S2, S3}일 경우 모든 조합
- S1, S2, S3, S1∪S2, S1∪S3, S2∪S3, S1∪S2∪S3
- 집합이 1개인 경우 3개 = 3C1
- 집합이 2개인 경우 3개 = 3C2
- 집합이 3개인 경우 1개 = 3C3
- 총합은 3+3+1= 7 = 2^3-1 개
- n개의 원소가 있을 경우
- 최대 (2n-1)개를 검사하여야
- n이 커지면 최적해를 찾는 것은 실질적으로 불가능
- 이를 극복하기 위한 방법
- 최적해를 찾는 대신에 최적해에 근접한 근사해 (Approximation solution)를 찾는다.
- 의사 코드
SetCover
입력: U, F = {Si
}, i=1,⋯,n
출력: 집합 커버 C
1. C = ∅
2. while U ≠ ∅
3. U의 원소를 가장 많이 가진 집합 Si를 F에서 선택
4. U = U - Si
5. Si를 F에서 제거하고, Si를 C에 추가
6. return C
수행 과정
U = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
F = {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10}
S1={1, 2, 3, 8} S6={5, 6, 7, 9, 10}
S2={1, 2, 3, 4, 8} S7={4, 5, 6, 7}
S3={1, 2, 3, 4} S8={1, 2, 4, 8}
S4={2, 3, 4, 5, 7, 8} S9={6, 9}
S5={4, 5, 6, 7} S10={6, 10}
![image.png]()
![image.png]()
![image.png]()
![image.png]()
시간 복잡도
- 먼저 while-루프가 수행되는 횟수는 최대 n회
- 루프가 1회 수행될 때마다 집합 U의 원소 1개씩만 커버된다면, 최악의 경우 루프가 n번 수행되어야 하기 때문
- 루프가 1회 수행될 때
- Line 3: U의 원소들을 가장 많이 포함하고 있는 집합 S를 찾으려면, 현재 남아있는 Si들 각각을 U와 비교하여야
- Si들의 수가 최대 n이라면, 각 Si와 U의 비교는 O(n) 시간이 걸리므로, line 3은 O(n2) 시간
- 집합 U에서 집합 Si의 원소를 제거하므로 O(n) 시간
- Si를 F에서 제거하고, Si를 C에 추가하는 것은 O(1) 시간
- 시간 복잡도: n x O(n^2) = O(n^3)
응용
- 도시 계획 (City Planning)에서 공공 기관 배치하기
- 경비 시스템: 경비가 요구되는 장소의 CCTV 카메라의 최적 배치
- 컴퓨터 바이러스 찾기
- 대기업의 구매 업체 선정
- 기업의 경력 직원 고용
- 그 외에도 비행기 조종사 스케줄링, 조립 라인 균형화, 정보 검색 등에 활용
4.6 작업 스케줄링 (Job Scheduling) 문제
- 작업의 수행 시간이 중복되지 않도록 모든 작업을 가장 적은 수의 기계에 배정하는 문제
- 학술대회에서 발표자들을 강의실에 배정하는 문제와 같다.
- 발표= ‘작업’, 강의실= ‘기계’
- 작업 스케줄링 문제에 주어진 문제 요소
- 작업의 수
- 입력의 크기이므로 알고리즘을 고안하기 위해 고려되어야 하는 직접적인 요소는 아니다.
- 각 작업의 시작시간과 종료시간
- 작업의 길이
- 작업의 시작시간과 종료시간은 정해져 있으므로 작업의 길이도 주어진 것
- 시작시간, 종료시간, 작업 길이에 대한 그리디 알고리즘
- 빠른 시작시간 작업 우선 (Earliest start time first) 배정
- 빠른 종료시간 작업 우선 (Earliest finish time first) 배정
- 짧은 작업 우선 (Shortest job first) 배정
- 긴 작업 우선 (Longest job first) 배정
- 위 4가지 중 첫 번째 알고리즘을 제외하고 나머지 3가지는 항상 최적해를 찾지 못함
- 의사 코드
JobScheduling
입력: n개의 작업 t1
, t2
, ⋯, tn
출력: 각 기계에 배정된 작업 순서
1. 시작 시간으로 정렬한 작업 리스트: L
2. while L ≠ ∅
3. L에서 가장 이른 시작 시간 작업 ti를 가져온다.
4. if ti를 수행할 기계가 있으면
5. ti를 수행할 수 있는 기계에 배정
6. else
7. 새 기계에 ti를 배정
8. ti를 L에서 제거
9. return 각 기계에 배정된 작업 순서
수행 과정
- t1=[7,8], t2=[3,7], t3=[1,5], t4=[5,9], t5=[0,2], t6=[6,8], t7=[1,6]
- [s, f]에서, s는 시작 시간, f는 종료 시간
- 정렬: L = {[0,2], [1,6], [1,5], [3,7], [5,9], [6,8], [7,8]}
![image.png]()
![image.png]()
![image.png]()
![image.png]()
시간 복잡도
- Line 1: 정렬 시간 O(nlogn)
- while-루프
- 작업을 L에서 가져다 수행 가능한 기계를 찾아서 배정하므로 O(m) 시간 소요, 단, m은 사용된 기계의 수
- while-루프가 수행된 총 횟수는 n번이므로, line 2~9까지는 O(m) x n = O(mn) 시간 소요
- 시간 복잡도: O(nlogn)+O(mn)
응용
- 비즈니스 프로세싱
- 공장 생산 공정
- 강의실/세미나 룸 배정
- 컴퓨터 태스크 스케줄링 등
4.7 허프만 파일 압축 (file compression) 문제
- 파일의 각 문자가 8 bit 아스키 (ASCII) 코드로 저장되면, 그 파일의 bit 수는 8 x (파일의 문자 수)
- 파일의 각 문자는 일반적으로 고정된 크기의 코드로 표현
- 고정된 크기의 코드로 구성된 파일을 저장하거나 전송할 때 파일의 크기를 줄이고, 필요시 원래의 파일로 변환할 수 있으면, 메모리 공간을 효율적으로 사용할 수 있고, 파일 전송 시간을 단축
- 파일의 크기를 줄이는 방법을 파일 압축 (file compression)이라 함
아이디어
- 허프만 (Huffman) 압축은 파일에 빈번히 나타나는 문자에는 짧은 이진 코드를 할당하고, 드물게 나타나는 문자에는 긴 이진 코드를 할당
- 허프만 압축 방법으로 변환시킨 문자 코드들 사이에는 접두부 특성 (prefix property)이 존재
- 각 문자에 할당된 이진 코드는 어떤 다른 문자에 할당된 이진 코드의 접두부 (prefix)가 되지 않는다.
- [예제] 문자 ‘a’에 할당된 코드가 ‘101’이라면, 모든 다른 문자의 코드는 ‘101’로 시작되지 않으며 또한 ‘1’이나 ‘10’도 아니다.
- 접두부 특성의 장점은 코드와 코드 사이를 구분할 특별한 코드가 필요 없다.
- 101#10#1#111#0#⋯에서 ‘#’가 인접한 코드를 구분 짓고 있는데, 허프만 압축에서는 이러한 특별한 코드 없이 파일을 압축/해제 가능
- 허프만 압축은 입력 파일에 대해 각 문자의 빈도수 (문자가 파일에 나타나는 횟수)에 기반을 둔 이진 트리를 만들어서, 각 문자에 이진 코드를 할당
- 의사 코드
HuffmanCoding
입력: 입력 파일의 n개의 문자에 대한 각각의 빈도수
출력: 허프만 트리
1. 각 문자 당 노드를 만들고, 그 문자의 빈도수를 노드에 저장
2. n 노드의 빈도수에 대해 우선 순위 큐 Q를 만든다.
3. while Q에 있는 노드 수 ≥ 2
4. 빈도수가 가장 적은 2개의 노드 (A와 B)를 Q에서 제거
5. 새 노드 N을 만들고, A와 B를 N의 자식 노드로 만든다
6. N의 빈도수 = A의 빈도수 + B의 빈도수
7. 노드 N을 Q에 삽입
8. return Q
수행 과정
- 각 문자의 빈도수에 대해 A: 450 T: 90 G: 120 C: 270
- Line 2를 수행한 후의 Q
-
우선 순위 큐 Q를 생성
![image.png]()
![image.png]()
![image.png]()
![image.png]()
- 반환된 트리를 살펴보면 각 이파리 (단말) 노드에만 문자가 있다.
- 루트로부터 왼쪽 자식 노드로 내려가면 ‘0’을, 오른쪽 자식 노드로 내려가면 ‘1’을 부여하면서, 각 이파리에 도달할 때까지의 이진수를 추출하여 문자의 이진 코드를 얻는다.
![image.png]()
압축률
- 예제에서 ‘A’는 ‘0’, ‘T’는 ‘100’, ‘G’는 ‘101’, ‘C’는 ‘11’의 코드가 각각 할당된다.
- 할당된 코드들을 보면, 가장 빈도수가 높은 ‘A’가 가장 짧은 코드를 가지고, 따라서 루트의 자식이 되어 있고, 빈도수가 낮은 문자는 루트에서 멀리 떨어지게 되어 긴 코드를 가진다.
- 이렇게 얻은 코드는 접두부 특성을 가진다.
- 압축된 파일의 bit 수
- (450x1)+(90x3)+(120x3)+(270x2) = 1,620 bits
- 아스키 코드로 된 파일 크기
- (450+90+120+270)x8 = 7,440 bits
- 파일 압축률
- (1,620/7,440)x100 = 21.8%이며, 원래의 약 1/5 크기로 압축
복호화
101 / 100 / 100 / 0 / 11 / 101 / 0 / 101 / 0 / 100
→ G T T A C G A G A T
시간 복잡도
- Line 1: n개의 노드를 만들고, 각 빈도수를 노드에 저장하므로 O(n) 시간
- Line 2: n개의 노드로 우선순위 큐 Q를 만든다.
- 여기서 우선 순위 큐로서 이진 힙 자료구조를 사용하면 O(n) 시간
- Line 3~7
- 최소 빈도수를 가진 노드 2개를 Q에서 제거하는 힙의 삭제 연산과 새 노드를 Q에 삽입하는 연산을 수행하므로 O(logn) 시간 소요
- while-루프는 (n-1)번 반복
- 왜냐하면 루프가 1번 수행될 때마다 Q에서 2개의 노드를 제거하고 1개를 Q에 추가하기 때문
- (n-1) x O(logn) = O(nlogn)
- Line 8
- 트리의 루트를 반환하는 것이므로 O(1) 시간
- 시간 복잡도는 O(n)+O(n)+O(nlogn)+ O(1) = O(nlogn)
응용
- 팩스(FAX), 대용량 데이터 저장, 멀티미디어 (Multimedia), MP3 압축 등에 활용
- 정보 이론 (Information Theory) 분야에서 엔트로피 (Entropy)를 계산하는데 활용
- 이는 자료의 불특정성을 분석하고 예측하는데 이용
요약
-
그리디 알고리즘은 (입력) 데이터 간의 관계를 고려하지 않고 수행 과정에서 ‘욕심내어’ 최적값을 가진 데이터를 선택하며, 선택한 값들을 모아서 문제의 최적해를 찾는다.
-
그리디 알고리즘은 문제의 최적해 속에 부분 문제의 최적해가 포함되어 있고, 부분 문제의 해 속에 그 보다 작은 부분 문제의 해가 포함되어 있다. 이를 최적 부분 구조 (Optimal Substructure) 또는 최적성 원칙 (Principle of Optimality)이라고 한다.
-
동전 거스름돈 문제를 해결하는 가장 간단한 방법은 남은 액수를 초과하지 않는 조건하에 가장 큰 액면의 동전을 취하는 것이다. 단, 일반적인 경우에는 최적해를 찾으나 항상 최적해를 찾지는 못한다.
-
크러스컬의 알고리즘은 가중치가 가장 작으면서 사이클을 만들지 않는 간선을 추가시키어 트리를 만든다. 시간 복잡도는 O(mlogm). 단, m은 그래프의 간선의 수
-
프림의 알고리즘은 최소의 가중치로 현재까지 만들어진 트리에 연결되는 간선을 트리에 추가시킨다. 시간 복잡도는 O(n^2)
-
다익스트라의 알고리즘은 출발점으로부터 최단 거리가 확정되지 않은 점들 중에서 출발점으로부터 가장 가까운 점을 추가하고, 그 점의 최단 거리를 확정한다. 시간 복잡도는 O(n^2)
-
부분 배낭(Fractional Knapsack) 문제에서는 단위 무게 당 가장 값나가는 물건을 계속해서 배낭에 담는다. 마지막엔 배낭에 넣을 수 있을 만큼만 물건을 부분적으로 배낭에 담는다. 시간 복잡도는 O(nlogn)
-
집합 커버(Set Cover) 문제는 근사(Approximation) 알고리즘을 이용하여 근사해를 찾는 것이 보다 실질적이다. U의 원소들을 가장 많이 포함하고 있는 집합을 항상 F에서 선택한다. 시간 복잡도는 O(n^3)
-
작업 스케줄링(Job Scheduling) 문제는 빠른 시작시간 작업 먼저(Earliest start time first) 배정하는 그리디 알고리즘으로 최적해를 찾는다 . 시간 복잡도는 O(nlogn)+O(mn). n은 작업의 수이고, m은 기계의 수
-
허프만 압축은 파일에 빈번히 나타나는 문자에는 짧은 이진 코드를 할당하고, 드물게 나타나는 문자에는 긴 이진 코드를 할당
- n이 문자의 수일 때, 시간 복잡도는 O(nlogn)