一、前言:
这是怀化学院的:Java数据结构中的一道难度偏难(偏难理解)的一道编程题(此方法为博主自己研究与学习一名叫qing影的博主,问题基本解决,若有bug欢迎下方评论提出意见,我会第一时间改进代码,谢谢!) 后面其他编程题只要我写完,并成功实现,会陆续更新,记得三连哈哈! 所有答案供参考,不是标准答案,是博主自己研究的写法。(这一个题书上也有现成类似的代码,重要的是理解它的算法原理!)
二、题目要求如下:
(第 9 题) 哈夫曼编码大全(难度系数100)
题目: 哈夫曼编码大全
描述:
关于哈夫曼树的建立,编码,解码。
输入
第一行输入数字N,代表总共有多少个字符以及权值
第二第三行分别是一行字符串,以及每个字符对应的权值
接下来输入一个数M,表示接下来有M行字符串,要求你对每个字符串进行编码
再输入一个数X,表示接下来有X行编码,要求你对每行编码进行解码
输出
第一行输出所有节点的权重
接下来输出N行,每行以 “a:001”的格式输出每个字符对应的编码
接着输出M行,对输入的字符串的编码结果
最后,输出X行的解码结果
输入样例
6
abcdef
50 10 5 5 20 10
2
abcdef
defabaabbc
2
011001100100110110101101100
1100011000110101100101100输出样例
50 10 5 5 20 10 10 20 30 50 100
a:0
b:100
c:1100
d:1101
e:111
f:101
010011001101111101
11011111010100001001001100
accbdfadb
cacadacfb
三、代码实现: (代码的做题原理全部在代码注释中)
<1>因为学校的提交测试的网站:不能有自己创建的包的声明,不能有代码注释以及要把所有的操作放在同一个类中等等......,所以我首先放一个干净的代码实现:(此题提交成功!),有注释的代码在下方!!!!!!
<但是这次我向学校网站提交时没有删除注释,能提交成功......>
import java.util.Scanner;
public class Main {
public static class Node{
private int data;
private int lChild;
private int rChild;
private int parent;
public Node(){
this.data=0;
this.lChild=0;
this.rChild=0;
this.parent=0;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
String node01 = sc.next();
Node[] node = new Node[(2*N)-1];
for(int i=0;i<N;i++) {
node[i] = new Node();
node[i].data = sc.nextInt();
}
for(int j=0;j<N-1;j++){
int low01=-1;
int low02=-1;
for(int k=0;k<j+N;k++){
if(node[k].parent==0&&(low01==-1||node[k].data<node[low01].data)){
low02=low01;
low01=k;
}else if(node[k].parent==0&&(low02==-1||node[k].data<node[low02].data)){
low02=k;
}
}
node[N+j]=new Node();
node[N+j].data=node[low01].data+node[low02].data;
node[N+j].lChild=low01;
node[N+j].rChild=low02;
node[low01].parent=node[low02].parent=N+j;
}
for(int i=0;i<2*N-1;i++){
System.out.print(node[i].data+" ");
}
System.out.println();
String[] code = new String[N];
for(int i=0;i<N;i++){
StringBuilder str01 = new StringBuilder();
int Child=i;
int Parent= node[i].parent;
while(Parent!=0){
if(node[Parent].lChild==Child){
str01.append('0');
}
else if(node[Parent].rChild==Child) {
str01.append('1');
}
Child=Parent;
Parent=node[Parent].parent;
}
str01.reverse();
code[i] = str01.toString();
System.out.println(node01.charAt(i)+":"+code[i]);
}
int M=sc.nextInt();
for(int j=0;j<M;j++){
String Node_test=sc.next();
int len =Node_test.length();
for(int k=0;k<len;k++){
int index = node01.indexOf(Node_test.charAt(k));
System.out.print(code[index]);
}
System.out.println();
}
int X = sc.nextInt();
for(int k=0;k<X;k++){
String Code_test=sc.next();
int present=0;
int len =Code_test.length();
while(present<len) {
for (int h = 0; h < N; h++) {
int index = Code_test.indexOf(code[h], present);
if (index == present) {
present=present+code[h].length();
System.out.print(node01.charAt(h));
break;
}
}
}
System.out.println();
}
}
}
补充:题目意思一定要深度揣摩一下,没有提示就得自己根据它题目给的输入输出来推一下原理了,不然就是盲目下手出错很多,大家一定要先去我之前那篇《构造哈夫曼树》去参考一下原理,再来攻克这道大题,(书上也提到了<使用哈夫曼树的算法求报文字符编码,以及根据编码去解码>)
(1)我把所有的实现细节包括哈夫曼树的创建、字符的编码,以及解码操作等等都放在一个类中。
import java.util.Scanner;
public class Main {
//静态结点内部类,不然主方法用不了
public static class Node{
private int data; //代表当前的哈夫曼树总权值
private int lChild; //左孩子
private int rChild; //右孩子
private int parent; //父结点
public Node(){
this.data=0;
//刚开始所有的叶子结点默认都没有孩子结点和父亲结点
this.lChild=0;
this.rChild=0;
this.parent=0;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
String node01 = sc.next(); //代表N个最开始的叶子结点的字符名称
//先创建一个结点数组来存储:最终构成的哈夫曼树的所有结点
//因为叶子结点个数为N,则对于非空二叉树来说:拥有2度的结点的个数=叶子结点个数减一,则总结点数=N-1+N=2*N-1
Node[] node = new Node[(2*N)-1];
for(int i=0;i<N;i++) {
node[i] = new Node(); //依次创建新结点,并先把最初的叶子结点放入
node[i].data = sc.nextInt();
}
//接下来就是开始构造哈夫曼树了
//控制最大只能到(2*N-1)-1下标
for(int j=0;j<N-1;j++){
//默认最小结点的下标
int low01=-1;
//默认次小结点的下标
int low02=-1;
//因为当两个较小的结点生成一个父结点时,那个父节点也可以被当作一个孩子结点与另外一个较小的没有父结点的结点生成一个新的哈夫曼树
for(int k=0;k<j+N;k++){
//首先条件是无父亲结点,再依次去找一个最小权值的下标
if(node[k].parent==0&&(low01==-1||node[k].data<node[low01].data)){
low02=low01; //如果找到了就先把当前"较小"值的下标给"次小"
low01=k; //然后再把之前"最小"小标更新成真正的最小值的下标
}else if(node[k].parent==0&&(low02==-1||node[k].data<node[low02].data)){
//最小下标条件不满足,就给次小下标赋值,再依次遍历直到合成完所有的结点变成一个最终的哈夫曼树
low02=k;
}
}
//每次找到当前最小和次小下标的值时就要创建一个新的父结点
node[N+j]=new Node(); //每次创建出的新的父结点都要登记进数组去
node[N+j].data=node[low01].data+node[low02].data; //记得给新结点赋权值
//它对应的孩子结点是,左边为最小的,右边为次小的
node[N+j].lChild=low01;
node[N+j].rChild=low02;
//父结点
node[low01].parent=node[low02].parent=N+j; //在原有的结点个数的位置上加1
}
//输出当前哈夫曼树的所有结点的权重
for(int i=0;i<2*N-1;i++){
System.out.print(node[i].data+" ");
}
System.out.println();
//接下来进行对刚刚输入的字符串中的每个字符进行编码
String[] code = new String[N]; //用来储存对应字符的编码值,之前有N个字符嘛
for(int i=0;i<N;i++){
//这里用到StringBuilder类,它不保证同步,因为它的长度是可变的(String类的字符串长度不可变),所以用这个方便增删操作
StringBuilder str01 = new StringBuilder();
//我们要假设每棵左子树的根节点的边为"0",而每棵右子树的根节点的边为"1",我们从最开始输入的根节点"a"向上到最终的根结点为止再遍历其他的前N个根节点
int Child=i; //这个意思就是当前要编码的字符
int Parent= node[i].parent; //就是向上找父结点,直到找到最上面的根结点为止
while(Parent!=0){ //只要有父结点,就一直向上
if(node[Parent].lChild==Child){ //这个意思就是当前要编码的字符的父结点,它到当前要编码的字符是通过左子树边连接还是右子树边连接
str01.append('0');
}
else if(node[Parent].rChild==Child) {
str01.append('1');
}
//每次编码成功一位就要继续向上,就要更新
Child=Parent; //之前的孩子结点变成当前的父节点
Parent=node[Parent].parent; //上一层的父节点要重新赋值
}
str01.reverse(); //因为编码是从下往上的,所以最后记得反过来
code[i] = str01.toString(); //返回其String类型的字符串表达形式,并存入code数组中
//从第一个加入的字符开始输出每个字符的最终编码
System.out.println(node01.charAt(i)+":"+code[i]);
}
//上面已经得到所有输入的字符的各自的编码,现在就可以对输入的测试字符串进行编码
int M=sc.nextInt(); //测试的字符串个数
for(int j=0;j<M;j++){
//这是每次需要编码的字符串
String Node_test=sc.next();
int len =Node_test.length();
for(int k=0;k<len;k++){
//这样写的妙处在于,每次找到对应字符串中某个字符第一次出现的位置,它对应的下标也是该字符对应的编码值储存位置的下标
int index = node01.indexOf(Node_test.charAt(k));
System.out.print(code[index]); //依次输出就行
}
System.out.println(); //每全部编码一次记得换行
}
//现在进行解码,就是编码匹配正确的输出对应的字符
int X = sc.nextInt(); //需要解码的总次数
for(int k=0;k<X;k++){
String Code_test=sc.next(); //需要解码的字符串
//这个地方用到一个很妙的匹配方法:indexOf(String str, int fromIndex) 返回指定子串的第一次出现的字符串中的索引,从指定的索引开始。
int present=0; //要用一个变量来记录:当前遍历到需要解码的字符串的哪一个位置了
int len =Code_test.length();
while(present<len) {
for (int h = 0; h < N; h++) { //最大只能到N个
//就是从需要解码的字符串中的0索引下标开始向后找,与每个字符对应的编码相等的第一个位置
int index = Code_test.indexOf(code[h], present);
//如果第一个位置就是从0开始,那么直接把下次寻找的开始点更新
if (index == present) {
present=present+code[h].length(); //如果找到一个就要把记录当前到哪的下标更新,以便下次从这开始继续向后解码
//并且找到了相匹配就要输出该字符串编码对应的字符,因为前面对应的是code[h],则其编码值对应的字符也是h位置
System.out.print(node01.charAt(h));
break; //退出当前循环,去进行下一次后面的寻找,直到下标不满足需要解码的字符串长度为止
}
}
}
System.out.println(); //每解码完成功一个字符串就要换行
}
}
}
四、不同情况的代码测试运行结果:
<1>题目中的测试输入样例:(最好手打输入测试,直接复制可能格式问题导致报错!)