时间限制: 1 Sec 内存限制: 32 MB
提交: 96 解决: 40
[提交][状态][讨论版][命题人:外部导入]
在通讯领域,经常需要将需要传送的文字转换成由二进制字符组成的字符串。在实际应用中,由于总是希望被传送的内容总长尽可能的短,如果对每个字符设计长度不等的编码,且让内容中出现次数较多的字符采用尽可能短的编码,则整个内容的总长便可以减少。另外,需要保证任何一个字符的编码都不是另一个字符的编码前缀,这种编码成为前缀编码。
而赫夫曼编码就是一种二进制前缀编码,其从叶子到根(自底向上)逆向求出每个字符的算法可以表示如下:
在本题中,读入n个字符所对应的权值,生成赫夫曼编码,并依次输出计算出的每一个赫夫曼编码。
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
共n行,每行一个字符串,表示对应字符的赫夫曼编码。
8
5 29 7 8 14 23 3 11
0110
10
1110
1111
110
00
0111
010
赫夫曼树又名最优二叉树,它是一类带权路径长度最小的二叉树。通过构造赫夫曼树,我们可以得到赫夫曼编码,从而使得通信能够得到更高的效率。在本题中,构造赫夫曼树的过程使用了从叶子到根的逆向顺序,另外,还有一种从根出发直到叶子的赫夫曼编码构造算法,这将在下一题中进行讨论。
方法一:纯c++风格的HuffManCode
#include
#include
#include
using namespace std;
const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码 顺序就是元素输入权值的顺序(不是权值排序后的顺序)
//线性结构存储
struct huffManNode{
int weight;
int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点 parent为0表示无父结点
//(HMNode[]静态数组的)1~i号结点里 找根结点 找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
int minw=INT_MAX;
//找最小s1
for(int i=1;i<=index;i++){
if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}
//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根)
//构造依据是int w[] 和 n
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
if(n<1) return;//一个结点 随便编码
int m=2*n-1;//Huffman树总结点数
//初始化原始n个叶子结点
for(int i=1;i<=n;i++){
HMNode[i].weight=w[i];
HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
}
//并不需要初始化 将要分配的m-n个结点
//开始正式构建Huffman二叉树
int s1,s2;
for(int i=n+1;i<=m;i++){
Min(i-1,s1,s2);
HMNode[i].lchild=s1;
HMNode[i].rchild=s2;
HMNode[i].parent=0;
HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
HMNode[s1].parent=HMNode[s2].parent=i;
}
//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)
//求HuffMan编码 自底向上求
//前提 已知静态数组中前n个一定初始的叶子结点
char* temp=new char[n];
int start;
for(int i=1;i<=n;i++){
start=n;temp[--start]='\0'; //f==0到根了
for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
if(HMNode[f].lchild==j) temp[--start]='0';//左0
else temp[--start]='1';//右1
j=f;
}
code[i]=temp+start;
}
delete temp;
}
//本程序 0号结点统一不用
int main(){
// freopen("inputa.txt","r",stdin);
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>w[i];
}
HuffManEncoding();
for(int i=1;i<=n;i++){
cout<
精简版:
#include
#include
using namespace std;
const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码 顺序就是元素输入权值的顺序(不是权值排序后的顺序)
struct huffManNode{
int weight,lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
void Min(int index,int &s1,int &s2){//查找范围[1,index]
int minw=INT_MAX;
for(int i=1;i<=index;i++){
if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}
void HuffManEncoding(){
if(n<1) return;//一个结点 随便编码
int m=2*n-1;//Huffman树总结点数
for(int i=1;i<=n;i++){
HMNode[i].weight=w[i];
HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
}
int s1,s2;
for(int i=n+1;i<=m;i++){
Min(i-1,s1,s2);
HMNode[i].lchild=s1;HMNode[i].rchild=s2;
HMNode[i].parent=0;HMNode[s1].parent=HMNode[s2].parent=i;
HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
}
char* temp=new char[n];
int start;
for(int i=1;i<=n;i++){
start=n;temp[--start]='\0'; //f==0到根了
for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
if(HMNode[f].lchild==j) temp[--start]='0';//左0
else temp[--start]='1';//右1
j=f;
}
code[i]=temp+start;
}
delete temp;
}
int main(){
while(cin>>n){
for(int i=1;i<=n;i++)cin>>w[i];
HuffManEncoding();
for(int i=1;i<=n;i++) cout<
数据结构纯c版
#include
#include
#include
#include
#include
using namespace std;
typedef struct {
unsigned int weight;
unsigned int parent, rchild, lchild;
}HTNode, *HuffmanTree;// 动态分配数组存储赫夫曼树
typedef char** HuffmanCode;// 动态分配数组存储赫夫曼编码表
int min(HuffmanTree t, int i) {
// 返回i个结点中权值最小的树的根结点序号,函数select()调用
int j, flag;
unsigned int k = UINT_MAX;// 取k为不小于可能的值(无符号整型最大值)
for (j = 1; j <= i; j++) {//不能重定义int j=1;屏蔽了外面的j
if (t[j].weight < k&&t[j].parent == 0) // t[j]是树的根结点
k = t[j].weight, flag = j;
//int m=t[j].weight;int x=t[j].parent;
}
t[flag].parent = 1; // 给选中的根结点的双亲赋1,避免第2次查找该结点
return flag;
}
void select(HuffmanTree t, int i, int &s1, int &s2)
{ // 在i个结点中选择2个权值最小的树的根结点序号, s1为其中序号小的那个
int j;
s1 = min(t, i);
s2 = min(t, i);
if (s1>s2)
{
j = s1;
s1 = s2;
s2 = j;
}
}
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n) {
//w存放n个结点的权值,(最后的叶子结点,且均大于0),
//构造huffman树HT,并求出n个字符的Huffman编码HC
int m, i, s1, s2, start;
unsigned c, f;
HuffmanTree p;
char* cd;//存字符串(每个字符的Huffman编码)
if (n <= 1)
return;
m = 2 * n - 1;//二叉树结点数=2*叶子节点数-1
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//0号单元未用
//初始化Huffman树 都是根结点 也都是叶子结点
for (p = HT + 1, i = 1;i <= n;++i, ++p, ++w) {
p->weight = *w;
p->parent = 0;
p->lchild = 0;
p->rchild = 0;//千万不要写成HT->rchild 应是(HT+1)->rchild
}
//上面只初始化了n个叶子结点 实际上最终和Huffman有m=2*n-1个结点 下面先对未初始化的m-n个结点初始化下
for (;i <= m;i++, p++) {
(*p).parent = 0;//后面遍历时要用到parent是否为0
(*p).lchild = 0;
(*p).rchild = 0;
(*p).weight = 0;
}
//建Huffman树
for (i = n + 1;i <= m;i++) {
//每次执行 前i-1个是已经生成的结点 在已经生成的结点中找权值最小的根结点
// 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2
select(HT, i - 1,s1,s2);
HT[s1].parent = HT[s2].parent = i;//两个最小权值的根结点组成一个新的根结点
HT[i].lchild = s1, HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
// 从叶子到根逆向求每个字符的赫夫曼编码
/*
注意char*:
1.HuffmanCode是二级指针所以单个元素值为char*
2.Huffman数组HC[1~n]存n个字符串,n个Huffman码
*/
HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));//[0]不用
/*
先用字符指针cd暂存好单个Huffman码,然后一一存进HC数组中
不定义字符数组了,动态分配,节省空间
*/
cd = (char*)malloc(n*sizeof(char));
cd[n - 1] = '\0';//c语言字符串必备结束符
for (i = 1;i <= n;i++) {// 逐个字符求赫夫曼编码
start = n - 1;//编码结束位置,倒着求并赋进编码
for (c = i, f = HT[i].parent;f != 0;c = f, f = HT[f].parent) {// 从叶子到根逆向求编码
if (HT[f].lchild == c)//c不能写成i,仅第一次不错,后来更新的是c,i值一直未变
cd[--start] = '0';
else if (HT[f].rchild == c)
cd[--start] = '1';//字符‘0’,不是数字0
HC[i] = (char*)malloc((n - start) * sizeof(char));//分配恰好存Huffman码的空间
strcpy(HC[i], &cd[start]);//从cd赋值有效编码串到HC[i]
}
}
free(cd);//循环结束,动态申请的cd该释放了
}
int main() {
HuffmanCode HC;
HuffmanTree HT;
int n;
while(cin >> n){
int* w = (int*)malloc(n*sizeof(int));
for (int i = 0;i < n;i++) {
cin >> w[i];
}
HuffmanCoding(HT,HC,w,n);
for (int i = 1;i <= n;i++) {//i又从0开始遍历了,找了好久才找出错
puts(HC[i]);
}
free(w);
}
return 0;
}
个人比较喜欢第一种写法
时间限制: 1 Sec 内存限制: 32 MB
提交: 56 解决: 40
[提交][状态][讨论版][命题人:外部导入]
在本题中,我们将要讨论的是自顶向下的赫夫曼编码算法。从根出发,遍历整棵赫夫曼树从而求得各个叶子结点所表示的字符串。算法的关键部分可以表示如下:
在本题中,读入n个字符所对应的权值,生成赫夫曼编码,并依次输出计算出的每一个赫夫曼编码。
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
共n行,每行一个字符串,表示对应字符的赫夫曼编码。
8
5 29 7 8 14 23 3 11
0110
10
1110
1111
110
00
0111
010
在本题中,与上一题不同的是在求赫夫曼编码的过程中,使用了从根出发开始遍历整棵赫夫曼树的自顶向下的算法。通过这两道题目的联系,应该能够熟练的掌握赫夫曼树和赫夫曼编码的构造和使用方法了。
本题与上一题为一区别在于求赫夫曼编码方向变了,既然要自顶向下,很自然想到深搜,一个DFS解决了
仅在上题基础上对75~80行做了修改 用DFS实现求编码
#include
#include
#include
using namespace std;
const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码 顺序就是元素输入权值的顺序(不是权值排序后的顺序)
//线性结构存储
struct huffManNode{
int weight;
int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点 parent为0表示无父结点
//(HMNode[]静态数组的)1~i号结点里 找根结点 找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
int minw=INT_MAX;
//找最小s1
for(int i=1;i<=index;i++){
if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}
void DFS(int i,string temp){
if(HMNode[i].lchild==0){//竟然写成这样 MNode[i].lchild==HMNode[i].rchild==0 蠢死了
code[i]=temp;//注意该编码赋值到该节点对应下标处 否则哪个编码对应哪个字符就乱了
return;//不要忘了
}
DFS(HMNode[i].lchild,temp+"0");
DFS(HMNode[i].rchild,temp+"1");
}
//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根)
//构造依据是int w[] 和 n
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
if(n<1) return;//一个结点 随便编码
int m=2*n-1;//Huffman树总结点数
//初始化原始n个叶子结点
for(int i=1;i<=n;i++){
HMNode[i].weight=w[i];
HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
}
//开始正式构建Huffman二叉树
int s1,s2;
for(int i=n+1;i<=m;i++){
Min(i-1,s1,s2);
HMNode[i].lchild=s1;
HMNode[i].rchild=s2;
HMNode[i].parent=0;
HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
HMNode[s1].parent=HMNode[s2].parent=i;
}
//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)
//与A题相比 仅以下代码不同
//求HuffMan编码 自顶向下
//由前面代码逻辑可知 最后一个结点即:m号结点为根结点 则从根开始先序遍历 DFS
//由于要用到递归,则新建一个函数
string temp="";
DFS(m,temp);
}
//本程序 0号结点统一不用
int main(){
// freopen("inputb.txt","r",stdin);
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>w[i];
}
HuffManEncoding();
for(int i=1;i<=n;i++){
cout<
时间限制: 1 Sec 内存限制: 32 MB
提交: 162 解决: 97
[提交][状态][讨论版][命题人:外部导入]
哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。
输入有多组数据。
每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。
输出权值。
2 2 8 3 5 11 30
10 62
本题可以转化为合并果子问题,来简化逻辑,不需要构建HuffMan树了
#include
#include
using namespace std;
int main(){
// freopen("inputc.txt","r",stdin);
int n,t,x,y,ans;
priority_queue,greater > q;
while(cin>>n){
ans=0;
for(int i=0;i>t;
q.push(t);
}
while(q.size()>1){
x=q.top();q.pop();
y=q.top();q.pop();
q.push(x+y);
ans+=(x+y);
}
q.pop();
cout<
时间限制: 1 Sec 内存限制: 128 MB
提交: 62 解决: 48
[提交][状态][讨论版][命题人:外部导入]
哈弗曼编码大家一定很熟悉吧(不熟悉也没关系,自己查去。。。)。现在给你一串字符以及它们所对应的权值,让你构造哈弗曼树,从而确定每个字符的哈弗曼编码。当然,这里有一些小规定:
1.规定哈弗曼树的左子树编码为0,右子树编码为1;
2.若两个字符权值相同,则ASCII码值小的字符为左孩子,大的为右孩子;
3.创建的新节点所代表的字符与它的做孩子的字符相同;
4.所有字符为ASCII码表上32-96之间的字符(即“ ”到“`”之间的字符)。
输入包含多组数据(不超过100组)
每组数据第一行一个整数n,表示字符个数。接下来n行,每行有一个字符ch和一个整数weight,表示字符ch所对应的权值,中间用空格隔开。
输入数据保证每组测试数据的字符不会重复。
对于每组测试数据,按照输入顺序输出相应的字符以及它们的哈弗曼编码结果,具体格式见样例。
3 a 10 b 5 c 8 4 a 1 b 1 c 1 d 1
a:0 b:10 c:11 a:00 b:01 c:10 d:11
改一下Min的逻辑和新建结点的逻辑即可
改了8 40 65 91行
#include
#include
#include
using namespace std;
const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
char ch[maxn];
string code[maxn];//存放编码 顺序就是元素输入权值的顺序(不是权值排序后的顺序)
//线性结构存储
struct huffManNode{
int weight;
int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点 parent为0表示无父结点
//(HMNode[]静态数组的)1~i号结点里 找根结点 找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
int minw=INT_MAX;
//找最小s1
for(int i=1;i<=index;i++){
if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要 (eg:两个权值一样大如何?)
if(ch[s1]>ch[s2]) swap(s1,s2);//改 ascii小的在前 而不是序号小的在前了
}
//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根)
//构造依据是int w[] 和 n
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
if(n<1) return;//一个结点 随便编码
int m=2*n-1;//Huffman树总结点数
//初始化原始n个叶子结点
for(int i=1;i<=n;i++){
HMNode[i].weight=w[i];
HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
}
//开始正式构建Huffman二叉树
int s1,s2;
for(int i=n+1;i<=m;i++){
Min(i-1,s1,s2);
HMNode[i].lchild=s1;
HMNode[i].rchild=s2;
HMNode[i].parent=0;
HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
HMNode[s1].parent=HMNode[s2].parent=i;
ch[i]=ch[s1];
}
//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)
//求HuffMan编码 自底向上求
//前提 已知静态数组中前n个一定初始的叶子结点
char* temp=new char[n];
int start;
for(int i=1;i<=n;i++){
start=n;temp[--start]='\0'; //f==0到根了
for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
if(HMNode[f].lchild==j) temp[--start]='0';//左0
else temp[--start]='1';//右1
j=f;
}
code[i]=temp+start;
}
delete temp;
}
//本程序 0号结点统一不用
int main(){
// freopen("inputd.txt","r",stdin);
// char c;
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>ch[i]>>w[i];//c用来缓冲空格
}
HuffManEncoding();
for(int i=1;i<=n;i++){
cout<
时间限制: 1 Sec 内存限制: 128 MB
提交: 130 解决: 87
[提交][状态][讨论版][命题人:外部导入]
合并果子
(fruit.pas/c/cpp)
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。
所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。
第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。
3 1 2 9
15
本题请用两张算法完成:
1.堆的应用
2.单调队列的应用
c题代码就可以过
#include
#include
using namespace std;
int main(){
// freopen("inputc.txt","r",stdin);
int n,t,x,y,ans;
priority_queue,greater > q;
while(cin>>n){
ans=0;
for(int i=0;i>t;
q.push(t);
}
while(q.size()>1){
x=q.top();q.pop();
y=q.top();q.pop();
q.push(x+y);
ans+=(x+y);
}
q.pop();
cout<