树和二叉树

6.1 树的定义和基本术语

一. 树的抽象数据类型定义

  1. 树的定义
    树是由n (n >=0)个结点组成的有限集合。
    如果n = 0,称为空树;
    如果n > 0,则:
    有且只有一个特定的称之为根(root)的结点,它只有后继,但没有前驱;
    除根以外的其它结点划分为m (m > 0)个互不相交的有限集合T1, T2, …, Tm,每个集合本身又是一棵树,并且称之为根的子树(SubTree)。每棵子树的根结点有且仅有一个直接前驱,但可以有0个或多个后继。
    2. 树的表示方法
    在这里插入图片描述
    在这里插入图片描述
    二. 树的基本术语
    结点:数据元素+若干指向子树的分支
    结点的度:结点拥有的分支个数
    树的度:树中所有结点的度的最大值
    叶子结点:度为零的结点
    分支结点:度大于零的结点
    (从根到结点的)路径:由从根到该结点所经分支和结点构成。
    孩子结点、双亲结点、兄弟结点、堂兄弟
    祖先结点、子孙结点
    结点的层次:假设根结点的层次为1,第l 层的结点的子树根结点的层次为l+1
    树的深度:树中叶子结点所在的最大层次
    有序树:是指树中结点的各子树从左至右是有次序的(不能互换),否则称为无序树。
    森林:是m(m≥0)棵互不相交的树的集合。
    在这里插入图片描述

6.2 二叉树

一. 二叉树的定义
一棵二叉树是结点的一个有限集合,该集合
(1)或者为空,
(2)或者是由一个根结点组成
(3)或者是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。
在这里插入图片描述
在这里插入图片描述
二. 二叉树的操作
(1)InitBiTree(&T) (2)DestroyBiTree(&T)
(3)CreateBiTree(&T,definition)
(4)ClearBiTree(&T) (5)BiTreeEmpty(T)
(6)BiTreeDepth(T) (7)Root(t)
(8)Value(T,e) (9)Assign(T,&e,value)
(10)Parent(T,e) (11)LeftChild(T,e)
(12)RightChild(T,e) (13)InsertChild(T,p,LR,c) (14)DeleteChild(T,p,LR)
(15)PreOrderTraverse(T,Visit())
(16)InOrderTraverse(T,Visit()) (17)PostOrderTraverse(T,Visit())
……
三. 二叉树的性质
性质1 若二叉树的层次从1开始, 则在二叉树的第 i 层最多有 2^(i-1)个结点。(i >=1)
性质2 深度为k的二叉树最多有 2^k-1个结点。
(k >=1)
性质3 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2, 则有
n0=n2+1
三. 二叉树的性质
定义1 满二叉树(Full Binary Tree)
一棵深度为k且有2^k -1个结点的二叉树称为满二叉树。
定义2 完全二叉树(Complete Binary Tree)
深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。
或者:若设二叉树的深度为h,则共有h层。除第h层外,其它各层(0h-1)的结点数都达到最大个数,第h层从右向左连续缺若干结点。
完全二叉树的特点是:
1)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;
2)对任一结点,如果其右子树的深度为l,则其左子树的深度必为l或l+1。
性质4 具有n个结点的完全二叉树的深度为 log2n +1
性质5 如果将一棵有n个结点的完全二叉树的结点按层序(自顶向下,同一层自左向右)连续编号1, 2, …, n,然后按此结点编号将树中各结点顺序地存放于一个一维数组中, 并简称编号为i的结点为结点i (1 <=i <=n)。则有以下关系:
若i = 1, 则 i 是二叉树的根,无双亲;
若i > 1, 则 i 的双亲为i /2
若2i > n, 则 i无左孩子;否则其左孩子结点为2i,
若2
i+1 > n, 则 i无右孩子,否则其右孩子结点为2i+1,
二叉树的存储结构
顺序存储
链式存储

  1. 顺序存储结构(数组表示)
    顺序存储二叉树的具体方法是:在一棵具有n个结点的完全二叉树中,从根结点开始编号为1,自上到下,每层自左至右地给所有结点编号,这样可以得到一个反映整个二叉树结构的线性序列;然后将完全二叉树上编号为i的结点依次存储在一维数组中下标为i-1的元素中。
    #define MAX_TREE_SIZE 100
    typedef TElemType SqBiTree[MAX_TREE_SIZE];
    SqBiTree bt;
    由于一般二叉树必须仿照完全二叉树那样存储,可能会浪费很多存储空间,单支树就是一个极端情况。
    一棵深度为k且只有k个结点的单支树需要长度为2^K-1的一维数组
  2. 链式存储结构
    链式存储是使用链表来存储二叉树中的数据元素,链表中的一个结点相应地存储二叉树中的一个结点。
    常见的二叉树的链式存储结构有两种:二叉链表和三叉链表。
    二叉链表是指链表中的每个结点包含两个指针域和一个数据域,分别用来存储指向二叉树中结点的左右孩子的指针及结点信息。
    三叉链表是指链表中的每个结点包含三个指针域和一个数据域,相比二叉链表多出的一个指针域则用来指向该结点的双亲结点。
    在这里插入图片描述
    二叉链表存储表示
    typedef struct BiTNode{
    TElemType data;
    Struct BiTNode *lchild,*rchild;
    }BiTNode, *BiTree;
    在这里插入图片描述

6.3 遍历二叉树和线索二叉树

“遍历”是任何类型均有的操作,对线性结构而言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。而二叉树是非线性结构,每个结点有两个后继,则存在如何遍历即按什么样的搜索路径遍历的问题。
一、遍历二叉树
遍历:顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次。即找一个完整而有规律的走法,得到树中所有结点的一个线性排列。
遍历的结果:产生一个关于结点的线性序列。
设访问根结点记作 D,
遍历根的左子树记作 L
遍历根的右子树记作 R
则可能的遍历次序有:
先序 DLR, 逆先序DRL
中序 LDR, 逆中序RDL
后序 LRD, 逆后序RLD
先序遍历 (Preorder Traversal)
先序遍历二叉树算法的框架是
若二叉树为空,则空操作;
否则
访问根结点 (D);
先序遍历左子树 (L);
先序遍历右子树 ®。
中序遍历 (Inorder Traversal)
中序遍历二叉树算法的框架是
若二叉树为空,则空操作;
否则
中序遍历左子树 (L);
访问根结点 (D);
中序遍历右子树 ®。
后序遍历 (Postorder Traversal)
后序遍历二叉树算法的框架是
若二叉树为空,则空操作;
否则
后序遍历左子树 (L);
后序遍历右子树 ®;
访问根结点 (D)。
在这里插入图片描述
由二叉树的先序序列和中序序列可唯一地确定一棵二叉树。
遍历算法的递归描述

void PreOrder (BiTree T,
        void( *Visit)(TElemType e))
{ // 先序遍历二叉树 
 if (T) {
  Visit(T->data) ; // 访问结点
  PreOrder(T->lchild, Visit); // 遍历左子树
  PreOrder(T->rchild, Visit);// 遍历右子树
 }
}

遍历二叉树(非递归算法)
中序遍历
在遍历左子树之前,先把根结点入栈,当左子树遍历结束后,从栈中弹出、访问,再遍历右子树
遍历的第一个和最后一个结点
第一个结点:沿着左链走,找到一个没有左孩子的结点;
最后一个结点:从根结点出发,沿着右链走,找到一个没有右孩子的结点;

 void inorder(BiTree T){
	InitStack(S);
         BiTree p=T;
	while(p||!StackEmpty(S){  
		if(p)
                         {Push(S,p);
			p=p->lchild;}
		else
		       {Pop(S,p);
			printf("%c",p->data);
			p=p->rchild;}
	}
   }

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
中序遍历的非递归算法

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);
          }
     }
     return OK;
}

先序遍历序列的形式定义一棵二叉树
根 左子树 右子树
先序建立二叉树的递归算法
Status CreateBiTree(BiTree &T)
{ char ch; scanf("%c",&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;

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

二、线索二叉树(穿线树、线索树) (Threaded Binary Tree)
线索:指向该线性序列中的“前驱”和“后继” 的指针。
线索链表:包含 “线索” 的存储结构。
在这里插入图片描述
线索二叉树:加上线索的二叉树
线索二叉树,即在一般二叉树的基础上,对每个结点进行考察。若其左子树非空,则其左指针不变,仍指向左子树;若其左子树为空,则让其左指针指向某种遍历顺序下该结点的前驱;若其右子树非空,则其右指针不变,仍指向右子树;若其右子树为空,则让其右指针指向某种遍历顺序下该结点的后继。如果规定遍历顺序为前序,则称为前序线索二叉树;如果规定遍历顺序为中序,则称为中序线索二叉树;如果规定遍历顺序为后序,则称为后序线索二叉树。
在这里插入图片描述
线索二叉树的类型描述

typedef enum PointerTag {Link,Thread};
//Link==0:指针,指向孩子结点
//Thread==1:线索,指向前驱或后继结点
typedef struct BiThrNode
    {	TElemType data;
	struct BiThrNode *lchild,*rchild;
	PointerTag LTag,RTag;
    }BiThrNode, *BiThrTree;
BiThrTree T;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1).在先序线索二叉树找前驱和后继
找后继:如果是非叶子结点,则:
如果有左孩子,则左孩子是其后继;
如果无左孩子,则右孩子是其后继;
如果是叶子结点,则右线索所指结点为后继;
找前驱:如果无左孩子,则左线索所指结点为前驱;
否则需要遍历
在这里插入图片描述

2).在中序线索二叉树找前驱和后继
找后继:若无右孩子,则右线索所指结点为后继;
否则,右孩子的“最左下”孩子为后继
找前驱:若无左孩子,则左线索所指结点为前驱;
否则,左孩子的“最右下”孩子为前驱
在这里插入图片描述
3).在后序线索二叉树找前驱和后继
找后继:如果无右孩子,则右线索所指结点为后继;
否则需要遍历;
找前驱:如果有右孩子,则右孩子是其前驱;
如果无右孩子,但有左孩子,则左孩子为前驱;
如果无左孩子,则左线索所指结点为前驱;
在这里插入图片描述
4). 线索二叉树上找前驱和后继的一般规律
先序线索二叉树找后继方便,找前驱可能需要遍历。
中序线索二叉树找前驱和后继都方便
后序线索二叉树找前驱方便,找后继可能需要遍历。
在这里插入图片描述
① 结束的条件?
树空或者指针指向头结点
② 中序遍历的第一个结点 ?
左子树上处于“最左下”(没有左子树)的结点
③ 在中序线索二叉树中结点的后继 ?
若无右子树,则右线索所指结点为后继
否则,右子树的“最左下”孩子为后继;
在这里插入图片描述

6.4 树和森林

树的三种存储结构
1.双亲表示法
用结构数组——树的顺序存储方式
类型定义:
找双亲方便,找孩子难
2.孩子链表表示法
顺序和链式结合的表示方法
找孩子方便,找双亲难
3.孩子兄弟表示法
找孩子容易,若增加parent域,则找双亲也较方便。
在这里插入图片描述
C语言的类型描述:
#define MAX_TREE_SIZE 100
结点结构:在这里插入图片描述
ypedef struct PTNode {
TElemType data;
int parent; // 双亲位置域
} PTNode;
树结构:
typedef struct {
PTNode nodes [MAX_TREE_SIZE];
int r, n; // 根结点的位置和结点个数
} PTree;
在这里插入图片描述
树:typedef struct
{ CTBox nodes[MAX_TREE_SIZE];
int n, r; // 结点数和根结点的位置
} CTree;
孩子链表头结点: typedef struct {
TElemType data;
ChildPtr firstchild; // 孩子链的头指针
} CTBox;
孩子链表结点:typedef struct CTNode {
int child;
struct CTNode *next;
} *ChildPtr;
在这里插入图片描述
在这里插入图片描述

森林与二叉树的转换
在这里插入图片描述

树转换为二叉树的步骤:
加线:在兄弟结点之间加一连线;
抹线:对任何结点,除了其最左的孩子
外,抹掉该结点原先与其孩子之
间的连线;
旋转:将水平的连线和原有的连线,以
树根结点为轴心,按顺时针方向
粗略地旋转450。
二叉树还原成树
(二叉树还原成树的步骤)
加线:如果p结点是双亲结点的左孩子;
则将p结点的右孩子,右孩子的右
孩子,……,沿着右分支搜索到的
所有右孩子都分别与p结点的双
亲用线连接起来;
抹线:抹掉原二叉树中所有双亲结点与
右孩子的连线;
调整: 将结点按层次排列,形成树的结构.
森林转换成二叉树
森林转换成二叉树的步骤:
转换:将森林中的每一颗树转换成二
叉树;
连线:将各棵转换后的二叉树的根结
点相连;
旋转:将添加的水平线和原有的连线,
以第一颗树的根结点为轴心,按
顺时针方向旋转450。
森林转化成二叉树的规则

 若森林F为空,即n = 0,则
     对应的二叉树B为空二叉树。
 若F不空,则
     对应二叉树B的根root (B)是F中第一棵树T1的根root (T1);
     其左子树为B (T11, T12, …, T1m),其中,T11, T12, …, T1m是root (T1)的子树;
     其右子树为B (T2, T3, …, Tn),其中,T2, T3, …, Tn是除T1外其它树构成的森林。

6.5赫夫曼树及其应用

1、最优树的定义
路径:从树中一个结点到另一个结点之间的分支所构成的通路 。
路径长度:路径上分支的数目。
树的路径长度:树的根到每个结点的路径长度之和。
结点的带权路径长度:从树根到该结点之间的长度与结点权值的乘积 ( l * w )
树的带权路径长度:树中所有叶子结点的带权路径长度之和
在这里插入图片描述
在所有含 n 个叶子结点、并带相同权值的 m 叉树中,必存在一棵其带权路径长度取最小值的树,称为“最优树”。
赫夫曼树带权路径长度达到最小的二叉树即为赫夫曼树(最优二叉树)。
在赫夫曼树中,权值大的结点离根最近。
赫夫曼算法
——如何构造一棵赫夫曼树
(1) 由给定的n个权值{w0, w1, w2, …, wn-1},构造具有n棵二叉树的森林F = {T0, T1, T2, …, Tn-1},其中每一棵二叉树Ti只有一个带有权值wi的根结点,其左、右子树均为空。
(2) 重复以下步骤, 直到F中仅剩下一棵树为止:
① 在F中选取两棵根结点的权值最小的二叉树, 做为左、右子树构造一棵新的二叉树。置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
② 在F中删去这两棵二叉树。
③ 把新的二叉树加入F。
3、最优树的应用
假设电文由A,B,C,D四个字符组成.
它们的编码分别为00,01,10和11.则电文‘ABACCDA’ 的编码00010010101100, 总长为14位.
为减少编码长度,重新设 A,B,C,D四个字符的编码为0,00,1和01.则电文编码为000011010,总长为9位.
0000 ABA AAAA BB BAA
前缀编码指的是,任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。
在这里插入图片描述
在这里插入图片描述
利用赫夫曼树可以构造一种不等长的二进制编码,并且构造所得的赫夫曼编码是一种最优前缀编码,使所传电文的总长度最短。
数据文件压缩:
已知一个由A,B,C,D,E,F,G,H等八个字符组成的文件包含100个字符,如果对这八个字符都按等长编码:
000,001,010,011,100,101,110,111
则文件包含的总位数为:
100×3=300 位
现已知八个字符在文件中出现的个数分别为:
5,29,7,8,14,23,3,11
如果采用赫夫曼编码:
0110,10,1110,1111,110,00,0111,010
则文件的总位数为:
5×4+29×2+7×4+8×4+14×3+23×2+11×3
=259 位
压缩率为13%
建立赫夫曼树及求赫夫曼编码的算法

typedef struct 
  {
	unsigned int weight;
	unsigned int parent,lchild,rchild;
  }  HTNode, *HuffmanTree;

typedef char  **HuffmanCode;
void HuffmanCoding
(HuffmanTree &HT,HuffmanCode &HC,int *w, int n)
  {  HuffmanTree p;	char *cd;	int i,s1,s2,start;
     unsigned int c,f;
     if (n<=1) return;    // n为字符数目,m为结点数目
     int m=2*n-1;
     HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));
                                   //  0号单元未用
for (p=HT, i=1; i<=n; ++i,++p,++w)  
      { p->weight = *w; p->parent=0;
         p->lchild=0;p->rchild=0; }    // *p = { *w,0,0,0 };
 for (; i<=m;++i,++p)
     {  p->weight = 0; p->parent=0;
        p->lchild=0; p->rchild=0; }     //*p={ 0,0,0,0 };
 for (i=n+1; i<=m;++i)    //  建赫夫曼树
     {	Select(HT,i-1,s1,s2);
	HT[s1].parent=i;  HT[s2].parent = i;
	HT[i].lchild = s1;  HT[i].rchild = s2;
	HT[i].weight = HT[s1].weight + HT[s2].weight;
      }
//从叶子到根逆向求赫夫曼编码
  HC=  (HuffmanCode)malloc((n+1)*sizeof(char *));
  cd = (char*)malloc(n*sizeof(char));
  cd[n-1]='\0';
  for (i=1;i<=n;++i)
      {  start = n-1;
         for (c=i,f=HT[c].parent; f!=0; c=f,f=HT[f].parent)
              if (HT[f].lchild ==c) cd[--start]='0';
              else cd[--start]='1';
         HC[i]=(char *)malloc((n-start)*sizeof(char));
         strcpy(HC[i],&cd[start]);
         printf("%s\n",HC[i]);
       }
  free(cd);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cai-4

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值