zoukankan      html  css  js  c++  java
  • 最优二叉树 (哈夫曼树) 的构建及编码

    思路来源:https://www.bilibili.com/video/BV18t411U7Tb?from=search&seid=13776480377358559786

                      https://www.bilibili.com/video/BV18t411U7eD?from=search&seid=13776480377358559786

    代码借鉴:https://blog.csdn.net/feynman1999/article/details/71178357

    一,哈夫曼树的构建

      要实现哈夫曼树首先有个问题摆在眼前,那就是用什么数据结构 ?

    通常我们为了方便都是用数组储存的,那么此时又有两个新的问题出现了

    权值放在哪?   父子关系如何表示 ?

    第一个问题我们可以很轻松的用结构体数组实现,

    第二个问题有些人会想到用完全二叉树那种利用数组之间的位置关系实现,

    但问题是你一开始所给的树叶的位置无法确定,就不知道放哪了,

    要是先随便放,那你必须等到找到根节点 。这时候菜都凉了。

    既然第一个问题想用结构体数组处理,还不如在结构体添加父节点子节点的位置。如此便可。

    typedef struct
    {
        double w;
        int p, lc, rc;
    }HTNode, *HuffmanTree;
    HTNode HT[100];

    例题: 用8个数,构造最优二叉树。

    7 19 2 6 32 3 21 10

    左边是我画的结果,右边是将最优二叉树存入结构体数组的结果,

    其中,前八点为树叶,后六点为内点,最后一点为树根。

    1,构造森林全是根    

     这一步就是把这 n 个点放入结构体数组中:

       有 n 个点,每一个点用一次,共产生 n-1 个点,所以用到的数组长度为 2n-1

    在实现的时候不用下标为 0 的位置,比较方便。

            int m = 2 * n - 1;
        for (int i = 1; i <= m; i++)
            HT[i].lc = 0, HT[i].rc = 0, HT[i].p = 0;  // 初始化为 0
        printf("请输入树叶的权值:
    ");
        for (int i = 1; i <= n; i++)
            scanf("%lf", &HT[i].w);    // 赋值        

    2,选择两小造新树

    就是在剩下没用过的点找到最小的两个数,即在那些没有父亲的结点中到最小的两个数

    这些结点包括树叶也包括新出现的未来的内点。

    int min(int k)
    {
        int min;        //用来存放weight最小且parent为0的元素的下标 
        double min_t;     //用来存放weight最小且parent为0的元素的weight值的暂时值
                          
        int i = 1;     //从 1 开始,0 放弃掉
        while (HT[i].p != 0)  // 看一下第一个父亲健在的位置
            i++;
        min_t = HT[i].w;     //将第一个 parent==0 的元素的 w 值赋给 min_t 
        min = i;
    
        //选出 w 最小且 p==0 的元素,并将其序号赋给 min
        for (; i <= k; i++)
        {
            if (HT[i].w < min_t&&HT[i].p == 0)
            {
                min_t = HT[i].w;
                min = i;
            }
        }
        //注意,选出weight最小的元素后,将其parent置1,使得下一次求第二小时将其排除在外
        HT[min].p = 1;
        return min;//返回下标     
    }
    void select(int k, int *min1, int *min2)//&为引用 
    {
        *min1 = min(k);
        *min2 = min(k);
    }
    
        for (int i = n + 1; i <= m; i++)   // 产生 n-1 个结点
        {
            select(i - 1, &s1, &s2);     // 选择两小
    
            
        }

    3,删除两小添新人

    删除两小就是: 给找到的结点 认好父亲,这样就不会在下次中被找到

    添新人就是:给最小的没用过的结点 与 找到的结点 建立联系

       
            HT[s1].p = i; HT[s2].p = i;
            HT[i].lc = s1; HT[i].rc = s2;
            HT[i].w = HT[s1].w + HT[s2].w;
       

    4,重复  2,3操作

    可以发现 我们从 n-1 个结点循环,

    这样要找的结点正好是 循环变量 i 的前面的结点

    so:

        int s1, s2;
        for (int i = n + 1; i <= m; i++)   // 产生 n-1 个结点
        {
            select(i - 1, &s1, &s2);     // 选择两小
    
            HT[s1].p = i; HT[s2].p = i;
            HT[i].lc = s1; HT[i].rc = s2;
            HT[i].w = HT[s1].w + HT[s2].w;
        }

    完整代码

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    typedef struct
    {
        int w;
        int p, lc, rc;
    }HTNode, *HuffmanTree;
    HTNode HT[100];
    int min(int k)
    {
        int min;        //用来存放weight最小且parent为0的元素的下标 
        int min_t;     //用来存放weight最小且parent为0的元素的weight值的暂时值
                       //注意应先将第一个 parent==0 的元素的 w 值赋给 min_t ,留作以后比较用
                       //不要直接将HT[0].wt赋给 min_t 了(这是个常用的技巧)
        int i = 1;     //从 1 开始,0 放弃掉
        while (HT[i].p != 0)  // 看一下第一个父亲健在的位置
            i++;
        min_t = HT[i].w;
        min = i;
    
        //选出 w 最小且 p==0 的元素,并将其序号赋给 min
        for (; i <= k; ++i)
        {
            if (HT[i].w < min_t&&HT[i].p == 0)
            {
                min_t = HT[i].w;
                min = i;
            }
        }
        //注意,选出weight最小的元素后,将其parent置1,使得下一次求第二小时将其排除在外
        HT[min].p = 1;
        return min;//返回下标     
    }
    void select(int k, int *min1, int *min2)//&为引用 
    {
        *min1 = min(k);
        *min2 = min(k);
    }
    void creatHT(int n)
    {
        if (n <= 1)  // 只有一个人没有父亲时结束
            return;
        int m = 2 * n-1;
        for (int i = 1; i <= m; i++)
            HT[i].lc = 0, HT[i].rc = 0, HT[i].p = 0;  // 初始化为 0
        printf("请输入树叶的权值:
    ");
        for (int i = 1; i <= n; i++)
            scanf("%d", &HT[i].w);    // 赋值
    
        // 从 p=0 的结点中 选择两小造新树
        int s1, s2;
        for (int i = n + 1; i <= m; i++)   // 产生 n-1 个结点
        {
            select(i - 1, &s1, &s2);     // 选择两小
    
            HT[s1].p = i; HT[s2].p = i;
            HT[i].lc = s1; HT[i].rc = s2;
            HT[i].w = HT[s1].w + HT[s2].w;
        }
    }
    void show(int n)
    {
        for (int i = 1; i <= 2 * n - 1; i++)
        {
            printf("权值为 %2d 的点的 父节点为 %2d ;子节点为 %2d %2d
    ", HT[i].w, HT[i].p, HT[i].lc, HT[i].rc);
        }
        printf("(其中结点为 0 代表没有这个结点.)
    ");
    }
    int main(void)
    {
        printf("请输入树叶的个数:");
        int n; scanf("%d", &n);
        creatHT(n);
        show(n);
    
        system("pause");
        return 0;
    }
    /*
    测试数据 1:
    6
    5 8 4 11 9 13
    测试数据 2:
    8
    7 19 2 6 32 3 21 10
    */
    View Code

     二,哈夫曼树编码

    这个思路挺简单的:

    首先是可以确定树高最高为为 n-1,自己画一下就知道了

    这样就可以用 n 给数组赋长了,这里用动态二维数组,比较专业,也可以不用动态,数组开大点就好了

    然后从树叶回溯到根,每一个结点判断一下是 左节点 还是 右节点 就好了

    因为由树高可知编码最长为 n-1,这样动用 下标为 0 的位置,可知 n-1 位为 '' ,

    然后往前赋值就可以了。

    最后真正编码长度可以用回溯时的循环次数确定

    完整代码:

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    typedef struct
    {
        double w;
        int p, lc, rc;
    }HTNode, *HuffmanTree;
    HTNode HT[100];
    int min(int k)
    {
        int min;        //用来存放weight最小且parent为0的元素的下标 
        double min_t;     //用来存放weight最小且parent为0的元素的weight值的暂时值
                          
        int i = 1;     //从 1 开始,0 放弃掉
        while (HT[i].p != 0)  // 看一下第一个父亲健在的位置
            i++;
        min_t = HT[i].w;     //将第一个 parent==0 的元素的 w 值赋给 min_t 
        min = i;
    
        //选出 w 最小且 p==0 的元素,并将其序号赋给 min
        for (; i <= k; i++)
        {
            if (HT[i].w < min_t&&HT[i].p == 0)
            {
                min_t = HT[i].w;
                min = i;
            }
        }
        //注意,选出weight最小的元素后,将其parent置1,使得下一次求第二小时将其排除在外
        HT[min].p = 1;
        return min;//返回下标     
    }
    void select(int k, int *min1, int *min2)//&为引用 
    {
        *min1 = min(k);
        *min2 = min(k);
    }
    void creatHT(int n)
    {
        if (n <= 1)  // 只有一个人没有父亲时结束
            return;
        int m = 2 * n - 1;
        for (int i = 1; i <= m; i++)
            HT[i].lc = 0, HT[i].rc = 0, HT[i].p = 0;  // 初始化为 0
        printf("请输入树叶的权值:
    ");
        for (int i = 1; i <= n; i++)
            scanf("%lf", &HT[i].w);    // 赋值
    
                                       // 从 p=0 的结点中 选择两小造新树
        int s1, s2;
        for (int i = n + 1; i <= m; i++)   // 产生 n-1 个结点
        {
            select(i - 1, &s1, &s2);     // 选择两小
    
            HT[s1].p = i; HT[s2].p = i;
            HT[i].lc = s1; HT[i].rc = s2;
            HT[i].w = HT[s1].w + HT[s2].w;
        }
    }
    void show(int n)
    {
        for (int i = 1; i <= 2 * n - 1; i++)
        {
            printf("权值为 %.1lf 的点的 父节点为 %2d ;子节点为 %2d %2d
    ", HT[i].w, HT[i].p, HT[i].lc, HT[i].rc);
        }
        printf("(其中结点为 0 代表没有这个结点.)
    ");
    }
    void HuffmanCoding(char** HC, int n)
    {
        //二维动态数组 用来保存指向每个霍夫曼编码串的指针(指针的指针)
        HC = (char**)malloc(n * sizeof(char *));
        if (!HC) {
            printf("HuffmanCode malloc failed!
    ");
            exit(-1);
        }
        //临时空间,用来保存每次求得的一个赫夫曼编码串
        char *code = (char *)malloc(n * sizeof(char));
        if (!code) {
            printf("code malloc failed!
    ");
            exit(-1);
        }
        code[n - 1] = '';//编码结束符,亦是字符数组的结束标志
    
        //下面求每个字符的赫夫曼编码
        for (int i = 1; i <= n; ++i) //从树叶向上回溯找根 
        {
            int now = i;            //定义当前访问的结点
            int fa = HT[i].p;      //定义当前节点的父节点
            int start = n - 1;      //每次编码的位置,初始为编码结束符的位置
    
            while (fa != 0)  // 向上找 根
            {
                if (HT[fa].lc == now)
                    code[--start] = '0';
                else
                    code[--start] = '1';
                now = fa;
                fa = HT[now].p;
            }
    
            //为第i个字符的编码串分配存储空间
            HC[i] = (char *)malloc((n - 1 - start + 1) * sizeof(char));//+1 是为了存'' 
            if (!HC[i]) {
                printf("HC[i] malloc failed!
    ");
                exit(-1);
            }
            strcpy(HC[i], code + start);//将编码串从最后的start编码~复制到HC上 
        }
        printf("各字符对应权值的霍夫曼编码为(按照输入顺序):
    ");
        for (int i = 1; i <= n; ++i)
        {
            printf("%.1lf: %s
    ", HT[i].w, HC[i]);
        }
    
        free(code);//释放保存每一个字符霍夫曼编码的临时空间
    }
    int main(void)
    {
        printf("请输入树叶的个数:");
        int n; scanf("%d", &n);
        creatHT(n);
        //show(n);
    
        char** HC = NULL;
        HuffmanCoding(HC, n);
    
        system("pause");
        return 0;
    }
    /*
    测试数据 1:
    5
    0.4 0.1 0.2 0.2 0.1
    */
    View Code

     ======== ======== ====== ===== ===== ===== ==== === == =

    鹊桥仙  秦观(宋)

    纤云弄巧,飞信传恨,银汉迢迢暗度。金风玉露一相逢,便胜却人间无数。

    柔情似水,佳期如梦,忍顾鹊桥归路。两情若是长久时,又岂在朝朝暮暮。

  • 相关阅读:
    centOS 6.5下升级mysql,从5.1升级到5.6
    利用PHP脚本辅助MySQL数据库管理5-检查异常数据
    利用PHP脚本辅助MySQL数据库管理4-两个库表结构差异比较
    利用PHP脚本辅助MySQL数据库管理3-删除重复表索引
    利用PHP脚本辅助MySQL数据库管理2-表主键表索引
    利用PHP脚本辅助MySQL数据库管理1-表结构
    Linux驱动设计——内存与IO访问
    Linux驱动设计—— 驱动调试技术
    Linux系统编程@终端IO
    Linux驱动设计—— 内外存访问
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/12988643.html
Copyright © 2011-2022 走看看