贪婪算法---哈夫曼树的构建及编码

哈夫曼树相关概念

  • 应用领域:压缩(提高了网络的传输效率)
  • 编码依据:最优二叉树(哈夫曼树)
  • 相关概念:
    • 节点的权(w):赋予叶子结点有意义的值
    • 节点的路径长度(L):从根节点到当前节点的个数
    • 节点的带权路径长度:w*L
    • 一棵二叉树的带权路径长度:二叉树的所有叶子节点的带权路径长度之和
    • 最优二叉树:二叉树的带权路径长度最小
  • 最优二叉树的特点:
    • 最优二叉树没有单亲节点,只有双亲节点和叶子结点
    • 若叶子节点的个数是n,那么双亲节点的个数是n-1

最优二叉树的构建总体思路

  • 以每一个节点为根节点构建的树组成森林;
  • 从森林中获取权值最小的和次小的二叉树,构成新的二叉树,再放回森林;
  • 重复第二步,直至森林中只有一棵树。

哈夫曼树的实现

  • 用链表存储哈夫曼树

    • 存储形式:

      • 节点:
        《贪婪算法---哈夫曼树的构建及编码》

      • 森林:用数组存储

    • 算法描述
      1.定义节点类型
//节点的结构
typedef struct node{
    struct node *left,*right;//储存指向左右节点的指针
    char word;//存储节点的表示
    int weight;//存储节点的权重 
}HuffNode;
      2.定义森林数组并初始化
    HuffNode **F;//指向动态数组的指针
    int n;//数组的长度 

    //从键盘输入数组长度 
    printf("请输入数组长度:");
    scanf("%d",&n); 

    //定义储存森林的数组
    F = (HuffNode **)malloc(n*sizeof(HuffNode*));

    //初始化森林
    for(int i = 0;i<n;i++){
        int w;//表示权重 
        char ch;//表示节点 

        //新建二叉树节点
        F[i] = (HuffNode*)malloc(sizeof(HuffNode));
        printf("请输入节点的表示");
        scanf("%c",ch);
        printf("请输入节点的权重:");
        scanf("%d",w);
        F[i]->word = ch;
        F[i]->weight = w; 
        F[i]->left =  F[i]->right = NULL;
    } 
      3.最优二叉树的构建
思路:输入的节点只能是叶子结点。所以构建二叉树的过程,就是创建双亲节点。根据最优二叉树的特点(若叶子节点的个数是n,那么双亲节点的个数是n-1),构建最优二叉树的循环次数为n-1次。而在每一循环中,都要从森林中获取最小以及次小的二叉树,作为新二叉树的左右节点,并返回森林,参与下一次循环
//构建最优二叉树
HuffNode *createHuffTree(HuffNode **F,int n){
    int loop;//表示循环次数
    HuffNode *newF; //表示新生成的节点 
    for(loop = 1;loop < n;loop++){
        int k1,k2;//k1表示最小节点的位置,k2表示次小节点的位置
        k1 = k2 = -1;

        //获取最小以及次小节点位置的初值 
        for(int i = 0;i < n && k2 == -1;i++){
            //F[i]=NULL时,表示该元素已经有双亲节点,不能参与最小以及次小的选取。
            if(k1 == -1 && F[i]){
                k1 = i;
            }else if(F[i]){
                k2 = i;
            }
        }
        //获取最小以及次小节点的位置
        for(int j = k2;j < n;j++){
            if(F[j]){
                if(F[j]->weight < F[k1]->weight){
                    k2 = k1;
                    k1 = j;
                }else if(F[j]->weight < F[k2]->weight){
                    k2 = j;
                }
            }
        }

        //生成新的节点
         newF = (HuffNode*)malloc(sizeof(HuffNode));

         //将找到的最小以及次小节点挂到新的节点 
         newF->left = F[k1];
         newF->right = F[k2];
         newF->weight = F[k2]->weight + F[k1]->weight;
         newF->word = ' ';

         //将新生成的节点放回森林
         F[k1] = newF;
         F[k2] = NULL;
    }
    return newF; 
} 
  • 用数组形式存储哈夫曼树
    • 存储形式
      • 节点
        《贪婪算法---哈夫曼树的构建及编码》
      • 森林
        《贪婪算法---哈夫曼树的构建及编码》
    • 算法描述
      1.定义节点的类型
//定义节点的类型
typedef struct {
    char word;//节点表示
    int weight;//节点的权重
    int parent,left,right;//用数组下标表示节点的双亲,左右子女节点
    int *code;//用指针数组表示哈夫曼编码
}Huff;

2.定义森林数组并初始化
思路:在节点初始化过程中,左右子节点以及它的双亲节点设为-1

    Huff *F;//表示节点
    int n;//表示节点的个数
    printf("请输入节点个数");
    scanf("%d",&n);

    F = (Huff *)malloc((2*n-1)* sizeof(Huff));//申明指针数组

    //初始化节点并存入数组
    for (int i = 0; i < n; ++i) {
        int w;//表示权重
        char ch;//表示节点
        printf("请输入节点的表示");
        scanf("%c",&ch);
        printf("请输入节点的权重");
        scanf("%d",&w);

        F[i].weight = w;
        F[i].word = ch;
        F[i].left = F[i].parent = F[i].right = -1;
        F[i].code = NULL;
    }

3.构建最优二叉树
思路:

  • 构造最优二叉树的过程,其实就是构造双亲节点的过程,根据最优二叉树的特点(若叶子节点的个数是n,那么双亲节点的个数是n-1)。故构造双亲节点的次数就是n-1。
  • 节点的左右子节点以及双亲节点皆用数组的下标表示。
  • 当节点的parent为-1时,表示还没有构造其双亲节点。若parent不为-1,则表示双亲节点在数组的位置,此节点不能参与构造双亲节点的过程中
//构造最优二叉树
void createHuffTree(Huff *F,int n){
    //loop表示构造双亲节点的次数
    //k1表示最小节点,k2表示次小节点
    int loop,k1,k2,j;
    k1 = k2 = -1;

    for (loop = 0; loop < n-1; ++loop) {

        //找到初始k1,k2的值
        //注意创建的双亲节点,如果parent也为-1,同样也要参与最小值以及次小值的选取
        for (int i = 0; i < loop+n && (k2 == -1); ++i) {
            if(k1 == -1 && F[i].parent != -1){
                k1 = i;
            }else if (F[i].parent != -1) {
                k2 = i;
            }
        }

        //找出最小值以及次小值
        for (j = 0; j < loop+n; ++j) {
            if (F[j].parent !=-1){
                if(F[j].weight < F[k1].weight){
                    k2 = k1;
                    k1 = j;
                }else if(F[j].weight < F[k2].weight){
                    k2 = j;
                }
            }
        }
        //跳出循环后,j正好表示紧接着已分配的数组元素的尚未分配的数组元素
        //双亲节点的设置
        F[j].word = ' ';
        F[j].left = k1;
        F[j].right = k2;
        F[j].weight = F[k1].weight + F[k2].weight;
        F[j].parent = -1;
        F[j].code = NULL;

        //左右子节点双亲节点的设置
        F[k1].parent = F[k2].parent = j;
    }
}
点赞