3.哈夫曼编/译码系统

【问题描述】利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。

【基本要求】一个完整的系统应具有以下功能:

1)I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。

2)E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。

3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。

4)P:打印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。

5)T:打印哈夫曼树(Tree printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。

 

Ø 算法设计思想

哈夫曼树是最优二叉树,所以存储结构跟二叉树的二叉链表存储表示相似,多了权值和父节点,哈夫曼树和哈夫曼编码的存储表示如下:

typedef struct{

    unsigned int weight; //权值

    unsigned int parent,lchild,rchild;

}HTNode,*HuffmanTree; //动态分配数组存储赫夫曼树

typedef char * *HuffmanCode; //动态分配数组存储赫夫曼编码表

由字符集及相应的频度建立哈夫曼树的,因此定义全局变量ch 字符数组保存字符。并宏定义了最大结点数目Max 200.

Ø 算法设计分析

1.      void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n) //wn个字符的权值(>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC。首先初始化结点的成员,然后在HT[1..i-1]选择parent0weight最小的两个结点,其序号分别为S1S2开始建哈夫曼树,在这里为了区别叶子结点字符及方便以后操作,把非叶子结点的字符都初始化为“?”。最后从叶子到根逆向求每个字符的赫夫曼编码。

2.      void Initialization(HuffmanTree &HT, HuffmanCode &HC, int &n)//初始化,注意加引用符号。先判断hfmTree.txt文件是否存在,如果不存在就直接从终端读入字符集大小n,以及n个字符和n个权值,调用上述函数建立哈夫曼树,否则就读到内存中。由于字符可能是空格,所以要用getchar() 函数读入字符。用到的文本文件读写操作如下:

ifstream fin("hfmTree.txt", ios::in);//读出

ofstream fout("hfmTree.txt", ios::out);//写入

3. void Encoding(HuffmanCode & HC,int n)//对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。首先把正文用getline()函数一行一行读入字符串str中。编码过程,一个字符一个字符来,算法如下:

          for(i=0;str[i]!='\0';i++)//编码过程,一个字符一个字符来

{       j=1;

      while(ch[j]!=str[i]&&j<=n) j++;

      if(j<=n) fout<<HC[j];

}

4. void Decoding(HuffmanTree HT, int n)//将文件CodeFile中的代码进行译码,结果存入文件TextFile中。由于文件中代码全是01,无空格,所以可以直接读入字符串str,译码过程,从根出发,按字符‘0’或‘1’确定找左孩子或右孩子,直至叶子结点,算法如下:    i=0; j=m;

 while(i<len ) //len=str.size()

 {   if(str[i]=='0')   j=HT[j].lchild;

        else   j=HT[j].rchild;

       if(HT[j].lchild==0) { fout<<ch[j]; j=m; }

       i++;

     }

5. void Print(HuffmanTree HT, int n)//打印代码文件将文件CodeFile以紧凑格式显示在终端上,每行50个代码。用对50取摸等于0来控制换行。

6. void PrintTree(HuffmanTree &HT, int &n)// 将已在内存中的哈夫曼树以凹入表形式显示在终端上。非叶子结点用权值表示,叶子结点用字符表示,其中为了便于区分层次,空格字符用‘#’代替算法如下:

   for(l=m;l>0;l--) //m为根结点的权值

               for(i=1;i<=2*n-1;i++)

                     if(HT[i].weight==l)

       {   for(j=0;j<k;j++)  //k 表示层数,r表示每层的结点数

              {      cout<<"   ";  fout<<"   ";  }//控制每层前面的空格

                     if(ch[i]!='?') //如果是叶子结点就直接输出,否则输出权重

                     {   if(ch[i]==' ') ch[i]='#';//标记为空的字符

                            { cout<<ch[i]<<endl; fout<<ch[i]<<endl; }

                  }

 else {   cout<<HT[i].weight<<endl;

 fout<<HT[i].weight<<endl; p++;

}//p 表示每层的非叶子结点数

                   q++;//q 记录每层的结点数目

 if(q==r) { k++; r=2*p; p=0; q=0; } //如果该层的结点满了,就到下一层,即k+1

        }  

源代码:

 

#include<iostream>
#include<string>
#include<fstream>
using namespace std;
#define Max 200 //最大结点数目
#define INT 10000
char ch[Max]; //叶子结点信息(字符)
int i,w[Max]; //w为输入的权值数组
typedef struct{
 unsigned int weight; //权值
 unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree; //动态分配数组存储赫夫曼树
typedef char * *HuffmanCode; //动态分配数组存储赫夫曼编码表
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)
{//w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC。
 int s1,s2,j,min1,min2;
 if(n<=1)  return;
 int m=2*n-1;
 HT=new HTNode[m+1];//0号单元未用
 for(i=1;i<=n;++i) //赫夫曼树叶子结点的初始化
 {
  HT[i].weight=w[i];  HT[i].parent=0;
  HT[i].lchild=0;     HT[i].rchild=0;
  }
 for(i=n+1;i<=m;++i) //非叶子结点的初始化
 {
    HT[i].weight=0;   HT[i].parent=0;
    HT[i].lchild=0;   HT[i].rchild=0;
 }
 for(i=n+1;i<=m;++i) //建赫夫曼树
 { //在HT[1..i-1]选择parent为0且weight最小的两个结点,其序号分别为S1和S2
  min1=min2=INT;
  for(j=1;j<i;j++)
   if(HT[j].parent==0&&(HT[j].weight<min1))
   {   min1=HT[j].weight;  s1=j;  }
      for(j=1;j<i;j++)
      if(HT[j].parent==0&&(HT[j].weight<min2)&&j!=s1)
   {   min2=HT[j].weight;  s2=j;   }
          HT[s1].parent=i; HT[s2].parent=i;
    HT[i].lchild=s1;HT[i].rchild=s2;
       HT[i].weight=HT[s1].weight+HT[s2].weight;
    ch[i]='?';//非叶子结点的结点字符
 }
 //从叶子到根逆向求每个字符的赫夫曼编码
 HC=new char*[n+1]; //分配n个字符编码的头指针向量
 char *cd=new char[n]; //分配求编码的工作空间
 HT[i].lchild=s1; HT[i].rchild=s2;
 cd[n-1]='\0';  //编码结束符
 for(i=1;i<=n;i++)  //逐个字符求赫夫曼编码
 {
  int c,f,start=n-1;//编码结束符位置
  for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent) //从叶子到根逆向求编码
   if(HT[f].lchild==c)
    cd[--start]='0';
   else
    cd[--start]='1';
   HC[i]=new char[n-start];//为第i个字符编码分配空间
   strcpy(HC[i],&cd[start]); //从cd复制编码(串)到HC
 }
     delete cd;
}
void Initialization(HuffmanTree &HT,HuffmanCode &HC,int &n)//初始化,注意加引用符号
{
  char c;
     ifstream fin("hfmTree.txt",ios::in);//读出
     if(fin.fail()) //若不存在此文件,则从终端读入
  {
         cout<<"请输入字符个数n:";
   cin>>n;
   cout<<"请输入字符及相应权值:"<<endl;
   for(i=1;i<=n;i++)//注意点
   {
    //getchar()是在输入缓冲区顺序读入一个字符(包括空格、回车和tab)。
             getchar(); //取消换行符
    //getchar每次只能读取一个字符.如果需要取消'\n'的影响,可以用getchar();来清除,
    //这里getchar();只是取得了'\n'但是并没有赋给任何字符变量,所以不会有影响,相当于清除了这个字符.
    ch[i]=getchar(); //保证空格读进
    cin>>w[i];
   }
   HuffmanCoding(HT,HC,w,n);
         ofstream fout("hfmTree.txt",ios::out);//写入
      fout<<n<<endl;
         for(i=1;i<=2*n-1;i++)//写入字符,权值,父结点,左右孩子
      fout<<ch[i]<<" "<<HT[i].weight<<" "<<HT[i].parent<<
     " "<<HT[i].lchild<<" "<<HT[i].rchild<<endl;
   for(i=1;i<=n;i++)//写入字符及相应的编码
    fout<<ch[i]<<"  "<<HC[i]<<endl;
   fout.close();
  }
 else{   //如果文件存在,即读到内存中
        fin>>n;
        for(i=1;i<=2*n-1;i++)
     {
        fin.get(ch[i]);
     fin>>HT[i].weight>>HT[i].parent>>HT[i].lchild>>HT[i].rchild;
     fin.get(c);
     } 
      for(i=1;i<=n;i++)
   {  
    fin.get(ch[i]);
       fin>>HC[i];
       fin.get(c);
   }
  }
 fin.close();
}
void Encoding(HuffmanCode & HC,int n) //编码
{
 int i,j;
 string str,str1;
 ifstream fin("ToBeTran.txt",ios::in);//打开文件
 ofstream fout("CodeFile.txt",ios::out);
 if(fin.fail())
  cout<<"文件ToBeTran.txt打开失败!"<<endl;
    getline(fin,str);//读入一行,可以读入空格
 while(!fin.eof())//文件不结束
 {
  getline(fin,str1);
  str=str+str1;
 }
   fin.close();
 for(i=0;str[i]!='\0';i++)//编码过程,一个字符一个字符来
 {  
  j=1;
  while(ch[j]!=str[i]&&j<=n) j++;
  if(j<=n)  fout<<HC[j];
 }
    fout<<endl;
 fout.close();
}
void Decoding(HuffmanTree HT,int n)//译码
{
  int j,len,m;
  string str;
  m=2*n-1;
  ifstream fin("CodeFile.txt",ios::in);
  ofstream fout("TextFile.txt",ios::out);
  if(fin.fail())
  {
   cout<<"文件CodeFile.txt打开失败!"<<endl;
   return ;
  }
   fin>>str;
   fin.close();
   len=str.size();
   i=0;j=m;
  while(i<len)
  { //从根出发,按字符‘0’或‘1’确定找左孩子或右孩子,直至叶子结点
   if(str[i]=='0')
         j=HT[j].lchild;
   else j=HT[j].rchild;
   if(HT[j].lchild==0)
   {  fout<<ch[j];  j=m; }
   i++;
  }
  fout<<endl;
}
void Print(HuffmanTree HT,int n)//打印代码文件
{
 int i;
 char str[Max];
 ifstream fin("CodeFile.txt",ios::in);
 ofstream fout("CodePrin.txt",ios::out);
 if(fin.fail())
  cout<<"文件CodeFile.txt打开失败!"<<endl;
 fin>>str;
 cout<<"毎行50个代码形式的编码如下:"<<endl;
 for(i=0;i<strlen(str);i++)
 {
  if(i%50==0&&i!=0) //毎50以换行
  {
   cout<<endl; fout<<endl;
  }
      cout<<str[i];fout<<str[i];
 }
        cout<<endl;fout<<endl;
  fin.close(); fout.close();
}

//非叶子结点用权值表示,叶子结点用字符表示
void PrintTree(HuffmanTree &HT,int &n)
{
 ofstream fout("TreePrint.txt",ios::out);//写入文件
 int l,j,m=0,k=1,q=0,p=0,r=1;//k 表示层数,r表示每层的结点数
 for(i=1;i<=2*n-1;i++)
    if(HT[i].parent==0)//找根结点的权值
     m=HT[i].weight;
 for(l=m;l>0;l--) 
   for(i=1;i<=2*n-1;i++)
   if(HT[i].weight==l)
   {  
    for(j=0;j<k;j++)
    { cout<<"   "; fout<<"   "; }//控制每层前面的空格
    if(ch[i]!='?') //如果是叶子结点就直接输出,否则输出权重
    {
     if(ch[i]==' ')  ch[i]='#';//标记为空的字符
     {  cout<<ch[i]<<endl; fout<<ch[i]<<endl; }
    }
    else
    {   cout<<HT[i].weight<<endl; fout<<HT[i].weight<<endl;  p++; }//p 表示每层的非叶子结点数
       q++;//q 记录每层的结点数目
       if(q==r) { k++; r=2*p; p=0; q=0; } //如果该层的结点满了,就到下一层,即k+1
   }  
}
int main()
{
 HuffmanTree HT;
 HuffmanCode HC;
 char c;
 int n;
 HT=new HTNode[Max];
 HC=new char*[Max];
 for(int i=1;i<=Max;i++)//初始化
 HC[i]=new char[20];
 cout<<"-----------------------欢迎进入哈夫曼的编/译码系统----------------------"<<endl;
 cout<<"         菜单如下:"<<endl;
 cout<<"                 I、初始化"<<endl;
 cout<<"                 E、编码"<<endl;
 cout<<"                 D、译码"<<endl;
 cout<<"                 P、打印代码文件"<<endl;
 cout<<"                 T、打印哈夫曼树"<<endl;
 cout<<"                 Q、退出"<<endl;
  while(c!='Q')
 {
     cout<<"请选择操作:";
  cin>>c;
  switch(c)
  {
  case 'I': Initialization(HT,HC,n);
               cout<<"哈夫曼树成功存入文件hfmTree中!"<<endl;
      break;
  case 'E': Encoding(HC,n);
         cout<<"对文件ToBeTran的编码结果成功存入文件CodeFile中!"<<endl;
      break;
  case 'D': Decoding(HT,n);
         cout<<"对文件CodeFile的译码结果成功存入文件TextFile中!"<<endl;
      break;
  case 'P': Print(HT,n);
         cout<<"毎行50个代码形式的编码结果成功写入文件CodePrin中!"<<endl;
      break;
  case 'T': cout<<"哈夫曼树的凹入表示法如下:"<<endl;
         PrintTree(HT,n);
         cout<<"哈夫曼树以凹入表形式成功写入文件TreePrint中!"<<endl;
  default: break;
  }
  }
 return 0;
}

 

posted @ 2011-06-29 17:06  笑巧  阅读(4739)  评论(0编辑  收藏  举报