任务1:对已知字符及其权值(频率)创建哈夫曼树并可以打印出每个字符的哈夫曼编码值
任务2:从控制台输入一个字符串,计算出字符的权值,转换为任务一
实现过程:
在哈夫曼树中,所有字符节点都作为叶节点,具有字符内容和频率的属性;将字符节点合成树后,具有左右节点的属性。
所以可以创建出Node节点类:
class Node{
Node left;//根节点
Node right;//根节点
String str;//此节点的字符
int weight;//权值或频率
}
构造哈夫曼树:
1.将节点按照权值排序(从小到大或从大到小都行),将排序后结果存储到(队列、数组)
2.从该节点(排好序的)序列中,选择权值最小的两个节点,将其合成一个子树(子树的权值之和为两个节点的权值之和)
3.将这子树看成新的节点重复1、2步骤直到所有节点合成一个树
排序算法
//排序从小到大
public void sort(Node[] nodes) {
Node temp = new Node();
for(int i=0;i<nodes.length;i++) {
for(int j=i+1;j<nodes.length;j++) {
if(nodes[i].weight>nodes[j].weight) {
temp = nodes[j];
nodes[j] = nodes[i];
nodes[i] = temp;
}
}
}
}
用冒泡排序每一轮选出具有最小的权值的节点
根据已知序列权值建树,返回根节点
String[ ] strs 是序列字符数组, int[ ] weights 是字符权值数组,此时是指一一对应的,strs[ i ] 对应的权值是 weights[ i ].
public Node createHFM(String[] strs,int[] weights ) {
//转换成Node数组
Node [] nodes = new Node[strs.length];
for(int i = 0;i<strs.length;i++) {
nodes[i] = new Node();
nodes[i].weight=weights[i];
nodes[i].str=strs[i];
}
while(nodes.length>1) {
//排序
sort(nodes);
Node n1 = nodes[0];
Node n2 = nodes[1];
Node node = new Node();
node.left = n1;
node.right = n2;
node.weight = n1.weight+n2.weight;
//把n1和n2从数组中删除,把node加进去
Node[] nodes2 = new Node[nodes.length-1];
for(int i=2;i<nodes.length;i++) {
nodes2[i-2]=nodes[i];
}
nodes2[nodes2.length-1]=node;
nodes = nodes2;
}
Node root = nodes[0];
return root;
}
while 循环中,对nodes数组进行排序,结果是节点按权值从小到大顺序存储在nodes中。合成一个子树需要三个节点,其中node作为根节点,从nodes数组中选择最小权值的两个节点分别作为左子节点、右子节点,显而易见就是nodes数组的前两个节点。
合成子树后,需要一个新的数组来存放这些节点并排序,显然数组的长度减少了一个,将之前的nodes数组中剩下的节点复制到新数组中,将合成的子树添加在后面,让nodes指向新数组nodes2。
循环一直进行下去,新数组的长度会越来越少,直到最后只有一个节点就是所有节点都合成为树的时候。所以循环条件是nodes.length>1。
这时,一个哈夫曼树就完成了,根据枝干左0右1,遍历树的时候就可以输出字符的哈夫曼编码值。
打印算法
public void printHFMcode(Node root,String code) {
if(root!=null) {
//先序遍历
if(root.left==null&&root.right==null) {
String msg = root.str+"权值"+root.weight+" HFM编码:"+code;
System.out.println(msg);
}
printHFMcode(root.left,code+"0");
printHFMcode(root.right,code+"1");
}
}
任务1举例:
public static void main(String[] args) {
HfmTree hfm = new HfmTree();
String [] strs = {"A","B","C","D","E","F"};
int[] weights = { 4 , 6 , 1 , 9 , 8 , 2 };
Node root = hfm.createHFM(strs, weights);
hfm.printHFMcode(root, "");
}
任务2中需要的新算法:
//根据已知字符串序列创建HashMap
public Map<String,Integer> CreateHashMap(String str){
Map<String, Integer> hm = new HashMap<String, Integer>();
for(int i=0;i<str.length();i++) {
char s = str.charAt(i);//取出i位置上的字符
String key = String.valueOf(s);//将字符类型转换为字符串类型
if(hm.get(key)==null) {
//map中没有字符串key,则添加字符串key进去并设置频率(value)为1
hm.put(key, 1);
}else {
//map中已存在字符串key,则将其频率(value)增加1
hm.put(key, hm.get(key)+1);
}
}
return hm;
}
//根据HashMap得到字符串数组
public String[] StrfromHash(Map<String,Integer> map) {
int i=0;
String skey[] = new String[map.size()];
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()) {
skey[i++]=it.next();
}
return skey;
}
//根据HashMap得到权值数组
public int[] weightfromHash(Map<String,Integer> map) {
int i=0;
int [] weight = new int[map.size()];
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()) {
String s = it.next();
weight[i++]=map.get(s);
}
return weight;
}
在这里需要使用到HashMap,通过键值对可以存储字符与权值一一对应的对象,key–value。
根据HashMap获得字符串数组和权值数组,就可以套入任务1中的方法中了。
得到字符串数组需要使用HashMap的keySet()方法,该方法是将HashMap中的所有key对象放在Set中返回,这个集合是无序的(元素存储的位置顺序由哈希码来确定),元素是唯一不重复的。因此要获取其中元素,需要使用迭代器,Iterator.
hasNext方法是来判断迭代器中是否有元素,有则返回true
next是返回下一个元素。
skey[i++] = it.next();可以将所有的元素都存到是skey数组中。
而得到权值数组则有一点不同,因为要做到一一对应,所以在while循环中要使用到HashMap的get()方法,将对应于当前元素的value值取出,才能存入weight数组中。
任务2举例:
System.out.println("输入字符串:");
Scanner sc = new Scanner(System.in);
String string = sc.next();
Map<String,Integer> map = hfm.CreateHashMap(string);
String str[] = hfm.StrfromHash(map);
int weight[] =hfm.weightfromHash(map);
System.out.println("打印出的字符及其权值和哈希值为:");
for(int i=0;i<map.size();i++) {
System.out.println(str[i]+"--"+weight[i]+"--"+str[i].hashCode());
}