哈夫曼樹的編碼實驗

Java哈夫曼編碼實驗--哈夫曼樹的建立,編碼與解碼

建樹,造樹,編碼,解碼

一、哈夫曼樹編碼介紹

  • 1、哈夫曼樹:
    • (1)定義:假設有n個權值{w1, w2, ..., wn},試構造一棵含有n個葉子結點的二叉樹,每個葉子節點帶權威wi,則其中帶權路徑長度WPL最小的二叉樹叫做最優二叉樹或者哈夫曼樹。
    • (2)特點:哈夫曼樹中沒有度爲1的結點,故由n0 = n2+1以及m= n0+n1+n2,n1=0可推出m=2*n0-1,即一棵有n個葉子節點的哈夫曼樹共有2n-1個節點。
  • 2、哈夫曼編碼步驟:
    • (1)計算字符出現的次數:
      • 假設我們現在有一段文檔需要進行編碼,我們現在需要對於每一個字符出現的次數進行統計,然後分別計算出其在這篇文檔的權重,也就是頻率,我們用下面的一段文字進行舉例;
      • 在如下的文檔中包括的字符有8個字母和2個空格,所以我們現在需要去統計這10個字符的個數,統計後的數據如下:
    • (2)對字符出現的次數作爲一個權重,從小到大進行排序;
      • 排序結果:0.1,0.1,0.1,0.1,0.1,0.1,0.2,0.2
    • (3)依照排序結果從小到大根據以下規則進行造樹:
      • a、最小的前兩個數進行權重的相加,形成他們兩個作爲左子樹和右子樹的父結點,如果是左結點就標記爲0,如果是右結點就標記爲1
      • b、然後將父結點作爲一個新的結點進入排序結果,之後進行重新排序(ps:假如出現添加後的父結點的權重和之前排序中的結點的權重相等的情況,兩者的位置具體是對於建樹沒有影響的,但是在編程實現的過程中,程序需要將兩者的位置進行確定)
      • c、重複上述過程,直到得到的父結點的權重爲1。
      • d、具體流程如下:
      i like you  //文檔
字符 頻率
i 0.2
l 0.1
k 0.1
e 0.1
y 0.1
o 0.1
u 0.1
空格 0.2

從根向下一次讀取0或者1,進行編碼,編碼結果如下表

字符 編碼
i 111
l 010
k 011
e 000
y 001
o 100
u 101
空格 110

二、編程實現過程

  • 1、首先我準備了一段英文文檔,包括26個字母和一個空格字符
for many young people they dont have the habit to save money because they think they are young and should enjoy the life quickly so there is no need to save money but saving part of the income can better help us to deal with emergent situations though it is hard to store income index zero we still can figure out some ways
  • 2、然後先進行文件的讀取和進行字符出現的次數統計
  //讀取文檔中的英文文檔
        String[] a = new String[800];
        try (FileReader reader = new FileReader("英文文檔");
             BufferedReader br = new BufferedReader(reader)
        ) {
            int b =0;
            for (int i =0;i<800;i++){
                a[b]=br.readLine();
                b++;
            }
            } catch (IOException e) {
            e.printStackTrace();
        }
        String[] b = a[0].split("");
    //    System.out.println(Arrays.toString(b));
//開始構造哈夫曼樹
          Objects Za= new Objects("a",an);
        Objects Zb = new Objects("b",bn);
        Objects Zc = new Objects("c",cn);
        Objects Zd = new Objects("d",dn);
        Objects Ze = new Objects("e",en);
        Objects Zf = new Objects("f",fn);
        Objects Zg = new Objects("g",gn);
        Objects Zh = new Objects("h",hn);
        Objects Zi = new Objects("i",in);
        Objects Zj = new Objects("j",jn);
        Objects Zk = new Objects("k",kn);
        Objects Zl = new Objects("l",ln);
        Objects Zm = new Objects("m",mn);
        Objects Zn = new Objects("n",nn);
        Objects Zo = new Objects("o",on);
        Objects Zp = new Objects("p",pn);
        Objects Zq = new Objects("q",qn);
        Objects Zr = new Objects("r",rn);
        Objects Zs = new Objects("s",sn);
        Objects Zt = new Objects("t",tn);
        Objects Zu = new Objects("u",un);
        Objects Zv = new Objects("v",vn);
        Objects Zw = new Objects("w",wn);
        Objects Zx = new Objects("x",xn);
        Objects Zy = new Objects("y",yn);
        Objects Zz = new Objects("z",zn);
        Objects Zkongge = new Objects(" ",zkongge);

        System.out.println("各個字符的概率統計爲:");
        Objects[] temp = new Objects[]{Za,Zb,Zc,Zd,Ze,Zf,Zg,Zh,Zi,Zj,Zk,Zl,Zm,Zn,Zo,Zp,Zq,Zr,Zs,Zt,Zu,Zv,Zw,Zx,Zy,Zz,Zkongge};
            for (int i =0;i<temp.length;i++){
                System.out.println(temp[i].getName()+"的概率爲"+temp[i].getWeight()/323);
            }
  • 3、因爲我用了一個Objects保存了一個元素的名字權重編碼,還有他的左孩子,右孩子
package 哈夫曼樹編碼實驗;

public class Objects implements Comparable<Objects> {
    private String name;
    private double weight;
    private String date;
    private Objects left;
    private Objects right;
    public Objects(String Name , double Weight){
        name=Name;
        weight=Weight;
        date="";
    }
    public String getName() {
        return name;
    }

    public double getWeight() {
        return weight;
    }

    public Objects getLeft() {
        return left;
    }

    public Objects getRight() {
        return right;
    }

    public void setLeft(Objects left) {
        this.left = left;
    }

    public void setRight(Objects right) {
        this.right = right;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Objects{" + "name='" + name + '\'' + ", weight=" + weight + ", 編碼爲='" + date + '\'' + '}'+"\n";
    }

    @Override
    public int compareTo(Objects o) {
        if (weight>=o.weight){
            return 1;
        }
        else {
            return -1;        //規定發現權重相等向後放;
        }
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getDate() {
        return date;
    }
}
  • 4、進行哈夫曼樹的建立
 List tempp = new ArrayList();
        for (int i =0;i<temp.length;i++){
            tempp.add(temp[i]);
        }
        Collections.sort(tempp);        //將我們的Objects類中的每一個字符放進鏈表進行排序


        while (tempp.size() > 1) {            //直到我們鏈表只剩下一個元素,也就是我們的根結點的時候跳出循環
            Collections.sort(tempp);            //排序
            Objects left = (Objects) tempp.get(0);        //得到第一個元素,作爲左孩子
            left.setDate( "0");    //初始化左孩子的編碼爲0
            Objects right = (Objects) tempp.get(1);    //得到第二個元素,作爲右孩子
            right.setDate( "1");    //初始化有孩子的編碼爲1
            Objects parent = new Objects(left.getName()+right.getName(), left.getWeight() + right.getWeight());    //構造父結點
            parent.setLeft(left);    //設置左結點
            parent.setRight(right);    //設置右結點
            tempp.remove(left);    //刪除左結點
            tempp.remove(right);    //刪除右結點
            tempp.add(parent);    //將父結點添加進入鏈表
            }
  • 5、開始進行編碼
     //開始進行哈夫曼編碼
        Objects root = (Objects) tempp.get(0);    //我們通過一個root保存爲根結點

        System.out.println( );        //我們利用先序遍歷,遍歷到每一個結點,因爲這樣可以保證都從根結點開始遍歷
        List list = new ArrayList();
        Queue queue = new ArrayDeque();
        queue.offer(root);
        while (!queue.isEmpty()){
            list.add(queue.peek());
            Objects temp1 = (Objects) queue.poll();

            if(temp1.getLeft() != null)
            {
                queue.offer(temp1.getLeft());
                temp1.getLeft().setDate(temp1.getDate()+"0");    //判斷假如爲左結點,就基於結點本身的編碼加上0
                }

            if(temp1.getLeft() != null)
            {
                queue.offer(temp1.getRight());
                temp1.getRight().setDate(temp1.getDate()+"1");    //判斷假如爲右結點,就基於結點本身的編碼加上1
            }
            }
  • 6、開始對於文檔進行加密並且保存進文檔
 //進行加密
        String result = "";            //定義了一個字符串,用來保存加密後的文檔
        for (int i =0 ;i<b.length;i++){
            for (int j=0;j<temp.length;j++){
                if (b[i].equals(temp[j].getName())){
                    result+=temp[j].getDate();        //因爲現在我們之前保存Objects的數組中的每一個字符已經有各自的編碼,所以我們用我們之前保存文檔的數組b進行對於,假如找到相對應的,就將編碼賦給result,進行累加,重複過程
                    break;                                            
                }

            }
        }
        System.out.println("加密後");
        System.out.println(result);
        File file = new File("加密後文檔");
        FileWriter fileWritter = null;
        try {
            fileWritter = new FileWriter(file.getName(),true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWritter.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWritter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 7、進行解密
  //解密,讀取需要解密的文檔
        try (FileReader reader = new FileReader("加密後文檔");
             BufferedReader br = new BufferedReader(reader)
        ) {
            int e =0;
            for (int i =0;i<800;i++){
                a[e]=br.readLine();
                e++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        String duqu=a[0];    
        String jiemi="";    //保存解密後的文檔
        String temp2="";   // 作爲一個臨時的字符串,一個一個進行獲取密文,當進行匹配成功了以後,變爲空,如下

        for (int i =0;i<duqu.length();i++){
            temp2+=duqu.charAt(i);
            for (int j = 0;j<temp.length;j++){            //這裏解密的思路就是我們從加密的文檔中一個一個字符進行獲取,然後與我們的之前建好的Objects數組中的元素的編碼
                if (temp2.equals(temp[j].getDate())){  //進行獲取,然後獲取成功以後將其賦給jiemi,然後清空temp2;
                    jiemi+=temp[j].getName();
                    temp2="";
                }
            }
        }
        System.out.println("解密後");
        System.out.println(jiemi);
        //將解密後的文本寫入文件

        File file1 = new File("解密後文檔");
        FileWriter fileWritter1 = null;
        try {
            fileWritter1 = new FileWriter(file1.getName(),true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWritter1.write(jiemi);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWritter1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 8、成功完成加密解密,結果如下:

實驗代碼鏈接

參考資料

java創建哈夫曼樹和實現哈夫曼編碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章