第五章 树和二叉树

树和二叉树的定义

树的定义

树(Tree) 是n(n≥0)个节点的有限集,它或为空树(n = 0),或为非空树。

对于非空树T:

  • 有且仅有一个称之为根的节点;
  • 除根节点以外的其余节点可分为m(m>0)个互不相交的有限集T1, T2, …, Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

在这里插入图片描述
树的结构定义是一个递归的定义,即在树的定义中又用到树的定义,它道出了树的固有特性。

树还可有其他表示形式,图5.2所示为图5.1(b)中树的各种表示。

  • 其中图5.2(a)是以嵌套集合(即一些集合的集体,对于其中任何两个集合,或者不相交,或者一个包含另一个)的形式表示的;
  • 图5.2(b)是以广义表的形式表示的,根作为由子树森林组成的表的名字写在表的左边;
  • 图5.2(c)用的是凹入表示法(类似书的编目)。

树的基本术语

  1. 节点:树中的一个独立单元。包含一个数据元素及若干指向其子树的分支。
  2. 节点的度:节点拥有的子树数称为节点的度。
  3. 树的度:树的度是树内各节点度的最大值。
  4. 叶子:度为0的节点称为叶子或终端节点。
  5. 非终端节点:度不为0的节点称为非终端节点或分支节点。除根节点之外,非终端节点也称为内部节点。
  6. 双亲和孩子:节点的子树的根称为该节点的孩子,相应地,该节点称为孩子的双亲。
  7. 兄弟:同一个双亲的孩子之间互称兄弟。
  8. 祖先:从根到该节点所经分支上的所有节点。
  9. 子孙:以某节点为根的子树中的任一节点都称为该节点的子孙。
  10. 10.层次:节点的层次从根开始定义,根为第一层,根的孩子为第二层。树中任一节点的层次等于其双亲节点的层次加1。
  11. 堂兄弟:双亲在同一层的节点互为堂兄弟。
  12. 树的深度:树中节点的最大层次称为树的深度或高度。
  13. 有序树和无序树:如果将树中节点的各子树看成从左至右是有次序的(不能互换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子。
  14. 森林:m(m≥0)棵互不相交的树的集合。对树中每个节点而言,其子树的集合即为森林。由此,也可以用森林和树相互递归的定义来描述树。

在这里插入图片描述

二叉树的定义

二叉树(Binary Tree) 是n(n≥0)个节点所构成的集合,它或为空树(n = 0),或为非空树。

对于非空树T:
(1)有且仅有一个称之为根的节点;
(2)除根节点以外的其余节点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树。

二叉树与树一样具有递归性质,二叉树与树的区别主要有以下两点:
(1)二叉树每个节点至多只有两棵子树(二叉树中不存在度大于2的节点);
(2)二叉树的子树有左右之分,其次序不能任意颠倒。

二叉树可以有5种基本形态

在这里插入图片描述

案例引入

  • 数据压缩问题。
  • 利用二叉树求解表达式的值。

树和二叉树的抽象数据类型定义

  • 树的抽象数据类型定义:
    在这里插入图片描述
    在这里插入图片描述

  • 二叉树的抽象数据类型定义如下 :
    在这里插入图片描述
    在这里插入图片描述

二叉树的性质和存储结构

二叉树的性质

二叉树具有下列重要特性。

  1. 性质1 在二叉树的第i层上至多有2i−1(i≥1)个节点。
  2. 性质2 深度为k的二叉树至多有2k−1(k≥1)个节点。
  3. 性质3 对任何一棵二叉树T,如果其终端节点数为n0,度为2的节点数为n2,则n0 = n2 + 1。

两种特殊形态的二叉树:

  • 满二叉树:深度为k且含有2k−1个节点的二叉树。
    在这里插入图片描述
    图所示是一棵深度为4的满二叉树。

满二叉树的特点是:每一层上的节点数都是最大节点数,即每一层i的节点数都具有最大值2i−1
可以对满二叉树的节点进行连续编号,约定编号从根节点起,自上而下,自左至右。

  • 完全二叉树: 深度为k的、有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1至n的节点一一对应时,称之为完全二叉树。
    在这里插入图片描述
    图所示为一棵深度为4的完全二叉树。
    完全二叉树的特点是:
    (1)叶子节点只可能在层次最大的两层上出现;
    (2)对任一节点,若其右分支下的子孙的最大层次为L,则其左分支下的子孙的最大层次必为L或L + 1。
    在这里插入图片描述
    性质4 具有n个节点的完全二叉树的深度为 ⌊log2n⌋ + 1
    性质5 如果对一棵有n个节点的完全二叉树(其深度为⌊log2n⌋ + 1)的节点按层序编号(从第1层到第 ⌊log2n⌋ + 1层,每层从左到右),则对任一节点i(1≤i≤n),以下结论成立。
    • 如果i = 1,则节点i是二叉树的根,无双亲;如果i>1,则其双亲PARENT(i)是节点⌊i/2⌋。
    • 如果2i>n,则节点i无左孩子(节点i为叶子节点);否则其左孩子LCHILD(i)是节点2i。
    • 如果2i + 1>n,则节点i无右孩子;否则其右孩子RCHILD(i)是节点2i + 1。
      在这里插入图片描述

二叉树的存储结构

二叉树的存储结构也可采用顺序存储和链式存储两种方式。

  1. 顺序存储结构
//-----二叉树的顺序存储表示-----
#define MAXTSIZE 100          //二叉树的最大节点数
typedef TElemType SqBiTree[MAXTSIZE]; //0号单元存储根节点
SqBiTree bt ;
  1. 链式存储结构
    在这里插入图片描述

由二叉树的定义得知,二叉树的节点[见图5.9(a)]由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的节点至少包含3个域:数据域和左、右指针域。

有时,为了便于找到节点的双亲,还可在节点结构中增加一个指向其双亲节点的指针域,如图5.9(c)所示。利用这两种节点结构所得二叉树的存储结构分别称为二叉链表三叉链表

在这里插入图片描述

//- - - - -二叉树的二叉链表存储表示- - - - -
typedef struct BiTNode{
  TElemType data;         //节点数据域
  struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;

遍历二叉树和线索二叉树

遍历二叉树

  1. 遍历二叉树算法描述

遍历二叉树(traversing binary tree) 是指按某条搜索路径巡访树中每个节点,使得每个节点均被访问一次,而且仅被访问一次。

遍历的实质是对二叉树进行线性化,即遍历的结果是将非线性结构树中的节点排成一个线性序列。

若限定先左后右,则只有3种遍历方案:先(根)序遍历、中(根)序遍历和后(根)序遍历。

先序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空;否则
(1)访问根节点;
(2)先序遍历左子树;
(3)先序遍历右子树。

中序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空;否则
(1)中序遍历左子树;
(2)访问根节点;
(3)中序遍历右子树。

后序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空;否则
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根节点。

前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。

  • 中序遍历的递归算法
void InOrderTraverse(BiTree T)
{//中序遍历二叉树T的递归算法
 if(T) //若二叉树非空
 {
   InOrderTraverse(T->lchild); //中序遍历左子树
   cout<<T->data; //访问根节点
   InOrderTraverse(T->rchild); //中序遍历右子树
 }
}
  • 中序遍历的非递归算法

【算法步骤】
① 初始化一个空栈S,指针p指向根节点。
② 申请一个节点空间q,用来存放栈顶弹出的元素。
③ 当p非空或者栈S非空时,循环执行以下操作:
如果p非空,则使p进栈,p指向该节点的左孩子;
如果p为空,则弹出栈顶元素并访问根节点,将p指向该节点的右孩子。

void InOrderTraverse(BiTree T)
{//中序遍历二叉树T的非递归算法
 InitStack(S);p=T;
 q=new BiTNode;
 while(p||!StackEmpty(S))
 {
   if(p) //p非空
   {
     Push(S,p); //根指针进栈
     p=p->lchild; //根指针进栈,遍历左子树
   }
   else //p为空
   {
     Pop(S,q); //退栈
     cout<<q->data; //访问根节点
     p=q->rchild; //遍历右子树
   }
 } // while
}

在这里插入图片描述

  1. 根据遍历序列确定二叉树
    由二叉树的先序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树。
  2. 二叉树遍历算法的应用
    • 创建二叉树的存储结构——二叉链表
      按照先序遍历的顺序建立二叉链表

    【算法步骤】
    ① 查找字符序列,读入字符ch。
    ② 如果ch是一个“#”字符,则表明该二叉树为空树,即T为NULL;否则
    执行以下操作:
     申请一个节点空间T;
     将ch赋给T->data;
     递归创建T的左子树;
     递归创建T的右子树。

void CreateBiTree(BiTree &T)
{//按先序次序输入二叉树中节点的值(单字符),创建二叉链表表示的二叉树T
 cin>>ch;
 if(ch=='#') T=NULL; //递归结束,建空树
 else //递归创建二叉树
 {
   T=new BiTNode; //生成根节点
   T->data=ch; //根节点数据域置为ch
   CreateBiTree(T->lchild); //递归创建左子树
   CreateBiTree(T->rchild); //递归创建右子树
 } //else
}
  • 复制二叉树

复制二叉树就是利用已有的一棵二叉树复制得到另外一棵与其完全相同的二叉树。根据二叉树的特点,复制步骤如下:若二叉树不空,则首先复制根节点,这相当于二叉树先序遍历算法中访问根节点的语句;然后分别复制二叉树根节点的左子树和右子树,这相当于先序遍历中递归遍历左子树和右子树的语句。因此,复制函数的实现与二叉树先序遍历的实现非常类似。

【算法步骤】
如果是空树,递归结束,否则执行以下操作:
 申请一个新节点空间,复制根节点;
 递归复制左子树;
 递归复制右子树。

void Copy(BiTree T,BiTree &NewT)
{//复制一棵和T完全相同的二叉树
 if(T==NULL) //如果是空树,递归结束
 {
   NewT=NULL;
   return; 
}
 else
 {
   NewT=new BiTNode;
   NewT->data=T->data; //复制根节点
   Copy(T->lchild,NewT->lchild); //递归复制左子树
   Copy(T->rchild,NewT->rchild); //递归复制右子树
  } //else
}
  • 计算二叉树的深度

二叉树的深度为树中节点的最大层次,二叉树的深度为左右子树深度的较大者加1。

【算法步骤】
如果是空树,递归结束,深度为0,否则执行以下操作:
 递归计算左子树的深度记为m;
 递归计算右子树的深度记为n;
 如果m大于n,二叉树的深度为m+1,否则为n+1。

int Depth(BiTree T)
{//计算二叉树T的深度
 if(T==NULL) return 0; //如果是空树,深度为0,递归结束
 else
 {
   m=Depth(T->lchild); //递归计算左子树的深度记为m
   n=Depth(T->rchild); //递归计算右子树的深度记为n
   if(m>n) return(m+1); //二叉树的深度为m与n的较大者加1
   else return(n+1);
 }
}
  • 统计二叉树中节点的个数

如果是空树,则节点个数为0,递归结束;否则,节点个数为左子树的节点个数加上右子树的节点个数再加上1。

int NodeCount(BiTree T)
{//统计二叉树T中节点的个数
 if(T==NULL) return 0; //如果是空树,则节点个数为0,递归结束
 else return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
 //否则节点个数为左子树的节点个数+右子树的节点个数+1
}

手算遍历二叉树

在这里插入图片描述

线索二叉树

  1. 线索二叉树的基本概念

当以二叉链表作为存储结构时,只能找到节点的左、右孩子信息,而不能直接得到节点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到,为此引入线索二叉树来保存这些在动态过程中得到的有关前驱和后继的信息。

在这里插入图片描述

//- - - - -二叉树的二叉线索存储表示- - - - - 
typedef struct BiThrNode
{
 TElemType data ;
 struct BiThrNode *lchild,*rchild ; //左右孩子指针
 int LTag,RTag ; //左右标志
}BiThrNode,*BiThrTree ;

以这种节点结构构成的二叉链表作为二叉树的存储结构,叫作线索链表,其中指向节点前驱和后继的指针,叫作线索。加上线索的二叉树称之为线索二叉树(Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫作线索化

在这里插入图片描述

  1. 构造线索二叉树
  • 以节点p为根的子树中序线索化

【算法步骤】
① 如果p非空,左子树递归线索化。
② 如果p的左孩子为空,则给p加上左线索,将其LTag置为1,让p的左孩子
指针指向pre(前驱);否则将p的LTag置为0。
③ 如果pre的右孩子为空,则给pre加上右线索,将其RTag置为1,让pre的右
孩子指针指向p(后继);否则将pre的RTag置为0。
④ 将pre指向刚访问过的节点p,即pre = p。
⑤ 右子树递归线索化。

void InThreading(BiThrTree p)
{//pre是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索
 if(p)
 {
   InThreading(p->lchild); //左子树递归线索化
   if(!p->lchild) //p的左孩子为空
   {
     p->LTag=1; //给p加上左线索
     p->lchild=pre; //p的左孩子指针指向pre(前驱)
   } //if
   else p->LTag=0;  
   if(!pre->rchild) //pre的右孩子为空
   {
     pre->RTag=1; //给pre加上右线索
     pre->rchild=p; //pre的右孩子指针指向p(后继)
   } //if
   else pre->RTag=0;  
   pre=p; //保持pre指向p
   InThreading(p->rchild); //右子树递归线索化
 }
}
  • 带头节点的二叉树中序线索化
void InOrderThreading(BiThrTree &Thrt,BiThrTree T)
{//中序遍历二叉树T,并将其中序线索化,Thrt指向头节点
 Thrt=new BiThrNode ; //建头节点
 Thrt->LTag=0; //头节点有左孩子,若树非空,则其左孩子为树根
  Thrt->RTag=1; //头节点的右孩子指针为右线索
 Thrt->rchild=Thrt; //初始化时右指针指向自己
 if(!T) Thrt->lchild=Thrt; //若树为空,则左指针也指向自己
 else
 {
   Thrt->lchild=T; pre=Thrt; //头节点的左孩子指向根,pre初值指向头节点
   InThreading(T); //调用算法,对以T为根的二叉树进行中序线索化
   pre->rchild=Thrt; //算法结束后,pre为最右节点,pre的右线索指向头节点 
   pre->RTag=1; 
   Thrt->rchild=pre; //头节点的右线索指向pre
 }
}
  1. 遍历线索二叉树
  • 在中序线索二叉树中查找
    在这里插入图片描述

  • 在先序线索二叉树中查找
    在这里插入图片描述

  • 在后序线索二叉树中查找
    在这里插入图片描述
    在这里插入图片描述

  • 遍历中序线索二叉树

【算法步骤】
① 指针p指向根节点。
② p为非空树或遍历未结束时,循环执行以下操作:
 沿左孩子向下,到达最左下节点p,它是中序的第一个节点;
 访问
p;
 沿右线索反复查找当前节点*p的后继节点并访问后继节点,直至右线索为0或者遍历结束;
 转向p的右子树。

void InOrderTraverse_Thr(BiThrTree T)
{//T指向头节点,头节点的左链lchild指向根节点
 //中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
 p=T->lchild; //p指向根节点
 while(p!=T) //空树或遍历结束时,p==T
 {
   while(p->LTag==0) p=p->lchild; //沿左孩子向下
   cout<<p->data; //访问其左子树为空的节点
   while(p->RTag==1&&p->rchild!=T)
   {
     p=p->rchild;cout<<p->data; //沿右线索访问后继节点
   }
   p=p->rchild; //转向p的右子树
 }
}

树和森林

树的存储结构

  1. 双亲表示法
    在这里插入图片描述

  2. 孩子表示法
    在这里插入图片描述
    在这里插入图片描述

  3. 孩子兄弟表示法
    在这里插入图片描述
    在这里插入图片描述

森林与二叉树的转换

  1. 森林转换成二叉树
    在这里插入图片描述

  2. 二叉树转换成森林
    在这里插入图片描述
    在这里插入图片描述

手算二叉树转树和森林

在这里插入图片描述

手算树、森林转二叉树

在这里插入图片描述

树和森林的遍历

  1. 树的遍历
    由树结构的定义可引出以两种次序遍历树的方法:一种是先根(次序)遍历树,即先访问树的根节点,然后依次先根遍历根的每棵子树;另一种是后根(次序)遍历,即先依次后根遍历每棵子树,然后访问根节点。

  2. 森林的遍历
    (1)先序遍历森林
    若森林非空,则可按下述规则遍历:
    ① 访问森林中第一棵树的根节点;
    ② 先序遍历第一棵树的根节点的子树森林;
    ③ 先序遍历除去第一棵树之后剩余的树构成的森林。
    (2)中序遍历森林
    若森林非空,则可按下述规则遍历:
    ① 中序遍历森林中第一棵树的根节点的子树森林;
    ② 访问第一棵树的根节点;
    ③ 中序遍历除去第一棵树之后剩余的树构成的森林。

哈夫曼树及其应用

哈夫曼树的基本概念

哈夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用
途。哈夫曼树的定义,涉及路径、路径长度、权等概念。

(1)路径: 从树中一个节点到另一个节点之间的分支构成这两个节点之间的路径。
(2)路径长度: 路径上的分支数目称作路径长度。
(3)树的路径长度: 从树根到每一叶子节点的路径长度之和。
(4)权: 赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有节点(元素)和边(关系)两大类,所以对应有节点权和边权。节点权或边权具体代表什么意义,由具体情况决定。如果在一棵树中的节点上带有权值,则对应的就有带权树等概念。
(5)节点的带权路径长度: 从该节点到树根之间的路径长度与节点上权值的乘积。
(6)树的带权路径长度: 树中所有叶子节点的带权路径长度之和,通常记作在这里插入图片描述

(7)哈夫曼树: 假设有m个权值{w1, w2,…, wm},可以构造一棵含n个叶子节点的二叉树,每个叶子节点的权值为wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或哈夫曼树。

在这里插入图片描述
其中以图5.25(c)所示二叉树的带权路径长度为最小。可以验证,它恰为哈夫曼树,即其带权路径长度在所有带权值为7、5、2、4的4个叶子节点的二叉树中居最小。

哈夫曼树的构造算法

  1. 哈夫曼树的构造过程
    在这里插入图片描述
    在这里插入图片描述

  2. 哈夫曼算法的实现

//- - - - -哈夫曼树的存储表示 - - - - -
typedef struct{
  int weight;       //节点的权值
  int parent,lchild,rchild; //节点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树

【算法步骤】
构造哈夫曼树算法的实现可以分成两大部分。
① 初始化:首先动态申请2n个单元;然后循环2n−1次,从1号单元开始,依次将1至2n−1所有单元中的双亲、左孩子、右孩子的下标都初始化为0;最后循环n次,输入前n个单元中叶子节点的权值。
② 创建树:循环n−1次,通过n−1次的选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根节点s1和s2;删除是指将节点s1和s2的双亲改为非0;合并就是将s1和s2的权值和作为一个新节点的权值依次存入数组的n + 1号及之后的单元中,同时记录这个新节点左孩子的下标为s1,右孩子的下标为s2。

void CreateHuffmanTree(HuffmanTree &HT,int n)
{//构造哈夫曼树HT
 if(n<=1) return;
 m=2*n-1;
 HT=new HTNode[m+1]; //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根节点
 for(i=1;i<=m;++i) //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0 
   {HT[i].parent=0;HT[i].lchild=0;HT[i].rchild=0;}
 for(i=1;i<=n;++i) //输入前n个单元中叶子节点的权值
   cin>>HT[i].weight; 
/*- - - - - - - - - - -初始化工作结束,下面开始创建哈夫曼树- - - - - - - - - -*/
 for(i=n+1;i<=m;++i)
 {//通过n-1次的选择、删除、合并来创建哈夫曼树
   Select(HT,i-1,s1,s2);
   //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的节点,并返回它们在HT中的序号s1和s2
   HT[s1].parent=i;HT[s2].parent=i;
   //得到新节点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
   HT[i].lchild=s1;HT[i].rchild=s2; //s1,s2分别作为i的左右孩子
   HT[i].weight=HT[s1].weight+HT[s2].weight; //i的权值为左右孩子权值之和
 } //for
}

哈夫曼编码

  1. 哈夫曼编码的主要思想

下面给出有关编码的两个概念。

(1)前缀编码:如果在一个编码方案中,任一个编码都不是其他任何编码的前缀(最左子串),则称编码是前缀编码。例如,案例5.1中的第2种编码方案[见表5.1(b)]的编码0、10、110、111是前缀编码,而第3种编码方案[见表5.1(c)]的编码0、01、010、111就不是前缀编码。前缀编码可以保证对压缩文件进行解码时不产生二义性,确保正确解码。

(2)哈夫曼编码:对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,对每个右分支赋予1,则从根到每个叶子的路径上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。

哈夫曼编码具有下面的两个性质。

  • 性质1 哈夫曼编码是前缀编码。
  • 性质2 哈夫曼编码是最优前缀编码。
  1. 哈夫曼编码的算法实现

在构造哈夫曼树之后,求哈夫曼编码的主要思想是:依次以叶子为出发点,向上回溯至根节点为止。回溯时走左分支则生成代码0,走右分支则生成代码1。

由于每个哈夫曼编码是变长编码,因此使用一个指针数组来存放每个字符编码串的首地址。

//- - - - -哈夫曼编码表的存储表示- - - - -
typedef char **HuffmanCode; // 动态分配数组存储哈夫曼编码表

根据哈夫曼树求哈夫曼编码

【算法步骤】
① 分配存储n个字符编码的编码表空间HC,长度为n+1;分配临时存储每个字符编码的动态数组空间cd,cd[n−1]置为’\0’。
② 逐个求解n个字符的编码,循环n次,执行以下操作:
 设置变量start用于记录编码在cd中存放的位置,start初始时指向最后,即编码结束符位置n−1;
 设置变量c用于记录从叶子节点向上回溯至根节点所经过的节点下标,c初始时为当前待编码字符的下标i,f用于记录i的双亲节点的下标;
 从叶子节点向上回溯至根节点,求得字符i的编码,当f没有到达根节点时,循环执行以下操作:
 回溯一次start向前指一个位置,即–start;
 若节点c是f的左孩子,则生成代码0,否则生成代码1,生成的代码0或1保存在cd[start]中;
 继续向上回溯,改变c和f的值。
 根据数组cd的字符串长度为第i个字符编码分配空间HC[i],然后将数组cd中的编码复制到HC[i]中。
③ 释放临时空间cd。

void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
 HC=new char*[n+1]; //分配存储n个字符编码的编码表空间
 cd=new char[n]; //分配临时存放每个字符编码的动态数组空间
 cd[n − 1]='\0'; //编码结束符
 for(i=1;i<=n;++i) //逐个字符求哈夫曼编码
 {
   start=n-1; //start开始时指向最后,即编码结束符位置
   c=i;f=HT[i].parent; //f指向节点c的双亲节点
   while(f!=0) //从叶子节点开始向上回溯,直到根节点
   {              
     --start; //回溯一次start向前指一个位置
     if(HT[f].lchild==c) cd[start]='0'; //节点c是f的左孩子,则生成代码0
     else cd[start]='1'; //节点c是f的右孩子,则生成代码1
     c=f;f=HT[f].parent; //继续向上回溯
   } //求出第i个字符的编码
   HC[i]=new char[n-start];   //为第i个字符编码分配空间
   strcpy(HC[i],&cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
 } //for
delete cd; //释放临时空间
 }

在这里插入图片描述

  1. 文件的编码和译码
    在这里插入图片描述

案例引入

  • 利用二叉树求解表达式的值。
  • 表达式树的创建
  • 表达式树的求值

小结

在这里插入图片描述
在这里插入图片描述

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值