tree
1,树的类型定义
2,二叉树的类型定义
3,二叉树的存储结构
4,二叉树的遍历
5,线索二叉树
6,树和森林的表示方法
7,树和森林的遍历
8,哈弗曼树和哈弗曼编码
一,树的定义 1,数据对象D:D是具有相同特性的数据元素的集合
2,数据关系R:若D为空集,则称为空树。
否则:
(1)在D中存在唯一的称为根的数据元素root。
(2)当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,....Tm,其中每 一棵子集本身又是一棵符合本定义的树,称为根root的子树。
3,基本操作:
查找类,插入类,删除类。
查找类: 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棵子树
4,树的分类 (1)有向树:
1,有确定的根;
2,树根和子树根之间为有向关系。
(2)有序树:
1,树中节点的各子树之间的先后次序是有意义的,不能互换,否则就称为另一棵树了。子 树之间存在确定的次序关系
(3)无序树:
1,树中节点的各子树之间的先后次序无意义,可以互换。子树之间不存在确定的次序关系。
5,线性结构和树形结构的对比
1,线性结构:
第一个数据元素(无前驱) 最后一个元素(无后继) 其他元素(一个前驱、一个后继)
2,树形结构:
根节点(无前驱) 多个叶子节点(无后继) 其他数据元素(一个前驱,多个后继)
6,基本术语
一,节点:数据元素+若干指向子树的分支
二,结点的度:分支的个数
三,树的度:树中所有结点的度的最大值
四,叶子结点(终端结点):度为零的结点
五,分支结点(非终端结点):度大于零的结点
六(从根到结点的)路径:由从根到该结点所经分支和结点构成
七,孩子结点、双亲结点、兄弟结点、堂兄弟结点、祖先结点、子孙结点。
八,结点的层次:假设根结点的层次为1,依次加1
九,树的深度:树中叶子结点所在的最大层次
7,森林: 是m(m>=0)棵互不相交的树的集合。 任何一棵非空树是一个二元组 Tree=(root,F),其中root被称为根结点 F被称为子树森林 二,二叉树的类型定义:二叉树是树的基础,一般的树可以转化为二叉树来处理。
1,二叉树的定义:二叉树或为空树,或是由一个根结点加上两棵分别称为左子树和右子树的,互不相交的二叉树组成(即左,右子树次序不能颠倒)。
2,二叉树的五种基本形态: 一,空树 二,只含根结点 三,左右子树均不为空树 四,右子树为空树 五,左子树为空树
3,二叉树的主要基本操作: 查找类,插入类,删除类
一,查找类 Root(T);
Value(T,e);
Parent(T,e);
LeftChild(T,e);
RightChild(T,e);
LeftSibling(T,e);
RightSibling(T,e);
BiTreeEmpty(T);
BiTreeDepth(T);
PreOrderTraverse(T,Visit());
InOrderTraverse(T,Visit());
PostOrderTraverse(T,Visit());
LevelOrderTraverse(T,Visit());
二,插入类
InitBiTree(&T);
Assign(T,&e,value);
CreateBiTree(&T,definition);
InsertChiild(T,p,LR,c);
三,删除类 clearBiTree(&T); DestroyBiTree(&T); DeleteChild(T,p,LR);
四,二叉树的性质
1,在二叉树的第i层上至多有2的(i-1)次方
2,在深度为k的二叉树上至多有2的k次方减一个结点(k>=1)
3,对任何一棵二叉树,若它含有n0个叶子结点、n2个度为2的结点,则比存在关系式:n0=n2+1 4,具有n个结点的完全二叉树的深度为[log2 n]+1.
5,若对含n个结点的完全二叉树从上到下且从左至右进行1至n的编号,则对完全二叉树中任意为i的结点:
(1)若i=1,则该结点是二叉树的根,无双亲,否则,编号为[i/2]的结点为其双亲结点; (2)若2i>n,则该结点无左孩子,否则,编号为2i的结点为其左孩子结点;
(3)若2i+1>n,则该结点无 右孩子结点,否则,编号为2i+1的结点为其右孩子的结点。 五,两类特殊形态的二叉树:
1,满二叉树:指的是深度为k且含有2的k次方减一个结点的二叉树
2,完全二叉树:树中所含的n个结点和满二叉树中编号为1至n的结点一一对应。 三,二叉树的存储结构
1,二叉树的顺序存储表示(适合存储完全二叉树)
(1)即用一组地址连续的存储单元集中从上至下,从左至右存储完全二叉树上的结点元素。也就是说,将完全二叉树上编号为i的结点存放在数组下标为(i-1)的分量中。
(2)若要存储一棵一般的二叉树,结点的存放应与完全二叉树上的结点对照,存储在数组的相应分量中。用“0”表示不存在该结点。可能会浪费很多存储空间,单支树就是一个极端情况。 2,二叉树的链式存储表示
3,C语言描述
(1)顺序
#define MAX_TREE_SIZE 100;
typedef TElemType SqBiTree[MAX_TREE_SIZE];
SqBiTree bt;
(2)链式
二叉链表
typedef struct BiTNode{
TElemType data; struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
三叉链表 typedef struct TriTNode{
struct TriTNode *parent;//双亲指针 struct TriTNode *lchild;//左孩子指针 TElemType data; struct TriTNode *rchild;//右孩子指针
}TriTNode,*TriTree;
四,二叉树的遍历
1,遍历的概念
2,先左后右的遍历算法
3,算法的递归描述
4,遍历算法应用举例
一,遍历的概念:按照某种顺序不重复地访问二叉树中的所有结点。此处的访问可以是输出、修改等操作,根据实际需要而定。(使得每个结点均被访问一次,而且仅被访问一次。)
二,二叉树可以有两种搜索路径:
1,先上后下的按层次遍历;
2,先左后右的遍历 先序的遍历算法,中序的遍历算法,后序的遍历算法。
三,先序遍历算法: 若二叉树为空树,则空操作;否则,
(1)访问根结点; (2)先序遍历左子树; (3)先序遍历 右子树 void Preorder(BiTree T) {
if(T)//如果树不为空 {
visit(T->data);//输出根结点
Preorder(T->lchild);//先序遍历左子树
Preorder(T->rchild);//先序遍历右子树
}
}
四,中序遍历算法: 若二叉树为空树,则空操作;否则,
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树。
void Inorder(BiTree T)
{ if(T)//如果树不为空
{ Inorder(T->lchild);//中序遍历左子树
visit(T->data);//输出根结点
Inorder(T->rchild);//中序遍历右子树
}
}
五,后序遍历算法: 若二叉树为空树,则空操作;否则,
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点。
void Pastorder(BiTree T) {
if(T)//如果树不为空 {
Pastorder(T->lchild);//后序遍历左子树
Pastorder(T->rchild);//后序遍历右子树
visit(T->data);//输出根结点;
}
}
六,遍历算法的应用举例
1,统计二叉树中叶子结点的个数(先序遍历)
2,求二叉树的深度(后序遍历)
3,简历二叉树的存储结构
4,求二叉树节点的个数。
1,int CountLeaf(BiTree T) {
if(!T) return 0;
else if((!T->lchild)&&(!T->rchild))
return 1;
else{
nl=CountLeaf(T->lchild);
nr=CountLeaf(T->rchild);
return nl+nr;
}
}
2,int Depth(BiTree T) {
if(!T)
return 0;
else{
dl=Depth(T->lchild);
dr=Depth(T->rchild);
dep=1+(dl>dr?dl:dr);
}
return dep;
}
3,status CreateBiTree(BiTree &T) {
scanf(&ch);
if(ch=="")
T=null;
else{
if(!(T=(BiTree)malloc(sizeof(BiTNode))))
exit(OVERFLOW);
T->data=ch;
CreateBiTree(T->lchild);//构造左子树;
CreateBiTree(T->rchild);//构造右子树;
}
return OK;
}
4,int NodeCount(BiTree T) {
if(!T) return 0;
else{
nl=NodeCount(T->lchild);
nr=NodeCount(T->rchild);
return nl+nr+1;
}
}
七,由二叉树的先序和中序序列建树
主要思想: 1,先序序列中第一个为“根”,标出之;
2,在中序序列中标出“根”,并分出左右子树;
3,在先序序列中标出左右子树;
4,分别对左,右子树重复以上步骤。
八,由二叉树的后序和中序序列建树 主要思想:
1,后序序列中最后一个为“根”,标出之;
2,在中序序列中标出“根”,并分出左,右子树;
3,在后序序列中标出左,右子树;
4,分别对左、右子树重复以上步骤。
九,由二叉树的后序和先序序列不能唯一确定一棵二叉树。
十,在含有n个结点的二叉树中,含有n+1个空指针。
五,线索二叉树
1什么是线索二叉树 2,线索链表的遍历算法 3,如何建立线索链表
一,线索二叉树是指需要借助含有n个结点的二叉树中(n+1)个空指针域,作为线索,直接指向遍历序列中“前驱”或“后继”结点。 包含“线索”的存储结构,称为“线索链表” 与其相应的二叉树,称作“线索二叉树” 对线索链表中结点的约定: 在二叉链表的结点中增加两个标志域,并做如下规定:
1,若该结点的左子树不空,则Lchild域的指针指向其左子树,且左标志域的值为“指针Link=0“否则,Lchild域的指针指向其”前驱“,且左标志的值为”线索Thread=1".
2,若该结点的右子树不空,则rchild域的指针指向其右子树,且右标志域的值为”指针Link=0“;否则,rchilid域的指针指向其”后继“且右标志的值为”线索Thread=1“。 如此定义的二叉树的存储结构称作”线索链表“。
3,线索链表的类型描述: typedef enum{Link,Thread}PointerThr//Link==0;指针,Thread==1;线索 typedef structBiThrNod { TElemType data; struct BiThrNode *lchild,*rchild;//左右指针 PointerThr LTag,RTag;//左右标志 }BiThrNode,*BiThrTree;
二,线索链表的遍历算法: for(p=firstNode(T);p;p=Succ(p)) visit(p); 对中序线索化链表的遍历算法
1,中序遍历的第一个结点 左子树上处于”最左下“(没有左子树)的结点。
2,在中序线索化链表中结点的后继 若无右子树,则为后继线索所指向的结点;否则为对其右子树进行中序遍历时访问的第一个结点。
void InOrder(BiThrTree T,void (*Visist)(TElemType e)) {
p=T->lchild; //p指向根结点
while(p!=T) //空树或遍历结束时,p==T {
while(p->LTag==Link) p=p->lchild;//第一个结点
while(p->RTag==Thread&&p->rchild!=T) {
p=p->rchild; Visit(p->data);//访问后继结点
} p=p->rchild;
}
}
六,树和森林 一,树的三种存储结构 1,双亲表示法 2,孩子链表表示法 3,树的二叉链表(孩子-兄弟)存储表示法 双亲表示法:
1,定义:用一组地址连续的空间存储树的结点,同时在每个结点中附设一个指示器指示双亲结点在链表中的位置。(即:保持结点信息和双亲的下标)
2,特点:求双亲简便,求孩子麻烦。
3,C语言的类型描述 #define MAX_TREE_SIZE 100
结点结构 typedef struct PTNode{ ElemType data; int parent; }PTNode;
树结构 typedef struct{ PTNode nodes[MAX_TREE_SIZE]; int r,n;//根结点的位置和结点个数 }PTree; 孩子链表表示法:
1,定义:把每个结点的孩子结点链成单链表。
2,特点:求孩子方便,求双亲麻烦。
3,可能把孩子链表表示法和双亲表示法结合一起。data parent firstchild...
4,C语言的类型描述:
孩子结点结构: typedef struct CTNode{ int child; struct CTNode *next; }*ChildPtr;
双亲结点结构 typedef struct{ ElemType data; ChildPtr firstchild;//孩子链的头指针 }CTBox;
树结构: typedef struct{ CTBox nodes[MAX_TREE_SIZE]; int n,r;//结点数和根结点的位置 }
树的二叉链表存储表示法
1,定义:链表中的结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点。
2,结点结构:firstchild data nextsibling
3,C语言的类型描述: typedef struct CSNode{ TElemType data; struct CSNode *firstchild,*nextsibling; }CSNode,*CSTree;
二,树和二叉树的转换
1,转换规则 (1)树的根 <---> 二叉树的根 (2)树的第一个孩子 <---> 左孩子 (3)右兄弟 <---> 右孩子 注:由树转换得到的二叉树的右子树永远为空!
三,森林和二叉树的转换 1,规则: (1)森林中第一棵树的根变成二叉树的根 (2)森林中其他树的根当成第一棵树的根的兄弟 (3)把森林中每棵树都转换为相应的二叉树 注:由森林转换得到的二叉树的右子树可能不为空!
七,树和森林的遍历 树的遍历 把树看成两个部分:
1,树的根 2,根的子树森林 树的遍历可有三条搜索路径: 先根遍历:若树不空,则先访问根结点,然后依次先根遍历各棵子树。 后根遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点。 按层次遍历:若树不空,则自上而下自左至右访问数中每个结点。
森林的遍历 森林由三部分构成:
1,森林中第一棵树的根结点
2,森林中第一棵树的子树森林;
3,森林中其他树构成的森林 先序遍历:若森林不空,则访问森林中第一棵树的根结点; 先序遍历森林中第一棵树的子树森林;先序遍历森林中(除第一棵树之外)其余树构成的森林。 即:依次从左至右对森林中的每一颗树进行先序遍历。
后序遍历:若森林不空,则后序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点;后序遍历森林中(除第一棵树之外)其余树构成的森林。 即:依次从左至右对森林中的每一颗树进行后序遍历
八,哈弗曼树和哈弗曼编码
1,几个预备概念
2 最优树的定义
3 如何构造最优树
4 前缀编码
1,路径:树中一个结点到另一个结点所经过的分支
2,路径长度:路径上分支数目。
3,树的路径长度:从根到每一个结点的路径长度之和。(完全二叉树是路径长度最短的二叉树) 4,结点的路径长度定义为:从根结点到该结点的路径上的分支的数目。
5,结点的带权路径长度定义为:结点的权值乘以该结点的路径长度。
6,树的带权路径长度定义为:树中所有叶子结点的带权路径长度之和WPL(T)=&Wk*Lk(对所有叶子结点)
7,最优二叉树:在所有含n个叶子结点、并带相同权值的m叉树中,必存在一棵其带权路径长度取最小值的树,称为最优二叉树。 二,如何构造最优树(哈弗曼树) 问题:根据给定的n个权值{w1,w2,...,wn},构造一棵以这些权值为叶子的哈弗曼树? (哈弗曼算法)以二叉树为例: (1)用给定的n个权值构造n棵以各权值为根的二叉树。
(2)选取其根结点的权值为最小的两颗二叉树,分别作为左、右子树构造一棵新的二叉树,并且这课新的二叉树根结点的权值为其左、右子树根结点的权值之和;并删去这两棵二叉树,同时加入新生成的二叉树;
(3)重复第(2)步,直至生成一棵树为止。
三,前缀编码 指的是,任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。 利用哈弗曼树可以构造一种不等长的二进制编码,并且构造所得的哈弗曼编码是一种最优前缀编码,即:使所传电文的总长度最短。
四,按层次遍历二叉树 #define queue {
bitree v[MAXSIZE];
int* front,rear;//对头,队尾指针
}q void levelorder(bitree t) {
q.front=q.rear=0;
if(t!=NULL)
printf(" %d",t->data); //遍历根结点
q.v[q.rear]=t; q.rear=q.rear+1;
while(q.front<q.rear) {
s=q.v[q.front];
q.front=q.front;
if(s->lchild!=null) {
printf(" %d",s->lchild->data);
q.v[q.rear]=s->lchild;
q.rear=q.rear+1; }
if(s->rchild!=null) {
printf(" %d",s->rchild->data);
q.v[q.rear]=s->rchild;
q.rear=q.rear+1; }
}
}