第六章 树与二叉树

一、二叉树的主要性质

叶子结点数:n0
单分支结点:n1
双分支结点:n2
总结点数:n0+n1+n2
总分支数:n1 + 2n2 = 总结点数 - 1 ,即n0 = n2 +1
叶子结点:双分支结点 + 1

二、二叉树的存储结构

//1.顺序存储结构:采用一维数组进行存储,适合完全二叉树,用于一般结构的二叉树则会浪费大量存储空间;
//2.链式存储结构:
typedef struct BTNode
{
    char date;
    struct BTNode *lchild;
    sturct BTNode *rchild; 
}BTNode;

三、二叉树的遍历算法

1.二叉树简单遍历算法

  • 子树的遍历顺序都是先左后右
  • 二叉树的前中、中后这两对遍历序列可以唯一确定一棵二叉树,前后则不能确定
//1.先序遍历
void preorder(BTNode *p)
{
    if(p!=NULL)
    {
        Visit(p);             //访问根节点
        preorder(p->lchild);  //先遍历左子树
        preorder(p->rchild);  //后遍历右子树
    }
}

//2.中序遍历
void inorder(BTNode *p)
{
    if(p!=NULL)
    {
        inorder(p->lchild);
        Visit(p);
        inorder(p->rchild);
    }
}

//3.后序遍历
void posorder(BTNode *p)
{
    if(p!=NULL)
    {
        posorder(p->lchild);
        posorder(p->rchild);
        Visit(p);
    }
}

2.例程

1.将表达式(a-(b+c))*(d/e)存储在以二叉链表为结构的二叉树中

//明显是要用后序遍历
int op(int a,int b,char Op)
{
    if(op=='+')return a+b;
    if(op=='-')return a-b;
    if(op=='*')return a*b;
    if(op=='/')
    {
        if(b==0)
        {
            cout<<"ERROR"<<endl;
            return 0;
        }
        else return a/b;
    }
}

int com(BTNode *p)
{
    int A,B;
    if(p!=NULL)
    {
        if(p->lchild!=NULL&&p->rchild!=NULL)
        {
            A=com(p->lchild);
            B=com(p->rchild);
            return op(A,B,p->date);
        }
        else return p->date-'0';
        //如果当前结点的左右子树都为空,则为数值,直接返回
        //p->date-'0'是将字符型数字转换成整型数字
    }
    else return 0;//空树则返回0  
}

2.求二叉树的深度

//左子树的深度为LD,右子树的深度为RD,用后序遍历
int getDepth(BTNode *p)
{
    int LD,RD;
    if(p==NULL)return 0;
    else
    {
        LD=getDepth(p->lchild);
        RD=getDepth(p->rchild);
        return (LD>RD?LD:RD)+1;//左子树和右子树的最大值加1
    }
}

3.查找date域值是否存在等于key的结点,存在则将q指向该结点,否则q=NULL

//几种遍历都可以,这里采用先序遍历
void search(BTNode *p,BTNode *&q,int key)
{
    if(p!=NULL)
    {
        if(p->date==key) q=p;
        else
        {
            search(p->lchild,q,key);
            search(p->rchild,q,key);
        }
    }
}

//改进:左子树查找到满足要求的结点后,无须继续查找右子树
//称加入的这种操作称为截枝操作
void search(BTNode *p,BTNode *&q,int key)
{
    if(p!=NULL)
    {
        if(p->date==key)q=p;
        else
        {
            search(p->lchild,q,key)
            //截枝操作
            if(q==NULL)search(p->rchild,q,key);
        }
    }
}

4.输出先序序列中第k个结点的值

int n=0;
//先序
void trave(BTNode *p,int k)
{
    if(p!=NULL)
    {
        ++n;
        if(k==n)
        {
            cout<<p->date<<endl;
            return;
        }
        trave(p->lchild,k);
        trace(p->rchild,k);
    }
}

//中序
void trave(BTNode *p,int k)
{
    if(p!=NULL)
    {
        trave(p->lchild,k);
        ++n;
        if(k==n)
        {
            cout<<p->date<<endl;
            return;
        }
        trave(p->rchild,k);
    }
}

//后序
void trave(BTNode *p,int k)
{
    if(p!=NULL)
    {
        trave(p->lchild,k);
        trave(p->rchild,k);
        ++n;
        if(k==n)
        {
            cout<<p->date<<endl;
            return;
        }   
    }
}

5.先序遍历序列得到pre[l1,… ,r1],中序遍历序列得到in[l2,… ,r2],元素类型为char,并且二叉树中的数值互不相等,求由这两种序列构造的二叉树

/*
思路:
先序遍历的第一个元素即为根结点数值,在中序遍历中找到a,由a将中序遍历
序列分成两个子序列,左边构成左子树,右边构成右子树;
再对左右子树采用同样的处理方式,直到子序列只有1一个元素时,构造结束。
*/
BTNode *CreateBT(char pre[],char in[],int l1,int r2,int l2,int r2)
{
    BTNode *s;
    int i;

    //此语句不能去掉,代表处理序列长度为0的情况,而in[]这个数组只是一个参照数组
    if(l1>r1)return NULL;

    //申请一个结点空间
    s=(BTNode*)malloc(sizeof(BTNode));
    s->lchild=s->rchild=NULL;

    //通过找到in中的i确定左右子树范围
    for(i=r2;i<=r2;++i)
    {
        if(in[i]==pre[l1])break;
    }
    s->date=in[i];

    //对于中序遍历,[l2,i-1就是左子树,结点个数为i-1-l2,[i+1,r2]就是右子树,结点个数为r2-i-1
    //对于先序遍历,参照中序遍历,[l1+1,l1+i-l2]为左子树,[l1+i-l2+1,r1]为右子树
    //然后将左右子树根结点连接在s的左右指针域上
    s->lchild=CreateBT(pre,in,l1+1,l1+i-l2,l2,i-1);
    s->rchild=CreatBT(pre,in,l1+i-l2+1,r1,i+1,r2);
    //递归结束返回二叉树根结点s
    return s;
}

3.层次遍历(很重要)

  • 即对二叉树的每一层进行遍历,需要建立一个循环队列
  • 将二叉树头结点入队,然后出队列,访问该结点
  • 如果有左子树,则左子树根节点入队
  • 如果有右子树,则右子树根节点入队
  • 然后出队列,对出队结点进行访问

1.具体算法

void level (BTNode *p)
{
    int front, rear;
    BTNode *que[maxSize];
    front=rear=0;
    BTNode *q;
    if(p!=NULL)
    {
        rear=(rear+1)%maxSize;
        que[rear]=p;           //根节点入队
        while(front!=rear)     //对列不为空进行循环
        {
            front=(front+1)%maxSize;
            q=que[front];      //队头结点出队
            Visit(q);
            if(q->lchild!=NULL)//处理左子树
            {
                rear=(rear+1)%maxSize;
                que[rear]=p->lchild;
            }
            if(q->rchild!=NULL)//处理右子树
            {
                rear=(rear+1)%maxSzie;
                que[rear]=p->rchild;
            }
        }
    }
}

2.例程

//要求:求二叉树的宽度
//1.对于非空树,根据上面算法,如果我们知道了当前结点的层号,就可以知道其左右孩子的层号
//2.考虑存储队列的数组足够长,队头元素不会被覆盖,rear=(rear+1)%maxSize和front=(front+1)%maxSize可以直接写为++rear和++front
//3.第一点知道每个结点的层号,第二点用数组存放结点,于是就可以知道最多的层上的结点

//定义顺序非循环队列元素,存储结点指针和结点层次号
typedef struct
{
    BTNode *p;
    int lno;
}St;

int maxNode(BTNode *b)
{
    //定义顺序非循环队列
    St que[maxSize];
    int front,rear;
    front=rear=0;

    int Lno,i,j,n,max;
    BTNode *q;

    if(b!=NULL)
    {
        ++rear;
        que[rear].p=b;   //树根结点入队
        que.lno=1;       //树根结点层号为1
                         //相当于初始化
        while(front!=rear)
        {
            ++front;
            q=que[front].p;
            //存放当前结点层次号,便于得知左右孩子的层次号
            Lno=que[front].lno;
            if(q->lchild!=NULL)
            {
                ++rear;
                que[rear].p=q->lchild;
                que[rear].lno=Lno+1;
            }
            if(q->rchild!=NULL)
            {
                ++rear;
                que[rear].p=q->rchild;
                que[rear].lno=Lno+1;
            }
        }
        //循环结束Lno保存这颗二叉树的最大层数
        max=0;
        for(i=1;i<=Lno;++i)
        {
            n=0;
            //遍历整个顺序循环队列,查找具有相同层次号的结点个数
            for(j=1;j<=rear;++j)
            {
                if(que[j].lno==i)++n;
                if(max<n)max=n;
            }
        }
        return max;     
    }
    else return 0;  //空树直接返回0
}

四、遍历算法改进

  • 改进缘由
    上述深度优先遍历算法都是用递归函数实现,系统需要调用栈进行处理(凡是递归函数,系统都自动调用栈处理诸于保护现场和恢复现场等操作)。
  • 关于递归中的系统调用栈非递归用户自己定义的栈
    1.递归函数所申请的系统栈,是一个所有递归函数都通用的栈。对于二叉树深度优先遍历算法,系统除了记录访问过的结点信息之外,还有其他信息需要记录,以实现函数的递归调用;
    2.用户自定义的栈仅仅保存了遍历所需的结点信息,是对遍历算法的一个针对性设计,对于遍历算法,显然要比递归函数通用的系统栈更高效;
    3.考研视角上,相同的算法,递归比非递归函数低效,但实际应用并不是。

1.先序遍历

思路:将树根结点入栈,然后出栈,将树根结点的右孩子先入栈,左孩子后入栈(先入栈后访问),循环直至遍历结束

void preorderNonrecurision(BTNode *bt)
{
    if(bt!=NULL)
    {
        //定义顺序栈
        BTNode *Stack[maxSize];
        int top=-1;

        BTNode *p;
        Stack[++top]=bt;//根结点入栈
        while(top!=-1)
        {
            p=Stack[top--];//出栈并输出栈顶结点
            Visit(p);
            if(p->rchild!=NULL)Stack[++top]=p->rchild;
            if(p->lchild!=NULL)Stack[++top]=p->lchild;
        }
    }
}

2.中序遍历

思路:根节点入栈,如果栈顶结点左孩子存在,则左孩子入栈;如果左孩子不存在,则出栈并输出栈顶结点;然后检查右孩子是否存在,存在,则右孩子入栈,栈空结束算法

void inorderNorecursion(BTNode *bt)
{
    if(bt!=NULL)
    {
        BTNode *Stack[maxSize];
        int top = -1;
        BTNode *p;
        p=bt;
        //可能最后存在右孩子但此时栈空的情况
        while(top!=-1||p!=NULL)
        {
            while(p!=NULL)   //左孩子存在则左孩子入栈
            {
                Stack[++top]=p;
                p=p->lchild;
            }
            if(top!=-1)
            {
                p=Stack[top--];
                Visit[p];
                p=p->rchild;
            }
        }
    }
}

3.后序遍历

思路:逆后序遍历是先序遍历过程中对左右子树遍历顺序交换所得的结果,所以只要将之前的非循环先序遍历的左右子树遍历顺序交换得到逆后序遍历,然后将逆后序逆序就得到后序遍历。即把逆后序遍历的元素依次出栈stack1,然后入栈stack2,此时栈2自顶向下的顺序即为后序遍历顺序,再依次出栈即可

//与先序进行类比,基本一样
void posorderNonrecursion(BTNode *bt)
{
    if(bt!=NULL)
    {
        //定义两个栈
        BTNode *Stack[maxSize];int top1=-1;
        BTNode *Stack[maxSize];int top2=-1;
        BTNode *p=NULL;
        Stack1[++top1]=bt;
        while(top1 != -1)
        {
            p=Stack1[top1--];
            Stack2[top2++]=p;
            //左右子树遍历顺序变化
            if(p->lchild)Stack1[++top1]=p->lchild;
            if(p->rchild)Stack1[++top2]=p->rchild;
        }
        //栈2元素出栈
        while(top2 != -1)
        {
            p = Stack2[top2--];
            Visit(p);
        }
    }
}

五、线索二叉树

1.概念描述

  • 线索二叉树将上述的用户栈也省掉了,提高了一定的执行效率;
  • n个结点的二叉树有n+1个空链域
    链域一共有2*N个,(每个点有两个链域),对于除了根节点以外的每个点都是有一个父亲节点,所以一共有N-1个指针指向某个节点,形成N-1个有东西的链域(减1即是父亲节点),所以一共有2*N-(N-1)=N+1个链域没有指向任何东西;
  • 这种高效性体现在哪里
    二叉树被线索化后近似于一个线性结构,分支结构的遍历操作转化为近似于线性结构的遍历操作,通过线索的辅助使得寻找当前结点前驱或者后继的平均效率大大提高;
    由于每个结点多了两个ltag和rtag,它们导致的额外开销是否比非递归遍历算法中的栈空间开销小,在不同的场合很难确定,因此这里不提空间利用率的提高;
typedef struct TBTNode
{
    char date;
    int ltag,rtag;      //线索标记
    struct TBTNode *lchild;
    struct TBTNode *rchild;
}

思路:
左线索指针指向当前结点在中序遍历序列中的前驱结点,右线索指针指向后继结点;
定义一个p指针指向当前正在访问的结点,pre指向p的前驱结点(prior),p的左线索如果存在则让其指向pre,pre的右线索如果存在,则让其指向p。
当p要离开一个访问的结点时,pre指向p,当p来到一个新结点时,pre指向此时p所指结点的前驱结点。

2.具体算法描述

//1.中序遍历对二叉树进行线索化算法
void InThread(TBTNode *p,TBTNode *&pre)
{
    if(p!=NULL)
    {
        InThread(p->lchild,pre);
        if(p->lchild==NULL)//没有左孩子,存在左线索
        {
            p->lchild=pre;
            p->ltag=1;
        }
        if(pre!=NULL&&pre->rchild==NULL)//没有右孩子,则右线索存在
        {
            pre->rchild=p;
            pre->rtag=1;
        }
        //准备处理下一个结点
        pre=p;
        InThread(p->rchild,pre);
    }
}

//中序遍历建立中序线索二叉树
void creatInThread(TBTNode *root)
{
    TBTNode *pre=NULL;       //前驱结点指针
    if(root!=NULL)
    {
        InThread(root,pre);
        pre->rchild=NULL//非空二叉树线索化
        pre->rtag=1;         //后处理中序最后一个结点
    }
}

//遍历中序线索二叉树
//求以p为根的中序线索二叉树中,中序序列下的第一个结点
TBTNode *First(TBTNode *p)
{
    while(p->ltag==0)p=p->lchild;
    return p;
}

//求结点p在中序下的后继结点
TBTNode *Next(TBTNode *p)
{
    //如果右孩子存在,则返回右子树第一个中序需要访问下的结点
    if(p->rtag==0)return First(p->rchild);
    //右孩子不存在,则直接指向后继结点
    else return p->rchild;
}
//求最后一个结点
TBTNode *Last(TBTNode *p)
{
    while(p->rtag==0)p=p->rchild;
    return p;
}
//求结点p的前驱结点
TBTNode *Prior(TBTNode *p)
{
    if(p->ltag==0)return Last(p->lchild);
    else return p->lchild;
}

//中序线索二叉树上执行中序遍历算法
void Inorder
{
    for(TBTNode *p=First(root);p!=NULL;p=Next(p))
        Visit(p);
}
//2.前序线索二叉树
void preThread(TBTNode *p,TBTNode *&pre)
{
    if(p!=NULL)
    {
        if(p->lchild==NULL)
        {
            p->lchild=pre;
            p->ltag=1;
        }
        if(pre!=NULL&&pre->rchild==NULL)
        {
            pre->rchild=p;
            pre->rtag=1;
        }
        pre=p;
        //左右指针不是线索才能继续递归
        if(p->ltag==0)preThread(p->lchild,pre);
        if(p->rtag==0)preThread(p->rchild,pre);
    }
}

//在前序线索二叉树上执行前序算法
void preorder(TBTNode *root)
{
    if(!root=NULL)
    {
        TBTNode *p=root;
        while(p!=NULL)
        {
            //左指针不是线索,则边访问边左移
            while(p->ltag==0)
            {
                Visit(p);
                p=p->lchild;
            }
            Visit(p);
            //左孩子不存在,右孩子不管是不是非空都指向其后继结点
            p->rchild;
        }
    }
}
//3.后序线索二叉树
void postThread(TBTNode *p,TBTNode *&pre)
{
    if(p!=NULL)
    {
        postThread(p->lchild,pre);
        postThread(p->rchild,pre);
        if(p->lchild==NULL)
        {
            p->lchild=pre;
            p->ltag=1;
        }
        if(pre!=NULL&&pre->rchild==NULL)
        {
            pre->rchild=p;
            pre->rtag=1;
        }
        pre=p;
    }
}

/*
简要描述:
1) 如果结点x是二叉树的根,则其后继为空;
2) 如果结点x是其双亲的右孩子,或者是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲;
3) 如果x是其双亲的左孩子,且其双亲有右子树,则其后继结点为双亲右子树按后序遍历列出的第一个结点。
*/

六、相关树的转换和概念描述

1.树与二叉树的转换

  • 两个概念:
    1)用二叉链表存储二叉树,结点一个指针指向左孩子(lchild),另一个指向右孩子(rchild);
    2) 用二叉链表存储树,结点中的一个指针指向孩子(child),另一个指向兄弟结点(sibling),这就是孩子兄弟存储结构
  • 二叉树转换为树就是上述的逆过程

2.森林和二叉树的转换

  • 其实就是上述树与二叉树的拓展,直接将头结点连起来就可以了

3.树与森林的遍历

  • 树的遍历只有先序和后序遍历,与二叉树中的先序后序相同
  • 森林的遍历也只有先序和后序遍历:
    先序遍历:先访问森林的第一颗树的根结点,然后先序遍历第一棵树的根结点的子树,最后先序遍历森林中除了第一棵树以外的其他树;
    后序遍历:后序遍历第一颗树的根结点的子树,然后访问第一棵树的根结点,最后后序遍历森林中除第一棵树以外的森林;
    其实就是一棵树接着一棵树遍历,完全一样。

4.赫夫曼树和赫夫曼编码

  • 树的路径长度:从根到每个结点的路径长度之和;
  • 树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和;
  • 赫夫曼树又叫最优二叉树,特点是带权路径最短
  • 3.赫夫曼树构造方法
    1)描述:
    给定n个权值的结点,用这n个权值来构造赫夫曼树算法:
    (1)将这n个权值看作只有根结点的n棵二叉树,构成的集合记为F;
    (2)从F中选出两棵根结点的权值最小的树作为左右子树a、b,构造一棵新的二叉树c,即新的二叉树的结点的权值为左右子树的权值之和
    (3)从F中删除a、b,加入新构造的c;
    (4)重复(2)(3),直至F只剩下一棵树为止。
  • 4.赫夫曼树编码
    1)对于包含同一内容的文件有多种存储方式,我们可以找出一种最节省空间的存储方式,如.zip.jpeg等文件的底层技术都用到了赫夫曼编码;
    2)编码方式是不定长的,解码问题的处理:采用前缀码解决。在前缀码中,任一字符的编码串都不是另一字符编码串的前缀
    3)编码规则:被编码的字符处在叶子结点上,而根通往任一叶子结点的路径都不可能是通往其余叶子结点的子路径,所以可以使用根通往叶子的路径对该叶子结点进行编码;
    4)采用赫夫曼树构造的前缀码,其树的带权路径长度是最短的,即赫夫曼编码产生的是最短前缀码(一般左子树权值小于右子树权值);
  • 5.赫夫曼n叉树
    1)当发现构造不了三叉树的时候,可以采用补上一个权值为0的结点;
    2)构造方法:
    与赫夫曼二叉树构造方法相同,左边是权值小的结点,右边是权值大的结点。
1. 一棵二叉树的顺序存储情况如下: 中,度为2的结点数为( )。 A.1 B.2 C.3 D.4 2. 一棵“完全二叉树结点数为25,高度为( )。 A.4 B.5 C.6 D.不确定 3.下列说法中,( )是正确的。 A. 二叉树就是度为2的 B. 二叉树中不存在度大于2的结点 C. 二叉树是有序 D. 二叉树中每个结点的度均为2 4.一棵二叉树的前序遍历序列为ABCDEFG,它的中序遍历序列可能是( )。 A. CABDEFG B. BCDAEFG C. DACEFBG D. ADBCFEG 5.线索二叉树中的线索指的是( )。 A.左孩子 B.遍历 C.指针 D.标志 6. 建立线索二叉树的目的是( )。 A. 方便查找某结点的前驱或后继 B. 方便二叉树的插入与删除 C. 方便查找某结点亲 D. 使二叉树的遍历结果唯一 7. 有abc三个结点的右单枝二叉树的顺序存储结构应该用( )示意。 A. a b c B. a b ^ c C. a b ^ ^ c D. a ^ b ^ ^ ^ c 8. 一颗有2046个结点的完全二叉树的第10层上共有( )个结点。 A. 511 B. 512 C. 1023 D. 1024 9. 一棵完全二叉树一定是一棵( )。 A. 平衡二叉树 B. 二叉排序 C. 堆 D. 哈夫曼 10.某二叉树的中序遍历序列和后序遍历序列正好相反,则该二叉树一定是( )的二叉树。 A.空或只有一个结点 B.高度等于其结点数 C.任一结点无左孩子 D.任一结点无右孩子 11.一棵二叉树的顺序存储情况如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A B C D E 0 F 0 0 G H 0 0 0 X 结点D的左孩子结点为( )。 A.E B.C C.F D.没有 12.一棵“完全二叉树结点数为25,高度为( )。 A.4 B.5 C.6 D.不确定 二、填空题(每空3分,共18分)。 1. 的路径长度:是从根到每个结点的路径长度之和。对结点数相同的来说,路径长度最短的是 完全 二叉树。 2. 在有n个叶子结点的哈夫曼中,总结点数是 2n-1 。 3. 在有n个结点的二叉链表中,值为非空的链域的个数为 n-1 。 4. 某二叉树的中序遍历序列和后序遍历序列正好相反,则该二叉树一定是 任一结点无左孩子 的二叉树。 5. 深度为 k 的二叉树最多有 个结点,最少有 k 个结点。 三、综合题(共58分)。 1. 假定字符集{a,b,c,d,e,f }中的字符在电码中出现的次数如下: 字符 a b c d e f 频度 9 12 20 23 15 5 构造一棵哈夫曼(6分),给出每个字符的哈夫曼编码(4分),并计算哈夫曼的加权路径长度WPL(2分)。 (符合WPL最小的均为哈夫曼,答案不唯一) 哈夫曼编码: 2. 假设用于通信的电文由字符集{a,b,c,d,e,f,g}中的字符构成,它们在电文中出现的频率分别为{0.31,0.16,0.10,0.08,0.11,0.20,0.04}。要求: (1)为这7个字符设计哈夫曼(6分)。 (2)据此哈夫曼设计哈夫曼编码(4分)。 (3)假设电文的长度为100字符,使用哈夫曼编码比使用3位二进制数等长编码使电文总长压缩多少?(4分) (1) 为这7个字符设计哈夫曼为(符合WPL最小的均为哈夫曼,答案不唯一): (2) 哈夫曼编码为: a:01;b:001;c:100;d:0001;e:101;f:11;g:0000 (3) 假设电文的长度为100字符,使用哈夫曼编码比使用3位二进制数等长编码使电文总长压缩多少? 采用等长码,100个字符需要300位二进制数,采用哈夫曼编码发送这100个字符需要261二进制位,压缩了300-261=39个字符。压缩比为39/300=13%。 3. 二叉数T的(亲到孩子的)边集为: { <A,B>, <A,C>, <D,A>, <D,E>, <E,F>, <F,G> } 请回答下列问题: (1)T的根结点(2分): (2)T的叶结点(2分): (3)T的深度(2分): (4)如果上述列出边集中,某个结点只有一个孩子时,均为其左孩子;某个结点有两个孩子时,则先列出了连接左孩子的边后列出了连接右孩子的边。画出该二叉树其及前序线索(6分)。 (1)T的根结点 (2)T的叶结点 : (3)T的深度 : (4)该二叉树其及前序线索为: 4.现有以下按前序和中序遍历二叉树的结果: 前序:ABCEDFGHI 中序:CEBGFHDAI 画出该二叉树的逻辑结构图(5分),并在图中加入中序线索(5分)。 5.有电文:ABCDBCDCBDDBACBCCFCDBBBEBB。 用Huffman构造电文中每一字符的最优通讯编码。画出构造的哈夫曼,并给出每个字符的哈夫曼编码方案。(符合WPL最小的均为哈夫曼,答案不唯一) (1)构造哈夫曼(6分): (2)哈夫曼编码方案(4分):
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值