haffman编码说明
1、程序简介
       本程序用用C语言来实现haffman编码,可以为任意ASCII码字编码。首先从指定文件读取待编码的字符,统计并打印出现的字符以及字符出现的次数,然后根据haffman编码原理进行编码,最后将编码结果打印到屏幕,为阅读方便,将编码结果另存到当前工程目录的Encode.txt文件中。
    程序基本流程如图 1所示。后面两节会详细介绍建立haffman树和编码两个环节,其它环节不再介绍。
 

 
图1 程序基本流程
2、建立haffman树
       树是以节点为基础的,因此首先来确定节点。根据haffman编码原理,假如有n个字符需要编码,则节点总数为2n-1。建立haffman树的具体过程如下。
       (a)、节点进行初始化。以每个待编码字符作为为节点,他们的的左右子节点是不存在的。
   (b)、寻找上述节点的各自的父节点。首先从上述n个节点中找出出现概率最低的两个节点x1和x2,然后创建一个节点Xn+1,作为这x1和x2的父节点,即x1和x2为Xn+1的左右子节点,则Xn+1节点出现的概率为x1和x2出现之和。此时节点数变为n+1个,因为x1和x2已经有了父节点,不再考虑x1和x2,从剩下的n+1-2个节点中寻找两个概率最低的节点x3和x4,再创建一个新节点Xn+2,作为x3和x4的父节点,依次类推,直到只剩下一个节点没有父节点(为根节点),则haffman树创建完毕。
       建立haffman树的流程如图2所示。
 

 
图2 建立haffman树
3、根据haffman树编码
       haffman树建成后,开始进行编码,从树的最顶端的某个节点x0找自己的父节点x1,如果节点x0是左子节点,则路径为0,否则路径为1,然后再为节点x1找父节点x2,若节点x1为节点x2左子节点,则路径为0,否则路径为1。依次类推,直到找到根节点。从树的顶端节点到根节点经过的路径就是当前字符的编码结果。直到位于树顶端的节点的路径都寻找完成,则所有字符的编码也就完成了。
       根据haffman树编码的流程如图3所示。

  
图3 haffman编码
 

/**************************************************************************
* HuffmanEncoding.cpp:
***************************************************************************
*本程序用C语言实现哈弗曼编码。
*读取指定文件,将文件中的字符用哈弗曼编码,将编码结果输出到指定文件中,并且
*同时将编码结果打印到屏幕上。
**************************************************************************/

#include <stdio.h>
#include <string.h>

#define MAX 256       //8位ASCII码 共256个。
#define INF 0xffffff  //设定某个字符允许出现的最大次数。

typedef struct
{
 char c;      //字符c
 int  cnt;    //字符c出现的次数
}Character;

typedef struct   //霍夫曼树的节点
{
 int weight;
 int parent;
 int ld;
 int rd;
}Huffmantree;

typedef struct      
{
 char ch;
 int num;
}myNode;
typedef struct      //字符和其对应的编码
{
 char ch;      //字符
 int s[50];    //ch的编码
 int len;      //编码长度
}mycode;

int nNode;                   //叶子节点数目
int totalNode;               //霍夫曼树的总节点个数
char toCode[100000] ;        //待编码的字符串
myNode myToCode[100000];     //待编码的字符串和权值
int weightOfToCode[100000] ; //字符串的权值!
Huffmantree myHuffmantree[1000000]; //霍夫曼树(数组模拟)
char allchar[1000000];       //所有出现过的字符
mycode coder[1000000];       //字符与对应的编码

int Coding[100000];          //译码之后的01串
int lenOfCoding ;            //01串的长度
Character chara[MAX];       //存放每个待编码字符出现的次数
int chara_cnt=0;  //待编码的字符的个数

void read();                 //读取文件中待编码的字符
void build(int n);           //建立霍夫曼树
void select(int *a,int *b);  //选择两个权值最小的节点
void Code();                 //编码
void printCode();            //打印编码


int main()
{

 read();   //读取待编码文件中的字符,并打印出现的所有字符及其出现的次数。

 nNode=chara_cnt;   //子节点个数
 totalNode=nNode;   //totalNode初始值为nNode
 
 build(nNode);  //建立节点
 Code();        //编码
 printCode();   //打印编码结果
 system("pause");
 return 0;

}


void read()
{
 char filename[100];
 FILE* fp=0;
 int i=0;
 int j=0;
 int len=0;
 char *p=toCode;
 int h=1;
 printf("请输入待编码文件的名字(e.g. F:\\test\\in_file.txt):\n");
 scanf("%s",filename);
 if((fp=fopen(filename,"r"))==NULL)
 {
  printf("cannot open this file\n");
  exit(1);
 }
 while(!feof(fp))
 { 
  char c=fgetc(fp);
  if(c!=EOF&&c!='\n')  // 防止读入EOF标志,回车符号进行编码
   *p++=c;
 }
 len=strlen(toCode);
 for(i=0;i<len;i++)       //统计字符串中各字符出现的频次!
 {
  for( j=0;j<chara_cnt;++j){
   if(toCode[i]==chara[j].c)
    break;
  }
  if(j==chara_cnt)  //当前字母没有出现过
  {
   chara[chara_cnt].c=toCode[i];
   (chara[chara_cnt].cnt)++;
   chara_cnt++;
  }
  else
  {
   (chara[j].cnt)++;
  }
 } 

 for(j=0;j<chara_cnt;++j)
 {
  myToCode[h].ch=chara[j].c;
  allchar[h]=chara[j].c;
  weightOfToCode[h]=chara[j].cnt;
  myToCode[h++].num=chara[j].cnt;
 }

 printf("--------------------------字符统计如下----------------------------------\n");
 printf("  字符   次数\n");
 for(i=1;i<=chara_cnt;i++)   //显示将要编码的字符串中的各字符和他出现的次数!
 {
  printf("    %c     %d\n",myToCode[i].ch,myToCode[i].num);
 }
  
}

void build(int n)  //建立霍夫曼树
{
 int i;
 int m=2*n-1;  //n个叶子节点的霍夫曼树总节点数为2*n-1
 for(i=1;i<=n;i++)  //初始化霍夫曼数组
 {
  myHuffmantree[i].weight=weightOfToCode[i];  //叶子节点权值为字符出现次数
  myHuffmantree[i].ld=-1;  //叶子节点没有左孩子
  myHuffmantree[i].rd=-1;  //叶子节点没有右孩子
  myHuffmantree[i].parent=i; //叶子节点父节点先初始化为他本身
 }
 for(i=n+1;i<=m;i++)
 {
  int a,b;
  select(&a,&b);
  myHuffmantree[a].parent=i;
  myHuffmantree[b].parent=i;
  myHuffmantree[i].ld=a;
  myHuffmantree[i].rd=b;
  myHuffmantree[i].weight=myHuffmantree[a].weight+myHuffmantree[b].weight;
  myHuffmantree[i].parent=i;
 }
}
void Code()         //编码
{
 int i,j;
 int Len; //待编码的字符的总长度
 int numOfCode[100000];
 Len=strlen(toCode);                    //待编码的字符的总长度
 printf("--------------------------各字符编码结果如下----------------------------\n");
 if(Len==1)
 {
  printf("%c : 0\n",toCode[0]);
  return ;
 }
 for(i=1;i<=nNode;i++)
 {
  int h=0;
  int x=0;
  int k;
  j=i;
  
  while(myHuffmantree[j].parent!=j)
  {
   int x=j;
   j=myHuffmantree[j].parent;
   if(myHuffmantree[j].ld==x)
   {
    numOfCode[h++]=0;
   }
   else if(myHuffmantree[j].rd==x)
   {
    numOfCode[h++]=1;
   }
  }
  printf(" %c : ",allchar[i]);
  coder[i].len=h;
  coder[i].ch=allchar[i];
  for( k=h-1;k>=0;k--)
  {
   coder[i].s[x++]=numOfCode[k];
   printf("%d",numOfCode[k]);
  }
  printf("\n");
 }  
}
void select(int *a,int *b)  //选择两个权值最小的节点
{
 int i;
 int min1=INF;
 int min2=INF;
 int sign1=1;             //最小值的下标
 int sign2=2;              //次小值的下标
 for(i=1;i<=totalNode;i++)
 {
  if(myHuffmantree[i].parent==i)  //说明其是已经更新过的节点  
  {
   if(myHuffmantree[i].weight<min1)  找出权重最小的节点sign1
   {
    min1=myHuffmantree[i].weight;
    sign1=i;
   }
  }
 }
 for(i=1;i<=totalNode;i++)
 {
  if(myHuffmantree[i].parent==i)  //说明其是已经更新过的节点
  {
   if(myHuffmantree[i].weight<min2&&i!=sign1)  //找出除sign1外的权重最小的sign2
   {
    min2=myHuffmantree[i].weight;
    sign2=i;
   }
  }
 }
 *a=sign1;
 *b=sign2;
 totalNode++;  //总节点数加1
}
void printCode()   //打印编码结果!
{
 int i,j,k;
 int Len=strlen(toCode);
 int h=0;
 FILE *fp=0;
 if((fp=fopen("./Encode.txt","w"))==NULL)
 {
  printf("cannot open this file\n");
  exit(1);
 }
 printf("\n------------------------字符串的编码结果如下------------------------\n");
 if(Len==1)   //长度为1的时候特殊考虑
 {
  printf("0\n"); 
  fprintf(fp,"0\n");
  return ;         
 }
 
 for(i=0;i<Len;i++)
 {
  for( j=1;j<=nNode;j++)
  {
   if(toCode[i]==coder[j].ch)
   {
    for( k=0;k<coder[j].len;k++)
    {
     printf("%d",coder[j].s[k]);    //打印文件到屏幕
     fprintf(fp,"%d",coder[j].s[k]);//保存编码到文件
     Coding[h++]=coder[j].s[k];     //保存编码到数组
    }
   }
  }
 }
 lenOfCoding=h;
 printf("\n编码码流总长度为:%d\n",lenOfCoding);
 fprintf(fp,"\n");
 printf("\n为方便查阅,编码结果另存到当前工程目录下的Encode.txt文件中!!!\n");
}