Huffman編碼實現壓縮解壓縮
阿新 • • 發佈:2019-01-07
這是我們的課程中佈置的作業,找一些資料將作業完成,順便將其寫到部落格,以後看起來也方便。
原理介紹
什麼是Huffman壓縮
Huffman( 哈夫曼 ) 演算法在上世紀五十年代初提出來了,它是一種無失真壓縮方法,在壓縮過程中不會丟失資訊熵,而且可以證明 Huffman 演算法在無失真壓縮演算法中是最優的。 Huffman 原理簡單,實現起來也不困難,在現在的主流壓縮軟體得到了廣泛的應用。對應用程式、重要資料等絕對不允許資訊丟失的壓縮場合, Huffman 演算法是非常好的選擇。
怎麼實現Huffman壓縮
哈夫曼壓縮是個無損的壓縮演算法,一般用來壓縮文字和程式檔案。哈夫曼壓縮屬於可變程式碼長度演算法一族。意思是個體符號(例如,文字檔案中的字元)用一個特定長度的位序列替代。因此,在檔案中出現頻率高的符號,使用短的位序列,而那些很少出現的符號,則用較長的位序列。- 二叉樹
在電腦科學中,二叉樹是每個結點最多有兩個子樹的有序樹。通常子樹的根被稱作 “ 左子樹 ” ( left subtree )和 “ 右子樹 ” ( right subtree )。
- 哈夫曼編碼 (Huffman Coding)
哈夫曼編碼是一種編碼方式,哈夫曼編碼是可變字長編碼 (VLC) 的一種。 uffman 於 1952 年提出一種編碼方法,該方法完全依據字元出現概率來構造異字頭的平均長 度最短的碼字,有時稱之為最佳編碼,一般就叫作 Huffman 編碼。
- 二叉樹
- Huffman編碼生成步驟
- 掃描要壓縮的檔案,對字元出現的頻率進行計算。
- 把字元按出現的頻率進行排序,組成一個佇列。
- 把出現頻率最低(權值)的兩個字元作為葉子節點,它們的權值之和為根節點組成一棵樹。
- 把上面葉子節點的兩個字元從佇列中移除,並把它們組成的根節點加入到佇列。
- 把佇列重新進行排序。重複步驟 3、4、5 直到佇列中只有一個節點為止。
- 把這棵樹上的根節點定義為 0 (可自行定義 0 或 1 )左邊為 0 ,右邊為 1 。這樣就可以得到每個葉子節點的哈夫曼編碼了。
如 (a) 、 (b) 、 (c) 、 (d) 幾個圖,就可以將離散型的資料轉化為樹型的了。
如果假設樹的左邊用0 表示右邊用 1 表示,則每一個數可以用一個 01 串表示出來。
則可以得到對應的編碼如下:
1–>110
2–>111
3–>10
4–>0
每一個01 串,既為每一個數字的哈弗曼編碼。
- 為什麼能壓縮
壓縮的時候當我們遇到了文字中的1 、 2 、 3 、 4 幾個字元的時候,我們不用原來的儲存,而是轉化為用它們的 01 串來儲存不久是能減小了空間佔用了嗎。(什麼 01 串不是比原來的字元還多了嗎?怎麼減少?)大家應該知道的,計算機中我們儲存一個 int 型資料的時候一般式佔用了 2^32-1 個 01 位,因為計算機中所有的資料都是最後轉化為二進位制位去儲存的。所以,想想我們的編碼不就是隻含有 0 和 1 嘛,因此我們就直接將編碼按照計算機的儲存規則用位的方法寫入進去就能實現壓縮了。
比如:
1這個數字,用整數寫進計算機硬碟去儲存,佔用了 2^32-1 個二進位制位
而如果用它的哈弗曼編碼去儲存,只有110 三個二進位制位。
效果顯而易見。
編碼實現
- 流程圖
編碼流程
- 資料結構
CharacterWeight:記錄字元值,以及其在待壓縮檔案中的權重。
public class CharacterCode {
private int weight;//字元值
private char character;//字元值
private String code;//其對應huffman編碼
}
HuffmanNode:huffman樹中的節點資訊。
public class HuffmanNode {
private int parent;//父節點
private int lChild;//左子
private int rChild;//右子
private int weight;//權重
}
- 程式關鍵步驟
- Huffman樹的構建
Huffman樹的變數:ArrayList list;
流程圖
- Huffman樹的構建
程式碼
for(int i=0;i<list.size()-1;i++){
//w1 : the first min weight w2: the second min weight
//i1 : the first min weight index, i2: the second min weight index
int w1 = MAX_VALUE, w2=MAX_VALUE;
int i1 = 0, i2 = 0;
// find the two node with the minimum weight
for(int j=0;j<tree.size();j++){
HuffmanNode node = tree.get(j);
if(node.getWeight()< w1 && node.getParent()==-1){
w2 = w1;
w1 = node.getWeight();
i2 = i1;
i1 = j;
}
else if(node.getWeight()<w2 && node.getParent()==-1){
w2 = node.getWeight();
i2 = j;
}
}
//set the two node to be the children of a new node, and add the new node to the tree
HuffmanNode pNode = new HuffmanNode(w1+w2);
pNode.setlChild(i1);
pNode.setrChild(i2);
tree.add(pNode);
tree.get(i1).setParent(tree.indexOf(pNode));
tree.get(i2).setParent(tree.indexOf(pNode));}
- 根據Huffman 樹獲得Huffman編碼
從葉子節點開始網上遍歷Huffman樹,直到到達根節點,根據當前節點為其父節點的左兒子還是右兒子確定這一位值是0還是1。最後將依次獲得的0,1字串反轉獲得Huffman編碼。
for(int i=0;i<list.size();i++){
HuffmanNode node = tree.get(i);
HuffmanNode pNode = tree.get(node.getParent());
String code ="";
while(true){
if(pNode.getlChild()==tree.indexOf(node)){
code = "0"+code;
}
else if(pNode.getrChild() == tree.indexOf(node)){
code = "1"+code;
}
else {
System.out.println("Tree Node Error!!!");
return null;
}
node=pNode;
if(node.getParent()!=-1)
pNode=tree.get(node.getParent());
else
break;
}
list.get(i).setCode(new String(code));
}
標頭檔案設計
編碼 型別 位元組數 字元總數 Int 4 字元種類數 Short 2 葉子節點 char字元 short 父節點 3 非葉子節點 Short 左兒子 short 右兒子 short父節點 6 檔案頭長度(單位: byte)
l= 9n
其中n 為字元種類數。- 檔案內容的編碼和寫入
程式碼
while((temp=reader.read())!=-1){ //!= EOF
// get the code from the code table
String code = codeTable.get((char)temp);
c++;
if(c>=count/96){
System.out.print("=");
c=0;
}
try{
StringBuilder codeString = new StringBuilder(code);
outputStringBuffer.append(codeString);
while(outputStringBuffer.length()>8){
out.write(Short.parseShort(outputStringBuffer.substring(0, 8),2));
outputStringBuffer.delete(0, 8);
}
} catch(Exception e){
e.printStackTrace();
}
}
解碼實現
- 流程圖
- 資料結構
HuffmanNode:huffman樹中的節點資訊。
public class HuffmanNode {
private int parent;//父節點
private int lChild;//左子
private int rChild;//右子
private int weight;//權重
}
程式關鍵步驟
重建Huffman樹。在檔案頭中存放的原本就是Huffman樹的節點資訊。
in = new DataInputStream(new FileInputStream(file)); count = in.readInt(); charNum = in.readShort(); nodeNum = 2*charNum -1; //rebuild the huffman tree for(int i=0;i<charNum;i++){ HuffmanNode node = new HuffmanNode((char)in.readByte()); int parent = in.readShort(); node.setParent(parent); tree.add(node); } for(int i=charNum;i<nodeNum;i++){ HuffmanNode node = new HuffmanNode(' '); int l = in.readShort(); int r = in.readShort(); int p = in.readShort(); node.setlChild(l); node.setrChild(r); node.setParent(p); tree.add(node); }
解碼
流程圖
程式碼
while(true){
while(buff.length()<32){
temp = in.readInt();
String codeString = Integer.toBinaryString(temp);
while(codeString.length()<32){
codeString='0'+codeString;
}
buff.append(codeString);
}
node = tree.get(tree.size()-1);
dep = 0;
while(!(node.getlChild()==-1&&node.getrChild()==-1)){
if(dep>=buff.length()){
System.out.println( "Buff overflow");
}
if(buff.charAt(dep)=='0'){
node = tree.get(node.getlChild());
}
else if(buff.charAt(dep)=='1'){
node = tree.get(node.getrChild());
}
else{
System.out.println("Coding error");
}
dep++;
}
char c = node.getCH();
num++;
if(num>=n/99){
System.out.print("=");
num=0;
}
count++;
if(count>=n){
break;
}
charBuff+=c;
if(charBuff.length()>256){
writer.write(charBuff);
charBuff="";
}
buff.delete(0, dep);
}
} catch(EOFException e){
//just do nothing
}
catch(Exception e){
e.printStackTrace();
} finally{
//there may be data released in the buff and charbuff, so we need to process them
while(buff.length()>0){
node = tree.get(tree.size()-1);
dep = 0;
while(!(node.getlChild()==-1&&node.getrChild()==-1)){
if(dep>=buff.length()){
break;
}
if(buff.charAt(dep)=='0'){
node = tree.get(node.getlChild());
}
else if(buff.charAt(dep)=='1'){
node = tree.get(node.getrChild());
}
else{
System.out.println("Coding error");
//return;
}
dep++;
}
char c = node.getCH();
num++;
if(num>=n/99){
System.out.print("=");
num=0;
}
count++;
if(count>=n){
break;
}
charBuff+=c;
if(charBuff.length()>256){
try {
writer.write(charBuff);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
charBuff="";
}
buff.delete(0, dep);
}
try {
writer.write(charBuff);
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try{
writer.close();
} catch(IOException e){
throw e;
}
專案原始碼
留坑回頭放上