【邓俊辉版数据结构】之二叉树总结(二)

上一篇中主要是二叉树的一些介绍,本来想把Huffman编码放进去,介于篇幅过长,故移到这里。

1.Huffman编码

PFC编码以及解码

      以下基于二叉树结构,按照图5.28的总体框架。介绍PFC编码和解码算法的具体实现。

   

      如图5.29所示,若字符集E1和E2之间没有公共字符,且PFC编码方案对应于二叉树T1和T2,则通过引入一个根节点合并T1和T2 之后所得到的二叉树,就是对应于E1UE2的一种PFC编码方案。请注意,无论T1和T2的高度和规模是否相等,这一性质总是成  立。

      利用上述性质,可自底向上地构造PFC编码树。首先,由每一字符分别构造一颗单节点二叉树,并将它们视作一个森林。此后,反复从森林中取出两棵树并将其合二为一,如此,经|E| - 1步迭代之后。初始森林中的|E|棵树将合并成为一棵完整的PFC编码树。接下来,再将PFC编码树转译为编码表,以便能够根据待编码字符快捷确定与子对应的编码串。至此,对于任何待编码文本,通过反复查询编码表,即可高效地将其转化为二进制编码串。

       与编码过程相对应地,接受方也可以借助同一棵编码树来记录双方约定的编码方案。于是,每当接收到经信道传送过来的编码串后,(只要传送过程无误)接收方都可通过在编码树中反复从根节点出发做相应的漫游,依次完成对信息文本中各字符的解码。

总体框架

      以上编码和解码过程可描述为如下代码,这也是同类编码,解码算法的统一测试入

int main(int argc,char* argv[]) //PFC编码。解码算法统一测试入口
{
    PFCForest* forest = initForest(); //初始化PFC森林
    PFCTree*  tree = generateTree(forest);
    release(forest); //生成PFC编码树
    for(int i = 1;i < argc;i++) //对于命令行传入的每一明文串
    {
        Bintmap codeString;//二进制编码串
        int n = encode(table,codeString,argv[i]);//将根据编码表生成(长度为n)
        decode(tree,codeString,n);//利用编码树,对长度为n的二进制编码串解码(直接输出)
    }
    release(table);
    release(tree);
    return 0;     //释放编码表,编码树
}

数据结构的选取与设计

       如下面代码所示,这里使用向量实现PFC森林,其中各元素分别对应于一棵编码树:使用了跳转表式词典结构实现编码表。其中的词条各以某一待编码字符为关键码。以对应的编码串为数据项;使用位图Bitmap,实现各字符的二进制编码串。

//PFC编码使用的数据结构
#include "../BinTree/BinTree.h"//用BinTree实现PFC树
typedef BinTree<char> PFCTree;//PFC树

#include "../Vector/Vector.h"//用Vector实现PFC森林
typedef Vector<PFCTree*> PFCForest;//PFC森林

#include "../Bitmap/Bitmap.h"//使用位图结构实现二进制编码串
#include "../Skiplist/Skiplist.h" //引入Skiplist式词典结构实现
typedef Skiplist<char,char*> PFCTable; //PFC编码表,词条格式为:(key = 字符,value = 编码串)
#define  N_CHAR (0x80 - 0x20)  //只考虑可打印字符
                                                                                                                             
//初始化PFC森林
PFCForest* initForest()//PFC编码森林初始化
{
   PFCForest* forest = new PFCForest;//首先创建空森林,然后
   for(int i = 0;i < N_CHAR;i++)  //对每一个可打印字符[0x20,0x80)                                                                   
   {
       forest->insert(i,new PFCTree());//创建一棵对应的PFC编码树,初始时其中只包含对应的一个(叶,根)节点
       (*forest)[i]->insertAsRoot(0x20 + i);//只包含对应的一个(叶,根)节点
   }
   return forest;//返回包含N_CHAR棵树的森林,其中每棵树各包含一个字符
}

//构造PFC编码树
PFCTree* generateTree(PFCForest* forest) //构造PFC树
{
    srand((unsigned int )time(NULL)); //这里将随机取树合并,故先设置随机种子
    while(1 < forest->size())   //共做|forest| - 1次合并
    {
        PFCTree* s = new PFCTree;
        s->insertAsRoot('^');//创建新树(根标记为'^')‘
        Rank r1  = rand() % forest->size();//随机选取r1,且
        s->attackAsLC(s->root(),(*forest)[r1]);
        forest->remove(r1);//作为左子树后随即剔除
        Rank r2 = rand() % forest->size();//随机选取r2,且
        s->attackAsRC(s->root(),(*forest)[r2]);
        forest->remove(r2);//作为右子树接入后随即剔除
        forest->insert(forest->size(),s);//合并后的PFC重新植入PFC森林
    }
    return (*forest)[0];//至此,森林中尚存的最后一棵树,即全局PFC编码树
}

//生成PFC编码表
void generateCT //通过遍历获取各字符的编码
(Bitmap* code,int lenght,PFCTable* table,BinNodePosi(char) v)
{
    PFCTable* table = new PFCTable;//创建以Skiplist实现的编码表
    Bitmap* code = new Bitmap;//用于记录RPS的位图
    generateCT(code,0,table,tree->root());//遍历以获取各字符(叶节点)的RPS
    release(code);
    return table;//释放编码位图,返回编码表
}

//编码
int encode(PFCTable* table,Bitmap& codeString,char* s) //PFC编码算法
{
    int n = 0;
    for(size_t m = strlen(s),i = 0;i < m;i++) //对于明文s[]中的每个字符
    {
        char** pCharCode = table->get(s[i]);//取出其对应的编码串
        if(!pCharCode)
            pCharCode = table->get(s[i] + 'A' - 'a');//小写字母转为大写
        if(!pCharCode)
            pCharCode = table->get(' ');//无法识别的字符统一视作空格
        printf("%s",*pCharCode);//输出当前字符的编码
        for(size_t m = strlen(*pCharCode),j = 0;j < m;j++) //将当前字符的编码接入编码串
           '1' == *(*pCharCode + j) ? codeString.set(n++):codeString.clear(n++);
    }
    return n;//二进制编码串记录于codeString中,返回编码串总长
}

//解码
void decode(PFCTree* tree,Bitmap& code,int n) //PFC解码算法
{
    BinNodePosi(char) x = tree->root();//根据PFC编码树
    for(int i = 0;i < n;i++) //将编码(二进制为图)
    {
        x = code.test(i) ?  x->rChild :x->lChild;//转译为明码并
        if(IsLeat(*x))
        {
            printf("%c",x->data);
            x = tree->root(); //打印输出
        }    
    }
}

优化

      在介绍过PFC及其实现方法后,以下将就其编码效率做一分析,并设计出更佳的编码方法。

      同样地,我们依然暂且忽略硬件成本和信道误差等因素,而主要考查如何高效率地完成文本信息地编码和解码,不难理解,在计算资源固定的条件下,不同编码方法的效率主要体现于所生成二进制编码串的总长,或者更精确的,体现于二进制码长与原始文本长度的比率。

     那么。面对千变万化,长度不一的待编码文本,从总体上我们应该按照何种尺度来衡量这一因素呢?基于这一尺度,又该应用哪些数据结构来实现相关的算法呢?

2.最优编码树

      在实际的通讯系统中,信道的使用效率是个很重要的问题,这在很大程度上取决于编码算法本身的效率,比如,高效的编码算法生成的编码串应该尽可能地短。那么,如何做到这一点呢?

平均编码长度于叶节点平均深度

      由前文中得出地结论,字符x的编码长度|rps(x)|就是其对应的叶节点的深度depth(v(x))。于是,各字符的平均编码长度就是编码树T中各叶节点的平均深度(average leaf depth):

最优编码树

      同一字符集的所有编码方案中,编码长度最小者称作最优方案:对应编码树的平均编码长度也达到最短,故称之为最优编码树

猜你喜欢

转载自blog.csdn.net/qq_39218906/article/details/86505159
今日推荐