Java小程式之哈夫曼樹與檔案壓縮和解壓縮(一)哈夫曼樹構造篇
前言:在瞭解哈夫曼樹之前,我們還是先看下樹的相關知識吧。
一、資料結構中樹的相關知識 資料結構是 電腦 儲存、組織 資料 的方式。資料結構是指相互之間存在一種或多種特定關係的 資料元素 的集 合。通常情況下,精心選擇的資料結構可以帶來更高的運行或者儲存 效率 。資料結構往往同高效的檢索 演算法 和 索引 技術有關。資料結構主要包含集合、線性結構、樹行結構和圖行結構;
這次主要看下樹形結構:
1、樹的定義:樹:是由n(n>=1)個有限節點群組成一個具有層次關係的集合 2、樹的相關術語
節點度:一個節點含有子樹的個數稱為該節點的度
樹的度:一棵樹中,最大的節點的度為樹的度
葉子節點:度為零的節點
父親節點:若一個節點含有子節點,則當前節點為改子節點的父親節點
子節點:一個節點含有子樹,則子樹的根節點為改節點的子節點
兄弟節點:具有相同父親節點的子節點互為兄弟節點
節點層次:從根開始定義起,根為第1層,根的子節點為第2層,以此類推
樹的深度(高度):樹中節點最大的層次
其他:堂兄弟節點、子孫、森林:
3、樹的分類
無序樹:樹中的節點次序是沒有規律的
有序樹:指樹中同層結點從左至右有次序排列,這樣的樹稱為有序樹。
1)二叉樹:每個節點最多含有兩個子樹的樹稱為二叉樹
2)非二叉樹:所有不是二叉樹的樹都屬於非二叉樹 4、二叉樹 定義:二叉樹是一種特殊的樹形結構,每個節點至多隻有兩顆子樹,並且子樹有左右之分,其次序不能隨意顛倒,是有序樹的一種。
注意:二叉樹是由一個根結點、兩棵互不相交的左子樹和右子樹組成。
二叉樹分類
滿二叉樹:對於上述的完全二叉樹,如果去掉其第d層的所有節點,那麼剩下的部分就構成一個滿二叉樹
完全二叉樹:對於一顆二叉樹,假設其深度為d(d>1)。除了第d層外,其它各層的節點數目均已達最大值,且第d層所有節點從左向右連續地緊密排列,這樣的二叉樹被稱為完全二叉樹;
5、完全二叉樹性質
若根結點的層次為1,則二叉樹第i層最多有2的(i-1)次方個結點。
在高度為k的二叉樹中,則最多有2k-1個結點(k≥0)
設一棵二叉樹節點個數為n,則父節點個數為n/2。
一棵具有n個結點的完全二叉樹,對序號為i(0≤i<n)的結點,則:
若i=0,則i為根結點,無父母結點;若i >0,則i的左右子結點序號為:
若2i+1<n,則i的左孩子結點序號為2i+1;否則i無左孩子。
若2i+2<n,則i的右孩子結點序號為2i+2;否則i無右孩子。
6、哈夫曼樹 給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
有關哈夫曼樹的術語
路徑長度: 節點路徑長度:從一個節點(根節點)到另外一個節點所經過的路徑的分支數目 樹的路徑長度:指樹種所有節點的路徑長度之和
權值: 指的是所有葉子節點的 date 域,存放一個數值,用來表示當前葉子節點的資料,那這個值就是權值
帶權路徑長度: 所有葉子節點的帶權路徑長度之和
最優二叉樹(哈弗曼樹): 1 、通過所有權值節點,形成一個二叉樹,最終這個二叉樹的帶權路徑最小,則此二叉樹是哈夫曼樹(最優二叉樹) 2 、樹的結構不止一個,同一層的節點,可以左右替換
構造哈夫曼樹的思路:
1、建立一個節點類
2、把資料封裝成節點,放到容器中,用於建立一個森林
3、迴圈尋找權值最小的兩個節點,用於構造新的節點
需要定義一個方法,來尋找當前節點插入到森林中的位置
擷取哈夫曼編碼(用於後面檔案的壓縮與解壓縮)
二、二叉樹的構造原始碼:
節點類:
package MyTree;public class Node {//節點類、public int data;public Node left;public Node right;}
二叉樹的構造過程以及遍曆:
package MyTree;import java.util.ArrayList;public class TreeList {public int [] values ={1,2,3,4,5,6,7,8,9};public ArrayList<Node> list = new ArrayList<Node>();public static void main(String[] args) {TreeList tree = new TreeList();Node root=tree.createTree();System.out.println("前序走訪:");tree.lookTree1(root);System.out.println("中序遍曆:");tree.lookTree2(root);System.out.println("後序遍曆:");tree.lookTree3(root);}public Node createTree(){//封裝成節點for (int i = 0; i < values.length; i++) {Node node = new Node();node.data=values[i];list.add(node);}//構造二叉樹for (int i = 0; i < list.size()/2-1; i++) {Node node =list.get(i);node.left=list.get(2*i+1);node.right=list.get(2*i+2);}//構造最後一個父節點Node lastNode = list.get(list.size()/2-1);lastNode.left=list.get((list.size()/2-1)*2+1);if(list.size()%2==1){lastNode.right=list.get((list.size()/2-1)*2+2);}return list.get(0);}//前序走訪public void lookTree1(Node root){System.out.print(root.data+" ");if(root.left!=null){lookTree1(root.left);}if(root.right!=null){lookTree1(root.right);}}//中序遍曆public void lookTree2(Node root){if(root.left!=null){lookTree2(root.left);}System.out.print(root.data+" ");if(root.right!=null){lookTree2(root.right);}}//後續遍曆public void lookTree3(Node root){if(root.left!=null){lookTree3(root.left);}if(root.right!=null){lookTree3(root.right);}System.out.print(root.data+" ");}}
三、構造哈夫曼樹的原始碼: 哈夫曼節點類:
package com.bluesky.huffm;public class HuffmNode {//構造HuffmNode類private int data;private HuffmNode left;private HuffmNode right;//HuffmNode類建構函式public HuffmNode(int data) {this.data=data;}//封裝屬性public int getData() {return data;}public void setData(int data) {this.data = data;}public HuffmNode getLeft() {return left;}public void setLeft(HuffmNode left) {this.left = left;}public HuffmNode getRight() {return right;}public void setRight(HuffmNode right) {this.right = right;}}
哈夫曼樹的構造:
package com.bluesky.huffm;import java.util.LinkedList;public class HuffmTree {public int [] datas ={2,1,6,4,7,9,12,3};public LinkedList<HuffmNode> list = new LinkedList<HuffmNode>();public HuffmNode createTree() {//按照從小到大的將資料封裝成節點for (int i = 0; i < datas.length; i++) {HuffmNode node = new HuffmNode(datas[i]);//得到需要插入的位置索引int index=getIndex(node);//將資料添加到容器中list.add(index, node);}//構造哈夫曼樹while(list.size()>1){//移除容器中的第一個節點HuffmNode firstNode =list.removeFirst();//容器中原來的第二個節點變成新的第一個節點HuffmNode secondNode =list.removeFirst();//構造父節點資料域HuffmNode fatherNode = new HuffmNode(firstNode.getData()+secondNode.getData());//構造父節點左子葉fatherNode.setLeft(firstNode);//構造父節點右子葉fatherNode.setRight(secondNode);//得到構造好的父節點的索引int index=getIndex(fatherNode);//將父節點加入森林list.add(index, fatherNode);}//返回根節點return list.getFirst();}//得到索引public int getIndex(HuffmNode node) {for (int i = 0; i < list.size(); i++) {if(node.getData()>list.get(i).getData()){continue;}else {return i;}}//如果比容器中的任何一個數大,則插入最後面return list.size();}//得到哈夫曼編碼public void getHuffmCode(HuffmNode root,String code) {if(root.getLeft()!=null){getHuffmCode(root.getLeft(),code+"0");}if(root.getRight()!=null){getHuffmCode(root.getRight(),code+"1");}if(root.getLeft()==null && root.getRight()==null){System.out.println(code);}}}
程式入口:
package com.bluesky.huffm;public class Test {public static void main(String[] args) {HuffmTree tree = new HuffmTree();HuffmNode root = tree.createTree();tree.getHuffmCode(root, "");}}
四、總結: 樹是屬於資料結構方面的知識,以前自己在學資料結構的時候,就被老師要求自己去構造一些諸如鏈表,隊列等資料結構,並且要能夠實現這些資料結構的增刪查改等功能。鏈表和隊列還算簡單的了,樹和圖就真的有點蒙了,其實資料結構這門課程還是挺重要的了,這不,我們馬上就要看到哈夫曼樹的用處了;樹的遞迴遍曆真的有點難理解,都不知道遞迴到哪裡去了;一直糾結就會陷入死迴圈,有時也不要那麼糾結,你遞迴下去,腦袋立馬蒙了,當然,有些邏輯非常清楚的人就不會,只能說,我還需要曆練。 哈夫曼樹,也稱最優二叉樹,是樹型結構中比較重要的知識,以前只知道它大有用處,卻不知到它該怎麼用,現在算是見證到哈夫曼樹的威力了。構造哈弗曼樹的過程中,需要注意,移除子節點後,第二位元組點就變成了第一個節點,這裡需要注意下。 共勉。