最近多媒體佈置了一個哈夫曼樹的壓縮算反的題目。然後我覺得我裏面用了不少算法。努力把它的時間複雜度變成了線性。
首先怎麼生成一棵哈夫曼樹(人工運算)我們是知道的吧。
計算機上。
我的做法分成了3步,準確說是4步。去生成一棵哈夫曼樹,其複雜度控制在O(n)
然後我用哈夫曼樹去進行編碼其複雜度我也把它降成了O(n)這兩部都是線性就能解決問題。
最後一個解碼。沒辦法只能變成O(KN),K是哈夫曼編碼時的字典大小。字典是變得。我沒辦法降低算法複雜度了。
給一個String串。我們第一步肯定是吧字符數量從小到大排序。爲了求出存在的比例嘛~
先介紹下節點
class Node
{
int number;//存放多少個數。就是指裏面value有多少次出現在string裏
char value;//存放string裏每個出現的char。唯一的
Node(){}
Node(char temp,int number)
{
value=temp;
this.number=number;
}
//這裏是後面用於生產樹時在用的。
String code="";//存放哈西曼編碼
Node left=null;
Node right=null;
Node(Node a,Node b)//吧兩個出現頻率小的節點結合。
{
number=a.number+b.number;
left=a;
right=b;
value='#';//中間節點
}
}
此處我用map去數數。是爲了降低時間複雜度。
如果直接開一個結點然後把結點放到列表裏去,下次查找時就得從列隊的第一個結點查到最後一個。
這樣時間複雜度就是n^2了,而用map可以減低時間複雜度。因爲它是HashMap時間複雜度是1。總複雜度就變成了n
這一步時間複雜度是O(N)
private static List<Node> count(String src)
{
/*
*此處我用map去數數。是爲了降低時間複雜度。
*如果直接開一個結點然後把結點放到列表裏去,下次查找時就得從列隊的第一個結點查到最後一個。
*這樣時間複雜度就是n^2了,而用map可以減低時間複雜度。因爲它是HashMap時間複雜度是1
*/
Map<Character,Integer> map=new HashMap<Character,Integer>();
char[] charArray=src.toCharArray();
for(Character c:charArray)
{
if(!map.containsKey(c))
{
map.put(c,0);
}
map.put(c, map.get(c)+1);
}
//這是我們再把map裏的字符和數量導入到結點裏存儲。這樣就可以把這個函數的算法複雜度降低了
List<Node> list=new ArrayList<Node>();
for(Map.Entry<Character, Integer> entry:map.entrySet())
{
Node temp=new Node(entry.getKey(),entry.getValue());
list.add(temp);
}
Collections.sort(list,(Node a,Node b)->(a.number<b.number?b.number:a.number));
Collections.reverse(list);
return list;
}
然後是生成一棵哈夫曼樹(並編碼。這裏其實可以生成兩步的,我合併了下)
規則就是我們熟知的。左邊填1,右邊填0
先選出數量少的去生成一棵樹,然後最後把多的給嫁接上
這一步時間複雜度是O(N)
private static Node buildTree(List<Node> list)
{
while(!(list.size()==1))
{
Node left=list.get(0);//先選出裏面數量最少的當作左邊節點
left.code=left.code+"1";
list.remove(0);//拿出來後記得刪除它。便於下次再拿出一個頭
Node right=list.get(0);//由於前面的第一少的已經拿掉了。那麼這個就變成第一少的了
right.code=right.code+"0";
list.remove(0);
Node root=new Node(left,right);//然後生成一個新節點。把它放入數組中
int index=0;//把索引定位。找到它在第幾個數前面,爲了插入回原來的列表。使這個列表的順序還是有序的。這裏其實是一遍插入排序
while(list.size()-1>index&&root.number>(list.get(index)).number)
{
index++;
}
list.add(index,root);//插入列表中
}
/* 樹已經建好了。但是還沒編碼。
* 我之前對樹做了點手腳
* 讓它每個節點記下自己當前處在左邊還是右邊。
* 就像我們人工做時,左右寫1和0一樣。
* 然後我們只要通過一次廣度搜索並且把節點從上往下的加就好了
* 廣度收索爲了減少回溯等時間。我們採取隊列去做
*/
Queue<Node> queue=new LinkedList<Node>();
queue.add(list.get(0));
while(!queue.isEmpty())
{
Node temp=queue.poll();
if(temp.left!=null)
{
temp.left.code=temp.code+temp.left.code;
queue.add(temp.left);
}
if(temp.right!=null)
{
temp.right.code=temp.code+temp.right.code;
queue.add(temp.right);
}
}
return list.get(0);//傳出來應該就一個根節點了。
}
到上面爲止一棵哈夫曼樹就建好了。然後我們要把它變成字典。爲了方便查找嘛
採用廣度收索的方式去查找
這一步時間複雜度是O(N)
//之前已經生成了一棵哈夫曼樹,現在把這課哈夫曼樹變成個字典。便於我們查找
private static Map<Character,String> dictionary(Node root)
{
//把節點存放到map裏方便查找(預處理)
//這裏通過廣度搜索的方式存放節點
Map<Character,String> map=new HashMap<Character,String>();
Queue<Node> queue=new LinkedList<Node>();
queue.add(root);
while(!queue.isEmpty())
{
Node temp=queue.poll();
if(temp.left!=null)
queue.add(temp.left);
if(temp.right!=null)
queue.add(temp.right);
if(temp.value=='#')
continue;
map.put(temp.value, temp.code);
}
return map;
}
之後還得有轉碼字符串吧。吧字符串通過哈希錶轉碼
private static String createString(Map<Character,String> dictionary,String src)
{
StringBuilder answer=new StringBuilder();
char[] array=src.toCharArray();
//利用字典去查找檢索
for(int i=0;i<array.length;i++)
{
answer.append(dictionary.get(array[i]));
}
return answer.toString();
}
最後是解碼
/* 這裏沒辦法減小算法複雜度了好像。就行查字典幾個必須步驟好久就是這樣
* 需要全部遍歷一下數組,同時每次找都得遍歷下字典。
* 爲了儘可能的減少不必要的查找,我們用字典去找字符串
* 看看傳出來第一個找到的是不是第0位來加快速度
*/
這一步時間複雜度是O(KN) K是字典大小。是個不定的東西
private static String readString(Map<Character,String> dictionary,String src)
{
StringBuilder temp=new StringBuilder(src);//臨時存放數據的
StringBuilder result=new StringBuilder();
while(temp.length()>0)
{
for(Map.Entry<Character, String> entry: dictionary.entrySet())
{
if(temp.indexOf(entry.getValue())==0)//是不是第一位
{
result.append(entry.getKey());
temp.delete(0, entry.getValue().length());
break;
}
}
}
return result.toString();
}
結束。
下面是全部代碼
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Scanner;
//用於哈夫曼樹
class Node
{
int number;//存放多少個數。就是指裏面value有多少次出現在string裏
char value;
Node(){}
Node(char temp,int number)
{
value=temp;
this.number=number;
}
String code="";//存放哈西曼數
Node left=null;
Node right=null;
Node(Node a,Node b)
{
number=a.number+b.number;
left=a;
right=b;
value='#';//中間節點
}
}
public class Huffman
{
public static void main(String[] argc)
{
Scanner cin=new Scanner(System.in);
String code=cin.next().trim();
huffmancoding(code);
}
//哈夫曼樹部分
//第一步計算所有的字符數量並按小到大排序
private static List<Node> count(String src)
{
/*
*此處我用map去數數。是爲了降低時間複雜度。
*如果直接開一個結點然後把結點放到列表裏去,下次查找時就得從列隊的第一個結點查到最後一個。
*這樣時間複雜度就是n^2了,而用map可以減低時間複雜度。因爲它是HashMap時間複雜度是1
*/
Map<Character,Integer> map=new HashMap<Character,Integer>();
char[] charArray=src.toCharArray();
for(Character c:charArray)
{
if(!map.containsKey(c))
{
map.put(c,0);
}
map.put(c, map.get(c)+1);
}
//這是我們再把map裏的字符和數量導入到結點裏存儲。這樣就可以把這個函數的算法複雜度降低了
List<Node> list=new ArrayList<Node>();
for(Map.Entry<Character, Integer> entry:map.entrySet())
{
Node temp=new Node(entry.getKey(),entry.getValue());
list.add(temp);
}
Collections.sort(list,(Node a,Node b)->(a.number<b.number?b.number:a.number));
Collections.reverse(list);
return list;
}
/*生成一棵哈夫曼樹,並編碼
* 規則就是我們熟知的。左邊填1,右邊填0
* 先選出數量少的去生成一棵樹,然後最後把多的給嫁接上
*/
private static Node buildTree(List<Node> list)
{
while(!(list.size()==1))
{
Node left=list.get(0);//先選出裏面數量最少的當作左邊節點
left.code=left.code+"1";
list.remove(0);//拿出來後記得刪除它。便於下次再拿出一個頭
Node right=list.get(0);//由於前面的第一少的已經拿掉了。那麼這個就變成第一少的了
right.code=right.code+"0";
list.remove(0);
Node root=new Node(left,right);//然後生成一個新節點。把它放入數組中
int index=0;//把索引定位。找到它在第幾個數前面,爲了插入回原來的列表。使這個列表的順序還是有序的
while(list.size()-1>index&&root.number>(list.get(index)).number)
{
index++;
}
list.add(index,root);//插入列表中
}
/* 樹已經建好了。但是還沒編碼。
* 我之前對樹做了點手腳
* 讓它每個節點記下自己當前處在左邊還是右邊。
* 就像我們人工做時,左右寫1和0一樣。
* 然後我們只要通過一次廣度搜索並且把節點從上往下的加就好了
* 廣度收索爲了減少回溯等時間。我們採取隊列去做
*/
Queue<Node> queue=new LinkedList<Node>();
queue.add(list.get(0));
while(!queue.isEmpty())
{
Node temp=queue.poll();
if(temp.left!=null)
{
temp.left.code=temp.code+temp.left.code;
queue.add(temp.left);
}
if(temp.right!=null)
{
temp.right.code=temp.code+temp.right.code;
queue.add(temp.right);
}
}
return list.get(0);//傳出來應該就一個根節點了。
}
//之前已經生成了一棵哈夫曼樹,現在把這課哈夫曼樹變成個字典。便於我們查找
private static Map<Character,String> dictionary(Node root)
{
//把節點存放到map裏方便查找(預處理)
//這裏通過廣度搜索的方式存放節點
Map<Character,String> map=new HashMap<Character,String>();
Queue<Node> queue=new LinkedList<Node>();
queue.add(root);
while(!queue.isEmpty())
{
Node temp=queue.poll();
if(temp.left!=null)
queue.add(temp.left);
if(temp.right!=null)
queue.add(temp.right);
if(temp.value=='#')
continue;
map.put(temp.value, temp.code);
}
return map;
}
//Test函數
private static void huffmancoding(String src)
{
//哈夫曼樹的字典
Map<Character,String> temp=dictionary(buildTree(count(src)));
System.out.println("哈夫曼編碼");
System.out.println("原字符串:"+src);
System.out.print("字典: ");
int i=1;
for(Map.Entry<Character, String> entry: temp.entrySet())
{
if(i++%4==0)
System.out.println();
System.out.print(entry.getKey()+"值爲"+entry.getValue()+" ");
}
System.out.println();
String result=createString(temp, src);
System.out.println("編碼後爲:"+result);
System.out.println("解碼後爲:"+readString(temp,result));
}
//這一步就是利用這課哈夫曼樹對字符串進行編碼
private static String createString(Map<Character,String> dictionary,String src)
{
StringBuilder answer=new StringBuilder();
char[] array=src.toCharArray();
//利用字典去查找檢索
for(int i=0;i<array.length;i++)
{
answer.append(dictionary.get(array[i]));
}
return answer.toString();
}
//解碼哈西曼編碼
/* 這裏沒辦法減小算法複雜度了好像。就行查字典幾個必須步驟好久就是這樣
* 需要全部遍歷一下數組,同時每次找都得遍歷下字典。
* 爲了儘可能的減少不必要的查找,我們用字典去找字符串
* 看看傳出來第一個找到的是不是第0位來加快速度
*/
private static String readString(Map<Character,String> dictionary,String src)
{
StringBuilder temp=new StringBuilder(src);//臨時存放數據的
StringBuilder result=new StringBuilder();
while(temp.length()>0)
{
for(Map.Entry<Character, String> entry: dictionary.entrySet())
{
if(temp.indexOf(entry.getValue())==0)//是不是第一位
{
result.append(entry.getKey());
temp.delete(0, entry.getValue().length());
break;
}
}
}
return result.toString();
}
}