이 포스팅의 알고리즘 강의는 이곳에서 보실 수 있습니다.
Huffman Coding
- 가령 6개의 문자 a, b, c, d, e, f로 이루어진 파일이 있다고 하자. 문자의 총 개수는 100,000개이고 각 문자의 등장 횟수는 다음과 같다.
- Frequency는 문자의 등장횟수, Fixed-length code 는 고정길이 표현방법(3비트), Variable-length는 가변길이 표현방법이다.(표현방법에 중복된 표현은 존재하면 안된다.)
a | b | c | d | e | f | ||
---|---|---|---|---|---|---|---|
Frequency | 45 | 13 | 12 | 16 | 9 | 5 | |
Fixed-length code | 000 | 110 | 010 | 011 | 100 | 101 | |
Variable-length | 0 | 101 | 100 | 111 | 1101 | 1100 |
- 고정길이 코드를 사용하면 각각의 문자를 표현하기 위해서 3비트가 필요하며, 따라서 파일의 길이는 300,000비트가 된다.
- 위 테이블의 가변길이 코드를 사용하면 224,000비트가 된다. => 압축효과
Prefix Code
- 어떤 codeword도 다른 codeword의 prefix가 되지 않는 코드 (여기서 codeword란 하나의 문자에 부여된 이진코드를 뜻한다.)
- 모호함이 없는 decode가 가능함
- prefix code는 하나의 이진트리로 표현 가능함 (문자가 leaf node에 위치한다.)
- Prefix code를 사용해서 압축을 할 시 Huffam coding 보다 효율이 좋을 수 없다.
- 제일 처음에는 싱글 노드 트리가 5개가 있다고 생각한다.({}, {}, {}, {}, {})
- 그 중 가장 작은 것(트리로 만들 값을 선택해서) 2개를 골라 트리로 만든다.
- 계속 해서 반복해서 트리가 완성되면 prefix code로 활용한다.
Run-Length Encoding
- 런(run)은 동일한 문자가 하나 혹은 그 이상 연속해서 나오는 것을 의미한다. 예를 들어 스트링 s = “aaabba”는 다음과 같은 3개의 run으로 구성된다: “aaa”, “bb”, “a”.
- run-length encoding에서는 각각의 run을 그 “run을 구성하는 문자”와 “run의 길이”의 순서 쌍 (n, ch)로 encoding한다. 여기서 ch가 문자이고 n은 길이이다. => (3a2b1a)
- Run-length encoding은 길이가 긴 run들이 많은 경우에 효과적이다.
Huffman method with Run-length encoding
제1단계 : Run과 frequency 찾기
- 압축할 파일을 처음부터 끝까지 읽어서 파일을 구성하는 run들과 각 run들의 등장횟수를 구한다.
- 데이터 파일을 적어도 2번 읽어야 한다.
- Run을 찾아서 허프만 트리 생성
- 실제로 압축
- Run 인식하기
제2단계 : Huffman tree
- Huffman coding 알고리즘은 트리들의 집합을 유지하면서
- 매 단계 가장 frequency가 작은 두 트리를 찾아서
- 두 트리를 합친다.
- 이런 연산에 가장 적합한 자료구조는 최소 힙(minimum heap)이다.
- 즉 힙에 저장된 각각의 원소들은 하나의 트리이다. (노드가 아니라.)
제3단계 : codeword 부여하기
- 허프만 트리에서 완성된 트리에 코드워드를 부여한다.
제4단계 : codeword 검색하기
- 데이터 파일을 압축하기 위해서는 데이터 파일을 다시 처음부터 읽으면서 run을 하나씩 인식 한 후 해당 run에 부여된 codeword를 검색한다.
- huffman tree 에는 모든 run들이 리프노드에 위치하므로 검색하기 불편하다.
- 검색하기 편리한 구조를 만들어야한다
제5단계 : 인코딩하기
- 압축파일의 맨 앞부분(header)에 파일을 구성하는 run들에 대한 정보를 기록한다.
- 이때 원본파일의 길이도 함께 기록한다. (각각의 run들에 부여한 코드워드를 알고 있어야 복원가능하기 때문에)
제6단계 : 디코딩하기
- 인코딩의 반대로 실행한다.
HuffmanCoding
package huffman;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
public class HuffmanCoding {
private ArrayList<Run> runs = new ArrayList<Run>();
private Heap<Run> heap;
private Run theRoot = null;
private Run[] chars = new Run[256];
private long sizeOriginalFile;
private void outputFrequencies(RandomAccessFile fIn, RandomAccessFile fOut) throws IOException {
fOut.writeInt(runs.size());
fOut.writeLong(fIn.getFilePointer());
for(int j = 0; j < runs.size(); j++) {
Run r = runs.get(j);
fOut.write(r.symbol);
fOut.writeInt(r.runLen);
fOut.writeInt(r.freq);
}
}
private void createHuffmanTree() {
Run[] arrRuns = new Run[runs.size()];
heap = new Heap<Run>(runs.toArray(arrRuns));
heap.buildMinHeap();
while(heap.getHeapSize() > 0) {
Run exMin1 = heap.extractMin();
Run exMin2 = heap.extractMin();
Run tree = new Run(exMin1.getFreq() + exMin2.getFreq());
tree.setSons(exMin1, exMin2);
heap.minHeapInsert(tree);
}
theRoot = heap.extractMin();
}
private void printHuffmanTree() {
preOrderTraverse(theRoot, 0);
}
private void storeRunsIntoArray(Run p) {
if(p.left == null && p.right == null) {
insertToArray(p);
} else {
storeRunsIntoArray(p.left);
storeRunsIntoArray(p.right);
}
}
private void insertToArray(Run p) {
if(chars[(int)p.symbol] == null) {
chars[(int)p.symbol] = new Run(p);
} else {
Run temp = chars[(int)p.symbol];
chars[(int)p.symbol] = new Run(p);
chars[(int)p.symbol].right = temp;
}
}
public Run findRun(byte symbol, int length) {
Run temp = chars[(int)symbol];
while(temp.right != null) {
if(temp.runLen == length) {
return temp;
}
temp = temp.right;
}
return temp;
}
private void preOrderTraverse(Run node, int depth) {
for(int i=0; i<depth; i++)
System.out.print(" ");
if(node == null) {
System.out.println("null");
} else {
System.out.println(node.toString());
preOrderTraverse(node.left, depth+1);
preOrderTraverse(node.right, depth+1);
}
}
private void collectRuns(RandomAccessFile fIn) throws IOException {
int ch = fIn.read();
int runLen = 1;
byte symbol = (byte)ch;
while(ch != -1) {
ch = fIn.read();
if(symbol != (byte)ch) {
Run run = new Run(symbol, runLen);
if(runs.contains(run)) {
runs.get(runs.indexOf(run)).plusFreq();
} else {
runs.add(run);
}
symbol = (byte)ch;
runLen = 1;
} else {
runLen++;
}
}
}
private void assignCodewords(Run p, int codeword, int length) {
if(p.left == null && p.right == null) {
p.codeword = codeword;
p.codewordLen = length;
} else {
assignCodewords(p.left, codeword << 1, length+1);
assignCodewords(p.right, (codeword << 1) + 1, length+1);
}
}
public void compressFile(String inFileName, RandomAccessFile fIn) throws IOException {
String outFileName = new String(inFileName + ".z");
RandomAccessFile fOut = new RandomAccessFile(outFileName, "rw");
collectRuns(fIn);
outputFrequencies(fIn, fOut);
createHuffmanTree();
assignCodewords(theRoot, 0, 0);
storeRunsIntoArray(theRoot);
fIn.seek(0);
encode(fIn, fOut);
}
private void encode(RandomAccessFile fIn, RandomAccessFile fOut) throws IOException {
// TODO Auto-generated method stub
int buffer = 0;
int bufferLen = 0;
int ch = fIn.read();
int runLen = 1;
byte symbol = (byte)ch;
while(ch != -1) {
ch = fIn.read();
if(symbol != (byte)ch) {
Run run = findRun(symbol, runLen);
System.out.println(run);
System.out.println("codeword : " + run.codeword + ", codewordLen : " + run.codewordLen);
bufferLen += run.codewordLen;
if(bufferLen >= 32) {
fOut.write(buffer);
buffer = 0;
buffer += run.codeword;
buffer = buffer << run.codewordLen;
bufferLen = run.codewordLen;
} else {
buffer <<= run.codewordLen;
buffer += run.codeword;
}
symbol = (byte)ch;
runLen = 1;
} else {
runLen++;
}
}
if (buffer != 0) {
int padding = 32 - bufferLen;
System.out.println(buffer);
buffer <<= padding;
System.out.println(buffer);
fOut.writeInt(buffer);
}
}
public void decompressFile(String fileName, RandomAccessFile fIn) throws IOException {
// TODO Auto-generated method stub
String outFileName = new String(fileName + ".dec");
RandomAccessFile fOut = new RandomAccessFile(outFileName, "rw");
inputFrequencies(fIn);
createHuffmanTree();
assignCodewords(theRoot, 0, 0);
printHuffmanTree();
decode(fIn, fOut);
}
private void decode(RandomAccessFile fIn, RandomAccessFile fOut) throws IOException {
// TODO Auto-generated method stub
int nbrBytesRead = 0, j, ch, bitCnt = 1, mask = 1, bits = 8;
mask <<= bits - 1;
for (ch=fIn.read(); ch != -1 && nbrBytesRead<sizeOriginalFile;) {
Run p = theRoot;
System.out.println(ch);
while(true) {
if (p.left == null && p.right == null) {
for(j=0; j<p.runLen; j++) {
fOut.write(p.symbol);
}
nbrBytesRead += p.runLen;
break;
}
else if ((ch & mask) == 0) {
p = p.left;
} else p = p.right;
if(bitCnt++ == bits) {
ch = fIn.read();
bitCnt = 1;
} else ch <<= 1;
}
}
}
private void inputFrequencies(RandomAccessFile fIn) throws IOException {
// TODO Auto-generated method stub
int dataIndex = fIn.readInt();
sizeOriginalFile = fIn.readLong();
runs = new ArrayList<>();
for(int j=0; j < dataIndex; j++) {
Run r = new Run();
r.symbol = (byte) fIn.read();
r.runLen = fIn.readInt();
r.freq = fIn.readInt();
System.out.println(r);
runs.add(r);
}
}
}
Huffman Encoder
package huffman;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class HuffmanEncoder {
public static void main(String[] args) {
String fileName = "";
HuffmanCoding app = new HuffmanCoding();
RandomAccessFile fIn;
Scanner kb = new Scanner(System.in);
try {
System.out.print("Enter a file name: "); ///Users/yesdoing/CodeSquard/workspace/TISA/src/huffman/sample.txt
fileName = kb.next();
fIn = new RandomAccessFile(fileName, "r");
app.compressFile(fileName, fIn);
fIn.close();
} catch (IOException e) {
System.out.println("Cannot open " + fileName);
}
}
}
Huffman Decoder
package huffman;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class HuffmanDecoder {
public static void main(String[] args) {
String fileName = "";
HuffmanCoding app = new HuffmanCoding();
RandomAccessFile fIn;
Scanner kb = new Scanner(System.in);
try {
System.out.print("Enter a file name: ");
fileName = kb.next();
fIn = new RandomAccessFile(fileName, "r");
app.decompressFile(fileName, fIn);
fIn.close();
} catch (IOException io) {
System.err.println("Cannot open " + fileName);
}
}
}
Comments