文章目录

  • 7. 树
    • 7.1 树的逻辑结构
      • 7.1.1 例题
    • 7.2 树的基本术语
      • 7.2.1 例题
    • 7.3 二叉树的性质
      • 7.3.1 性质1
      • 7.3.2 性质2
      • 7.3.3 性质3
      • 7.3.4 性质4
      • 7.3.5 性质5
      • 7.3.6 例题
    • 7.4 二叉树的存储表示
      • 7.4.0 二叉树的顺序存储表示
      • 7.4.1 二叉树的二叉链表存储表示
      • 7.4.2二叉树的三叉链表存储表示
      • 7.4.3 二叉树双亲链表存储表示
      • 7.4.1 例题
      • 7.5 二叉树的遍历
      • 7.5.1 例题
    • 7.5 二叉树遍历算法的应用
      • 7.5.1 建立二叉树的存储结构
      • 7.5.2 统计二叉树中叶子结点个数
      • 7.5.3 求二叉树的深度
      • 7.5.4 复制二叉树(后序遍历)
      • 7.5.5 交换二叉树的左右子树
    • 7.6 线索二叉树
      • 7.6.1 变量中序线索二叉树
      • 7.6.3 构造中序线索二叉树
      • 7.6.4 例题
    • 7.7 树和森林的存储结构
      • 7.7.1 双亲表示法(树)
      • 7.7.2 孩子链表表示法(树)
      • 7.7.3 孩子兄弟表示法
      • 7.7.4树和二叉树的相互转换
        • 7.7.4.1树转换成二叉树
        • 7.7.4.2 二叉树转换成树
      • 7.7.5 森林转换成二叉树的转换规则:
      • 7.7.6 由二叉树转换为森林的转换规则
    • 7.8 树和森林的遍历
      • 7.8.1 森林的遍历
        • 7.8.1.1 森林的先序遍历
        • 7.8.1.2 森林的中序遍历
        • 7.8.1.3 树的遍历、森林的遍历和二叉树遍历的对应关系
      • 7.8.2 例题
    • 7.9 树和森林的算法
      • 7.9.1 建立树的存储结构
      • 7.9.2 求树的深度
      • 7.9.3 求树中的叶子结点树
      • 7.9.4 输出树中所有从根到叶子的路径
    • 7.10 赫夫曼树
      • 7.10.1 赫夫曼树的特点
      • 7.10.2 例题
      • 7.10.3 赫夫曼树的构造
      • 7.10.4 例题

7. 树

7.1 树的逻辑结构

树的类型定义

数据对象D:D是具有相同特性的数据元素的集合
数据关系R:若D 为空集,则称为空树,否则:(1)在D中存在唯一的称为根的数据元素root。(2)当n>=1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,...Tm,其中每一棵子集本身又是一棵符合本定义的树,称为根root的子树。
  • 查找类的操作:
Root(T)//求树的根结点
Value(T,cur_e)//求当前结点的元素值
Parent(T,cur_e)//求当前结点的双亲结点
LeftChild(T,cur_e)//求当前结点的最左孩子
RightSibling(T,cur_e)//求当前结点的最右兄弟
TreeEmpty(T)//判定树是否为空树
TreeDepth(T)//求树的深度
TraverseTree(T,visit())//遍历
  • 插入类的操作
InitTree(&T)//初始化置空树
CreateTree(&T,definition)//按定义构造树
Assign(T,cur_e,value)//给当前结点赋值
InsertChild(&T,&p,i,c)//将以c为根的树插入为结点p的第i棵子树
  • 删除类的操作
ClearTree(&T)//将树清空
DestroyTree(&T)//销毁树的结果
DeleteChild(&T,&p,i)//删除结点p的第i棵子树
  • 树型结构和线性结构的比较
线性结构 树型结构
第一个数据元素(无前驱) 根结点(无前驱)
最后一个数据元素(无后继) 多个叶子结点(无后继)
其它数据元素(一个前驱,一个后继) 其它数据元素(一个前驱,多个后继)

7.1.1 例题

  1. 树一定有唯一的根结点(X)
  2. 树的根结点有若干棵子树,则除树的根结点外的任一结点只能属于一棵子树(√)
  3. 树中结点最多只能有一个前驱,但可能有多个后继(√)

7.2 树的基本术语

  • 结点:数据元素+若干指向子树的分支。

  • 结点的度:分支的个数。

  • 树的度:树中所有结点的度的最大值

  • 叶子结点:度为零的结点

  • 分支结点:度大于零的结点

  • 有向树:

(1)有确定的根

(2)树根和子树根为有向关系

  • 有序树:

子树之间存在确定的次序关系

  • 无序树:

子树之间不存在确定的次序关系

  • 结点的层次:

假设根结点的层次为1,第l层的结点的子树根结点的层次为l+1

  • 树的深度:

树中叶子结点所在的最大层次

  • 森林:m棵互不相交的树的集合。

任何一棵非空树是一个二元组:Tree=(root,F)

其中:root被称为根结点。

​ F被称为子树森林。

7.2.1 例题

  1. 树的度:3

n0n_0n0​=6

n1n_1n1​=2

n2n_2n2​=1

n3n_3n3​=2

nnn=11

  1. 结点F的祖先结点包括:E,C,J
  2. 结点G 的子孙结点包括:D,H,I,B
  3. 从结点J到结点H的路径包括:结点J,G,D,H;路径:JG,GD,DH
  4. 树的深度为:4
  5. 非叶子/非终端结点包括J,C,G,E,D,内部结点包括C,G,E,D
  6. 树中每个结点有唯一的双亲结点,根结点除外(x)

7.3 二叉树的性质

7.3.1 性质1

在二叉树的第i层上至多有 $ 2^{i-1} $个结点

用归纳法证明:
归纳基:i = 1 层时,只有一个根结点:2i−1=20=12^{i-1}=2^0=12i−1=20=1
归纳假设:假设第 i -1层的结点数 =2i−22^{i-2}2i−2;
归纳证明:二叉树上每个结点至多有两棵子树,
则第 i 层的结点数 =2i−2×2=2i−12^{i-2}\times2=2^{i-1}2i−2×2=2i−1

7.3.2 性质2

深度为$ k $的二叉树至多含2k−12^{k}-12k−1个结点(k>=1)

证明:
基于性质1,深度为 k 的二叉树上的结点数至多为
20+21+...2k−1=2k−12^0+2^1+...2^{k-1}=2^k-120+21+...2k−1=2k−1

7.3.3 性质3

对于任何一棵二叉树,若它含有n0n_0n0​个叶子结点,n2n_2n2​个度为2的结点,则必存在关系式:n0=n2+1n_0=n_2+1n0​=n2​+1。

对于任何一棵二叉树,若它含有n0 个叶子结点、n2 个度为 2 的结点,则必存在关系式:n0 = n2+1。
证明:
设 二叉树上结点总数 n = n0 + n1 + n2
又 二叉树上分支总数 b = n1+2*n2
而 b = n-1 = n0 + n1 + n2 - 1
因此, n0 = n2 + 1 。

7.3.4 性质4

满二叉树:指的是深度为kkk,且含有2k−12^k-12k−1个结点的二叉树。

完全二叉树:树中所含的nnn个结点和满二叉树中编号为1至nnn的结点相对应。

具有n个结点的完全二叉树的深度为:[log2n]+1[log_2n]+1[log2​n]+1(向下取整)

证明:
设完全二叉树的深度为 k ,
则根据第2条性质得 2k−12^{k-1}2k−1≤ n < 2k2^k2k,
即 k-1 ≤ log2(n)log_2(n)log2​(n) < k ,
因为 k 只能是整数,因此,k=⌊log2(n)⌋+1k=\lfloor log_2(n)\rfloor +1k=⌊log2​(n)⌋+1

7.3.5 性质5

若对含n个结点的完全二叉树从上到下且从左至右进行1至n的编号,则对完全二叉树中任意一个编号为iii的结点:

(1)若i=1i=1i=1,则该结点是二叉树的根,无双亲,否则,编号为⌊i/2⌋\lfloor i/2\rfloor⌊i/2⌋的结点为其双亲结点。

(2)若2i>n2i>n2i>n,则该结点无左孩子,否则,编号为2i2i2i的结点为其左孩子结点。

(3)若2i+1>n2i+1>n2i+1>n则该结点无右孩子,否则编号为2i+12i+12i+1的结点为其右孩子结点。

7.3.6 例题

  1. 在结点个数为n(n>1)的各种形态的树中,深度最小的树有 2 层。
  2. 在一棵深度为6的完全二叉树中,最少可以有多少结点,最多可以有多少个结点? 最少:32 最多:63
  3. 在完全二叉树中,结点总数nnn为999,求叶子结点数n0n_0n0​为多少? 500
  4. 在完全二叉树中,结点总数为n,求叶子结点数为多少?

n0=n2+1n_0=n_2+1n0​=n2​+1

n=2n0+n1−1n=2n_0+n1-1n=2n0​+n1−1

在完全二叉树中,n1n_1n1​只能为1或0

当n1n_1n1​为1时,nnn只能是偶数,n0=n/2n_0=n/2n0​=n/2

当n1n_1n1​为0时,nnn只能是奇数,n0=(n+1)/2n_0=(n+1)/2n0​=(n+1)/2

所以n0=⌊(n+1)/2⌋n_0=\lfloor(n+1)/2\rfloorn0​=⌊(n+1)/2⌋ (向下取整)

7.4 二叉树的存储表示

7.4.0 二叉树的顺序存储表示

#define MAX_TREE_SIE 100
//二叉树的最大结点树
typedef TElemType SqBiTree[MAX_TREE_SIZE];
//0号单元存储根结点
SqBiTree bt;

7.4.1 二叉树的二叉链表存储表示

typedef struct BiTNode{//结点结构TElemType data;struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;

7.4.2二叉树的三叉链表存储表示

typedef struct TriTNode{//结点结构TElemType data;struct TriTNode *lchild,*rchild;//左右孩子指针struct TriTNode *parent;//双亲指针
}TriTNode, *TriTree;

7.4.3 二叉树双亲链表存储表示

typedeg strcut BPTNode{//结点结构TElemType data;int *parent;//指向双亲的指针char LRTag;//左、右孩子标志域
}BPTNode;
typedef struct BPTree{//树结构BPTNode nodes[MAX_TREE_SIZE];int num_node;//结点数目int root;//根结点的数模
}BPTree;

7.4.1 例题

  1. 如果二叉树用二叉链表表示,有多少个空链域

空链域:
2n0+n1=n0+n0+n1=n2+1+n0+n1=n+12 n0+n1=n0+n0+n1=n2+1+n0+n1=n+1 2n0+n1=n0+n0+n1=n2+1+n0+n1=n+1

  1. 如果二叉树用三叉链表表示,有多少个空链域?

每个结点的双亲指针都不空,但根结点的双亲指针为空,所以有n+2个空链域

7.5 二叉树的遍历

介绍二叉树的各种递归和非递归的遍历方法,以及如何建立二叉树的存储结构。

二叉树遍历的搜索路径

对“二叉树”而言,可以有三条搜索路径:
1.先上后下的按层次遍历;
2.先左(子树)后右(子树)的遍历;
3.先右(子树)后左(子树)的遍历。

先左后右的遍历算法

先(根)序的遍历算法
中(根)序的遍历算法
后(根)序的遍历算法

先(根)序的遍历算法
若二叉树为空树,则空操作;否则,
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树。
−+a∗b−cd/ef-+a*b-cd/ef−+a∗b−cd/ef

中(根)序的遍历算法

若二叉树为空树,则空操作;否则,
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树。
a+b∗c−d−e/fa+b*c-d-e/fa+b∗c−d−e/f

后(根)序的遍历算法

若二叉树为空树,则空操作;否则,
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点。
abcd−∗+ef/−abcd-*+ef/-abcd−∗+ef/−

//递归
void Preorder (BiTree T, void( *visit)(TElemType &e))
{ // 先序遍历二叉树 if (T) {visit(T->data);            // 访问结点Preorder(T->lchild, visit);   // 遍历左子树Preorder(T->rchild, visit);  // 遍历右子树}
}//先序非递归Status InOrderTraverse(BiTree T, Status (*Visit)(TElemType e)){  InitStack(S); Push(S,T);   //根指针进栈while (!StackEmpty (S)) {while(GetTop(S, p) && p) Push (S,p->lchild);Pop(S, p);   //空指针退栈if (!StackEmpty(S)){  //访问节点,退后一步Pop (S, p); if (!Visit(p->data))   return ERROR;Push(S,p->rchild);}  //if}  //whilereturn OK;}

//层次遍历算法的非递归描述
void translevel(BinNode  *bt)
{ struct BinNode  *b;q.front=0;     q.rear=0;  if (!bt)   return;q.elem[q.rear]=bt;     q.rear=q.rear+1;
while (q.front < q.rear){ b=q.elem[q.front];   q.front=q.front+1;printf("%c  ",b->data);if (b->lch!=0)      { q.elem[q.rear]=b->lch;      q.rear=q.rear+1;  }if (b->rch!=0)         { q.elem[q.rear]=b->rch;                                              q.rear=q.rear+1;                                      }}
}

7.5.1 例题

先序遍历:JCEMFNGDHIB

中序遍历:MFNECJHDIBG

后序遍历:NFMECHBIDGJ

  1. 若二叉树先序遍历的扩展序列为AB∗D∗EC∗∗F∗∗∗AB*D*EC**F***AB∗D∗EC∗∗F∗∗∗,其中*代表空链域,则二叉树的后序遍历序列为CFEDBA。
  2. 已知一棵二叉树的中序遍历序列为DBACFEGM,后序遍历序列为DAFGECBM,画出这棵二叉树,并给出先序遍历序列。

先序遍历序列:MBDCAEFG

7.5 二叉树遍历算法的应用

7.5.1 建立二叉树的存储结构

Status CreateBiTree(BiTree &T){Scanf(&ch);if(ch=='#')T=NULL;else{if(!(T=(BiTNode *)malloc(sizeof(BiTNode))))exit(OVERFLOW);T->data=ch;CreateBiTree(T->lchild);//构造左子树CreateBiTree(T->rchild);//构造右子树}return OK;
}//CreateBiTree

7.5.2 统计二叉树中叶子结点个数

算法基本思想:
先序(或中序或后序)遍历二叉树,在遍历过程中查找叶子结点,并计数。
由此,需在遍历算法中增添一个“计数”的参数,并将算法中“访问结点”的操作改为:若是叶子,则计数器增1。

void CountLeaf(BiTree T,int& count){if(T){if((!T->lchild)&&(!T->rchild)_count++;CountLeaf(T->lchild,count);CountLeaf(T->rchild,count);}
}

7.5.3 求二叉树的深度

算法基本思想:
首先分析二叉树的深度和它的左、右子树深度之间的关系。
从二叉树深度的定义可知,二叉树的深度应为其左、右子树深度的最大值加1。由此,需先分别求得左、右子树的深度,算法中“访问结点”的操作改为:二叉树的深度为其左、右子树深度的最大值加1。

int Depth(BiTree T){if(!T)depthval=0;else{depthleft=Depth(T->lchild);depthright=Depth(T->rchild);depthval=1+(depthleft>depthright?depthleft:depthright);}return depthval;
}

7.5.4 复制二叉树(后序遍历)

//生成一个二叉树的结点
//(其数据域为item,左指针域为lptr,右指针域为rptr)
BiTNode *GetTreeNode(TElemType item, BiTNode *lptr, BiTNode *rptr )
{if (!(T = (BiTNode*)malloc(sizeof(BiTNode))))exit(1);T-> data = item;T-> lchild = lptr;    T-> rchild = rptr;return T;
}BiTNode *CopyTree(BiTNode *T) {  if (!T )    return NULL;if (T->lchild ) newlptr = CopyTree(T->lchild);//复制左子树else  newlptr = NULL;if (T->rchild ) newrptr = CopyTree(T->rchild);//复制右子树else  newrptr = NULL;newT = GetTreeNode(T->data, newlptr, newrptr);return newT;
} // CopyTree

7.5.5 交换二叉树的左右子树

Void exchange (BinNode  *T )
{ BinNode  *q;  //中间变量if ( T ){ q = T->lchild ;T->lchild= T->rchild;T->rchild = q;   exchange(T->lchild);exchange(T->rchild);}
}

7.6 线索二叉树

内容:介绍线索二叉树的特性、中序线索二叉树的遍历和构建。

先序序列:
A B C D E F G H K
指向该线性序列中的“前驱”和
“后继” 的指针,称作“线索”。
包含 “线索” 的存储结构,称作 “线索链表”。
与其相应的二叉树,称作 “线索二叉树” 。

对线索链表中结点的约定:
在二叉链表的结点中增加两个标志域,

并作如下规定:
若该结点的左子树不空,
则Lchild域的指针指向其左子树,
且左标志域的值为“指针 Link”;
否则,Lchild域的指针指向其“前驱”,
且左标志的值为“线索 Thread” 。

若该结点的右子树不空,
则rchild域的指针指向其右子树,
且右标志域的值为 “指针 Link”;
否则,rchild域的指针指向其“后继”,
且右标志的值为“线索 Thread”。

如此定义的二叉树的存储结构称作“线索链表”。

typedef enum{Link,Thread}PointerThr;
typedef struct BiThrNode{TElemType data;struct BiThrNode *lchild,*child;//左右指针PointerThr LTag,RTag;//左右标志
}BiThrNode,*BiThrTree;
  • 中序遍历序列CBHFAEGD

7.6.1 变量中序线索二叉树

由于在线索链表中添加了遍历中得到的“前驱”和“后继”的信息,从而简化了遍历的算法。

    for ( p = firstNode(T); p; p = Succ(p) )Visit (p);

中序遍历的第一个结点 ?
左子树上处于“最左下”(没有左子树)的结点。
在中序线索化链表中结点的后继 ?
若无右子树,则为后继线索所指结点;
否则为对其右子树进行中序遍历时访问的第一个结点。

Status InOrderTraverse_Thr(BiThrTree T, Status (*Visit)(TElemType e))
{ p = T->lchild;       // p指向根结点while (p != T) {     // 空树或遍历结束时,p==Twhile (p->LTag==Link)  p = p->lchild;  // 第一个结点if (!Visit (p->data)) return ERROR;while (p->RTag==Thread && p->rchild!=T) {p = p->rchild;  Visit(p->data);      // 访问后继结点}p = p->rchild;          // p进至其右子树根}return OK;
} // InOrderTraverse_Thr。

7.6.3 构造中序线索二叉树

void InThreading(BiThrTree p) {if (p) {    // 对以p为根的非空二叉树进行线索化InThreading(p->lchild);      // 左子树线索化if (!p->lchild)      // 建前驱线索{ p->LTag = Thread;    p->lchild = pre; }if (!pre->rchild)   // 建后继线索{ pre->RTag = Thread;   pre->rchild = p; } pre = p;             // 保持 pre 指向 p 的前驱InThreading(p->rchild);      // 右子树线索化} // if
} // InThreadingStatus InOrderThreading(BiThrTree &Thrt, BiThrTree T) // 构建中序线索链表{  if (!(Thrt = (BiThrTree)malloc(sizeof( BiThrNode)))) exit (OVERFLOW);Thrt->LTag = Link;  Thrt->RTag =Thread; Thrt->rchild = Thrt;  // 添加头结点
if (!T)  Thrt->lchild = Thrt;
else {             Thrt->lchild = T;   pre = Thrt;InThreading(T);                 pre->rchild = Thrt;   // 处理最后一个结点pre->RTag = Thread;    Thrt->rchild = pre;    }return OK;
} // InOrderThreading

7.6.4 例题

  1. 已知二叉树,画出中序线索链表

  1. 引入二叉线索树的目的是©

​ A. 为了能方便地找到双亲
​ B. 为了能在二叉树中方便地进行插入和删除
​ C. 加快查找结点的前驱或后继的速度
​ D. 使二叉树的遍历结果唯一

  1. 在遍历中序线索二叉树时,某结点既有左子树又有右子树,那么它的前驱是其(D)

    A. 右子树中最左下的结点

    B. 右子树中最右下的结点

    C. 左子树中最左下的结点

    D. 左子树中最右下的结点

7.7 树和森林的存储结构

7.7.1 双亲表示法(树)

#define MAX_TREE_SIZE 100
typedef struct PTNode{//结点结构Elem data;int parent;//双亲位置域
}PTNode;typedef struct{//树结构PTNode nodes[MAX_TREE_SIZE];int r,n;//根结点的位置和结点个数
}PTree;

7.7.2 孩子链表表示法(树)

typedef struct CTNode {  // 孩子结点结构int child;struct CTNode *next;} *ChildPtr;
typedef struct {  // 双亲结点结构Elem data;int parent;   // 双亲位置域ChildPtr firstchild; // 孩子链的头指针} CTBox;
typedef struct {  // 树结构CTBox nodes[MAX_TREE_SIZE];int n, r;        // 结点数和根结点的位置} CTree;

7.7.3 孩子兄弟表示法

左链为孩子,右链为兄弟

typedef struct CSNode{Elem     data;struct   CSNode  *firstchild, *nextsibling;
} CSNode, *CSTree;

树的各种操作均可由对应的二叉树的操作来完成。
和树对应的二叉树,其左、右子树的概念已改变为:
左是孩子,右是兄弟。

int TreeDegree(CSTree T)
{int h1,h2;if(!T) return 0;else{h1=TreeDegree(T->firstchildrh2=TreeDegree(T->nextsibling);if(T->fch&&!T->fch->nextsibling) return h1+h2+1;else return h1+h2; ,}
}

7.7.4树和二叉树的相互转换

7.7.4.1树转换成二叉树

兄弟相连留长子

1.加线: 在兄弟之间加一条连线

2.抹去:对于每个结点, 除了左孩子外,去除与其余孩子间的连线

3.旋转:以树的根节点为轴心,将整树顺时针转45度

7.7.4.2 二叉树转换成树

左孩右右连双亲,去掉原来右孩线

1加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子………沿分支找到的所有右孩子,都与p的双亲用线连起来

2.抹线:抹掉原二叉树中双亲与右孩子之间的连线

3.调整:将结点按层次排列,形成树结构

7.7.5 森林转换成二叉树的转换规则:

树变二叉根相连

①将各棵树分别转换成二叉树

②将每棵树的根结点用线相连

③以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构

7.7.6 由二叉树转换为森林的转换规则

去掉全部右孩线,孤立二叉再还原

①抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树

②还原:将孤立的二叉树还原成树

7.8 树和森林的遍历

  1. 先根(次序)遍历:

    若树不空,则先访问根结点,然后依次先根遍历各棵子树

  2. 后根(次序)遍历:

    若树不空,则依次后根遍历各棵子树,然后访问根结点

  3. 按层次遍历

    若树不空,则自上而下自左至右访问树中的每个结点

先根遍历时顶点的访问次序:

A B E F C D G H I J K

后根遍历时顶点的访问次序:

E F B C I J K H G D A

层次遍历时顶点的访问次序:

A B C D E F G H I J K

7.8.1 森林的遍历

森林有三部分构成:

  1. 森林中第一棵树的根结点
  2. 森林中第一颗树的子树森林
  3. 森林中其它树构成的森林

7.8.1.1 森林的先序遍历

若森林不为空,则

访问森林中第一棵树的根结点

先序遍历森林中第一棵树的子树森林

先序遍历森林中(除第一棵树之外)其余树构成的森林。

依次从左至右对森林中的每一棵树进行先根遍历。

7.8.1.2 森林的中序遍历

若森林不空,则

中序遍历森林中第一棵树的子树森林

访问森林中第一棵树的根结点

中序遍历森林中(除第一棵树之外)其余树构成的森林。

依次从左至右对森林中的每一棵树进行后根遍历。

7.8.1.3 树的遍历、森林的遍历和二叉树遍历的对应关系

森林 二叉树
先序遍历 先根遍历 先序遍历
中序遍历 后根遍历 中序遍历

7.8.2 例题

  1. 求森林的先序和中序遍历

先序:ACEMFNGDHIB

中序:MFNECAHDIBG

  1. 求树的先根和后根遍历序列

先根:GCEMDHFNIB

后根:MECFHNDIBG

7.9 树和森林的算法

typedef struct CSNode{Elem data;strcut CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

7.9.1 建立树的存储结构

假设以二元组$(F,C) $的形式自上而下、自左而右
依次输入树的各边,建立树的孩子-兄弟链表。

“#” “A”
“A” “B”
“A” “C”
“A” “D”
“C” “E”
“C” “F”
“F” “G”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRmUv4zD-1652196080251)(C:\Users\86153\AppData\Roaming\Typora\typora-user-images\image-20210610194936114.png)]

void CreatTree(CSTree &T){T=NULL;for(scanf(&fa,&ch);ch!='';scanf(&fa,&ch)){p=GetTreeNode(ch);//创建结点EnQueue(Q,p);//指针入队列if(fa=='#')T=p;else{GetHead(Q,s);//取队列头元素(指针值)while(s->data!=fa){//查询双亲结点DeQueue(Q,s);GetHead(Q,s);}if(!(s->firstchils)){s->firstchild=p;r=p;}else{r->nextsibling=p; //链接第一个孩子结点r=p;//链接其他孩子结点}}}
}

7.9.2 求树的深度

int TreeDepth(CSTree T){if(!T)return 0;else{h1=TreeDepth(T->firstchild);h2=TreeDepth(T->nextsibling);return(max(h1+1,h2));}
}//TreeDepth

7.9.3 求树中的叶子结点树

void CountLeaf(CSNode T,int &count){if(T){if(!T->fch)count++;CountLeaf(T->fch,count);CountLeaf(T->nsib,count);}//if
}//CountLeaf

7.9.4 输出树中所有从根到叶子的路径

void OutPath(Bitree T,Stack &S){//输出森林中所有从根到叶的路径while(T){Push(S,T->data);else OutPath(T->firstchild,s);Pop(S);T=T->nextsibling;}//while
}OutPath

7.10 赫夫曼树

  • 路径长度:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上分支的数目称作路径长度。

  • 结点的路径长度:从根结点到该结点的路径上分支的数目。

  • 树的路径长度树中每个结点的路径长度之和。

  • 树的带权路径长度树中所有叶子结点的带权路径长度之和
    WPL(T)=Σwklk(所有叶子结点)WPL(T)=\Sigma w_kl_k(所有叶子结点) WPL(T)=Σwk​lk​(所有叶子结点)

  • 最优二叉树(或赫夫曼树):假设有n个权值w1,w2,...wn{w_1,w_2,...w_n}w1​,w2​,...wn​,试构造一棵有n个叶子结点的二叉树,每个叶子结点的权值为wiw_iwi​,其中带权路径长度WPLWPLWPL最小的二叉树称作最优二叉树(或赫夫曼树)。

WPL(T)=7×2+5×2+2×3+4×3+9×2=60WPL(T)= 7\times2+5\times2+2\times3+4\times3+9\times2=60 WPL(T)=7×2+5×2+2×3+4×3+9×2=60

WPL(T)=7×4+9×4+5×3+4×2+2×1=89WPL(T)=7\times4+9\times4+5\times3+4\times2+2\times1=89 WPL(T)=7×4+9×4+5×3+4×2+2×1=89

7.10.1 赫夫曼树的特点

  1. 赫夫曼树没有度为1的结点
  2. 赫夫曼树中结点总数nnn为2n0−12n_0-12n0​−1

证明:
n=n0+n1+n2=2n0−1n=n_0+n_1+n_2=2n_0-1 n=n0​+n1​+n2​=2n0​−1

7.10.2 例题

  1. 试证明具有n0n_0n0​个叶子结点的哈夫曼树的分支总数为2(n0−1)2(n_0-1)2(n0​−1)。

证明:

由于哈夫曼树是一个二叉树,而叶子结点树为n0n_0n0​,

由二叉树的性质3可知度为2的结点树为n0−1n_0-1n0​−1。

哈夫曼树的分支都是从度为2的结点发出的,所以哈夫曼树的分支总数为2(n0−1)2(n_0-1)2(n0​−1)

7.10.3 赫夫曼树的构造

以二叉树为例:
(1)根据给定的 n 个权值 {w1, w2, …, wn},构造 n 棵二叉树的集合
F = {T1, T2, … , Tn},
其中每棵二叉树中均只含一个带权值为 wi 的根结点,其左、右子树
为空树;
(2)在 F 中选取其根结点的权值为最小的两棵二叉树,分别作为左、
右子树构造一棵新的二叉树,并置这棵新的二叉树根结点的权值
为其左、右子树根结点的权值之和;

(3)从F 中删去这两棵树,同时加入刚生成的新树;
(4)重复 (2) 和 (3) 两步,直至 F 中只含一棵树为止。

7.10.4 例题

  1. 用于通信的电文由字符集{a, b, c, d, e, f, g, h}中的字符构成,这8个字母在电文中出现的概率分别为{0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10},为这8个字母设计哈夫曼编码。

数据结构与算法图解——树相关推荐

  1. 数据结构与算法——AVL树类的C++实现

    关于AVL树的简单介绍能够參考: 数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额 ...

  2. 数据结构与算法--B树原理及实现

    B树 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中.可说总要那么海量的数据需要通过个中数据结构去存储,我们不可能有这么多内存区存放这些数据.那么意味着我们需要将他们放磁盘.所以 ...

  3. 《数据结构与算法》——树与二叉树之遍历总结

    <数据结构与算法>--树与二叉树之遍历总结 树与二叉树部分计划分为三次进行复习总结,第一次为基本概念和二叉树的遍历,第二次内容为线索二叉树以及树和森林,第三次为树与二叉树的应用. 目录 & ...

  4. 算法为何重要(《数据结构与算法图解》by 杰伊•温格罗)

    本文内容借鉴一本我非常喜欢的书--<数据结构与算法图解>.学习之余,我决定把这本书精彩的部分摘录出来与大家分享. 写在前面 算法这个词听起来很深奥,其实不然.它只是解决某个问题的一套流程. ...

  5. 数据结构与算法——图解平衡二叉树及代码实现

    平衡二叉树介绍 平衡二叉树,是一种二叉排序树,其中每一个节点的左子树和右子树的高度差最多等于1.由3位科学家共同发明,用他们首字母命名 又被称为AVL树.从平衡二叉树的名称,你也可以体会到它是一种高度 ...

  6. 数据结构与算法(3)——树(二叉、二叉搜索树)

    前言:题图无关,现在开始来学习学习树相关的知识 前序文章: 数据结构与算法(1)--数组与链表(https://www.jianshu.com/p/7b93b3570875) 数据结构与算法(2)-- ...

  7. 高级数据结构与算法 | AVL树 (高度平衡树)

    文章目录 AVL树 实现思路 数据结构 查找 平衡因子 旋转 右旋 左旋 右左双旋 左右双旋 插入 删除 AVL树的验证 中序遍历 平衡判断 AVL树的性能 完整代码实现 AVL树 AVL树是最先发明 ...

  8. 数据结构与算法:树与二叉树python实现

    最近复习一遍数据结构与算法,做一些笔记,大家可以一起复习. 一.树的一些容易混淆的定义: 结点层:根结点的层定义为1:根的孩子为第二层结点,依此类推: 树的深度(或高度):树中最大的结点层: 满二叉树 ...

  9. 数据结构和算法——kd树

    一.K-近邻算法 K-近邻算法是一种典型的无参监督学习算法,对于一个监督学习任务来说,其 m m个训练样本为: {(X(1),y(1)),(X(2),y(2)),⋯,(X(m),y(m))} \lef ...

最新文章

  1. 什么猫咪最受欢迎?Python爬取全网猫咪图片,哪一款是你最爱的
  2. ubuntu和windows系统双系统的开机选项界面有很多无关选项
  3. 部署P2P扩容的脚本
  4. 怎样理解雷达的相参与非相参
  5. StringBuffer类的说明
  6. C++大学教程(第九版)2016-07 保罗·戴特尔 (Paul Deitel)、 哈维·戴特尔 (Harvey Deitel)_cafbe(C++中文版)
  7. 文献学习(part33)--Clustering by fast search and find of density peaks
  8. 奇妙的 CSS shapes(CSS图形)
  9. react 图片放在src里面还是public_手写Webpack从0编译Vue/React项目
  10. Windows小工具广告弹窗杀手+源码
  11. Oracle及Oracle客户端、PLSQL安装的一些问题
  12. java简单多线程_java中实现多线程的几种方式(简单实现)
  13. Docker之Docker网络讲解
  14. 给定一个0-1串,请找到一个尽可能长的子串,其中包含的0与1的个数相等。
  15. HDU2047 阿牛的EOF牛肉串【递推】
  16. 37.go struct 结构
  17. c语言输入的代码格式错误的是什么意思,详解输入输出格式(C语言代码)
  18. 说说“安规”的那些事儿
  19. t3软件怎么生成报表_t3财务报表怎么生成
  20. 零基础如何自学编程?用这6种方法就够了!

热门文章

  1. arduino灯光装置_创客实战 | 用Arduino制作一款奇幻的“灯光隧道”
  2. D/D/O/S学习思维导图
  3. 存款业务《三》——存款成本分析
  4. Kafka 的简单介绍
  5. Graph Mixture Density Networks 图混合密度网络
  6. 空间平面相交的直线的计算及其源码
  7. 基于Spark平台的协同过滤实时电影推荐系统
  8. Android 摄像头调用(不含拍照),kotlin开源
  9. 图说人工智能的研究内容
  10. 网站服务器 千牛,云服务器千牛