본문 바로가기

프로그래밍/알고리즘

압축 (3)

Compression (3)


HuffmanCoding

- Huffman Coding 알고리즘은 트리들의 집합을 유지한다.

- 매 단계마다 가장 빈도가 작은 두 트리를 찾는다.

- 두 트리를 하나로 합친다. 이 때 부모 노드는 두 자식 노드를 합한 값이다.

- 위 연산에 가장 적합한 자료구조는 최소 힙(minimum heap)이다.

- 힙에 저장된 각 원소들은 하나의 트리이다. (노드 또는 런이 아니다.)


최소 힙

위 트리의 각 구성요소들은 전부 트리들이다. (single node tree) 그렇기 때문에 위 트리는 5개의 single node tree가 있다고 할 수 있다.

왼쪽 트리는 개념적인 구조이며 오른쪽 배열은 트리를 실제로 구현한 것이다.

extractMin작업을 하게 될 경우 루트가 항상 가장 작기 때문에 루트를 제거하고 가장 마지막 요소를 루트로 올린다. 그렇게 되면 최소 힙의 구조가 깨지게 되며 이를 복구하기 위해 heapify를 수행한다.

최종 직전의 상태에서 두 트리를 extrctMin하면 잠시 힙이 비게 된다. 이 때 빈 힙에 최종적으로 완성된 허프만 트리를 삽입하여 과정을 완성한다. 완성된 트리의 루트 트리는 theRoot라 칭한다.


class Run 수정

class Run implements Comparable<Run> {

public byte symbol;

public int runLen;

public int freq;

//트리의 노드로 사용하기 위해 왼쪽과 오른쪽 자식 노드 필드를 추가한다.

//두 개의 run간 크기를 비교하는 compareTo 메소드를 overriding한다.

//run간 비교를 수행하는 기준은 freq 변수다.


public int codeword; //32비트의 정수로 저장된 codeword

public int codewordLen; //codewordLen이 실제로 저장된 codeword의 길이가 된다.

}


class Heap<Run>

- 일반적인 Heap클래스 구조를 이용하여 구현한다. Generics로 수정 후 heapify, insert, extractMin 등의 함수를 min heap에 맞게 수정한다.


Huffman Tree 만들기

public class HuffmanCoding {

private ArrayList<Run> runs = new ArrayList<>(); 

private Heap<Run> heap; //minimun heap을 이용한다.

private Run theRoot = null //완성된 허프만 트리의 루트를 저장하는 변수

private void createHuffmanTree() {

heap = new Heap<Run>();

//1. 모든 run들을 힙에 저장한다.

//2. heap의 크기가 1보다 클 경우 반복한다 (while heap size > 1)

//    extractMin연산을 두 번 수행한다.

//    extractMin연산을 통해 분리된 트리들을 하나로 결합한 트리를 만든다.

//    결합한 트리를 원래 힙에 합친다.

//3. theRoot변수에 최종적으로 완성된 허프만 트리의 루트를 저장한다.

}

}


print Huffman Tree

private void printHuffmanTree() {
    preOrderTraverse(theRoot, 0);

}

private void preOrderTraverse(Run node, int depth) {

for (int i = 0; i < depth; i++)

System.out.println(" ");

if (node == null)

System.out.println("null");

else {

System.out.println(node.toString());

preOrderTraverse(node.left, depth + 1);

preOrderTraverse(node.right, depth + 1);

}

}


Codeword 부여

- Huffman Tree의 리프 노드에 위치한 run들에 binary codeword를 부여한다. 왼쪽 가지는 0으로, 오른쪽 가지는 1로 설정한 후 각 리프 노드들의 codeword를 지정한다.

- prefix를 하나의 32비트 정수로 표현한다. 하지만 32비트 중 하위 몇개의 비트만이 실제로 부여된 codeword가 된다. 따라서 codeword의 길이는 따로 유지해야만 한다.

//assignCodewords(theRoot, 0, 0);을 통해 호출된다.

// codeword변수는 노드 p에 부여된것이며 length를 통해 그 길이를 알 수 있다.

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 + '0', length + 1);

assignCodewords(p.right, codeword + '1', length + 1);

}

}


main과 compressFile메소드

public class HuffmanCoding {
    ~

public void commpressFile(RandomAccessFile fIn) {

collectRuns(fIn);

createHuffmanTree();

assignCodewords(theRoot, 0, 0);

}

public static void main(String[] args) {

HuffmanCoding app = new HuffmanCoding();

RandomAccessFile fIn;

try {

fIn = new RandomAccessFile("sample.txt", "r");

app.compressFile(fIn);

fIn.close();

} catch (IOException e) System.err.println(fileName + "을 열 수 없습니다.");

}

}


비트 연산

public class bit {

public static void main(String[] args) {

int a = 60; // 0011 1100

int b = 13; // 0000 1101

int c = 0;


c = a & b; // 12 = 0000 1100 (and)

c = a | b; // 61 = 0011 1101 (or)

c = a ^ b // 49 = 0011 0001 (xor)

c = ~a; // -61 = 1100 0011 (nor)

c = a << 1 // 120 = 0111 1000 (left shift)

c = (a << 1) + 1; // 121 = 0111 1001

c = a << 2; // 240 = 1111 0000

}

}

'프로그래밍 > 알고리즘' 카테고리의 다른 글

동적 계획법 (1) - Fibonacci & Binomial  (0) 2019.06.27
압축 (4)  (0) 2019.06.24
압축 (2)  (0) 2019.06.23
압축 (1)  (0) 2019.06.23
최단 경로 (3)  (0) 2019.06.22