数据结构程序员考试 第六章树和二叉树.docx
《数据结构程序员考试 第六章树和二叉树.docx》由会员分享,可在线阅读,更多相关《数据结构程序员考试 第六章树和二叉树.docx(23页珍藏版)》请在冰豆网上搜索。
数据结构程序员考试第六章树和二叉树
第六章树和二叉树
6.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);
删除:
ClearTree(&T);DestroyTree(&T);
DeleteChild(&T,&p,i);
DestroyTree(&T);
有向树:
1)有确定的根;
2)树根和子树根之间为有向关系
有序树和无序树的区别在于:
子树之间是否存在次序关系?
和线性结构的比较
线性结构树结构
第一个数据元素根结点
(无前驱)(无前驱)
最后一个数据元素多个叶子结点
(无后继)(无后继)
其它数据元素树中其它结点
(一个前驱、一个后继)(一个前驱、多个后继)
基本术语
结点:
数据元素+若干指向子树的分支
结点的度:
分支的个数
树的度:
树中所有结点的度的最大值
叶子结点:
度为零的结点
分支结点:
度大于零的结点
从根到结点的路径:
孩子结点、双亲结点、兄弟结点、
祖先结点、子孙结点
结点的层次:
假设根结点的层次为1,
第l层的结点的子树根结点的层次为l+1
树的深度:
树中叶子结点所在的最大层次
森林:
是m(m≥0)棵互不相交的树的集合
任何一棵非空树是一个二元组
Tree=(root,F)
其中:
root被称为根结点,F被称为子树森林
6.2二叉树的类型定义
二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。
二叉树的五种基本形态:
二叉树的主要基本操作:
查找:
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);
InsertChild(T,p,LR,c);
删除:
ClearBiTree(&T);DestroyBiTree(&T);
DeleteChild(T,p,LR);
二叉树的重要特性:
性质1:
在二叉树的第i层上至多有2i-1个结点
(i≥1)
性质2:
深度为k的二叉树上至多含2k-1个结点
(k≥1)
性质3:
对任何一棵二叉树,若他含有n0个叶子结点、n2个度为2的结点,则必存在关系式:
n0=n2+1
两类特殊的二叉树:
满二叉树:
指的是深度为k且含有2k-1个结点的二叉树
完全二叉树:
树中所含的n个结点和满二叉树中编号为1至n的结点一一对应
性质4:
具有n个结点的完全二叉树的深度为
log2n+1
性质5:
若对含n个结点的二叉树从上到下且从左至右进行1至n的编号,则对二叉树中任意一个编号为i的结点:
(1)若i=1,则该结点是二叉树的根,无双亲,
否则,编号为i/2的结点为其双亲结点;
(2)若2i>n,则该结点无左孩子,
否则,编号为2i的结点为其左孩子结点;
(3)若2i+1>n,则该结点无右孩子结点,
否则,编号为2i+1的结点为其右孩子结点。
6.3二叉树的存储结构
一、二叉树的顺序存储表示
classBiTree
{
private:
ArraySqBiTree;
…
}
显然,这种顺序存储结构仅适用于完全二叉树。
因为,在最坏的情况下,一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)却需要长度为2k-1的一维数组。
二、二叉树的链式存储表示
1.二叉链表
template
classBiTreeNode//二叉树的结点
{
protected:
BiTreeNode*lchild;
//指向左子树的指针
BiTreeNode*rchild;
//指向右子树的指针
public:
Elem*data;
//constructor
TreeNode(constElem&item,
BiTreeNode*lptr=NULL,
BiTreeNode*rptr=NULL);
//virtualdestructor.neededforAVLtreeclass
virtual~TreeNode(void);
//accessmethodsforthepointerfields
BiTreeNode*Left(void)const;
BiTreeNode*Right(void)const;
voidAssignLeft(TreeNode*p);
voidAssignRight(TreeNode*p);
//BinSTreeneedsaccesstoleftandright
friendclassBinSTree;
};
2.三叉链表
protected:
BiTreeNode*lchild;
//指向左子树的指针
BiTreeNode*rchild;
//指向右子树的指针
BiTreeNode*parent;
//指向双亲的指针
3.双亲链表
protected:
BiTreeNode*parent;
//指向双亲的指针
charLRTag;//指示左右的标志
4.线索链表
6.4二叉树的遍历
一、问题的退出
顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次。
“访问”的含义可以很广,如:
输出结点的信息等。
对“二叉树”而言,可以有三条搜索路径:
1.先上后下的按层次遍历;
2.先左(子树)后右(子树)的遍历;
3.先右(子树)后左(子树)的遍历。
二、先左后右的遍历算法
先(根)序的遍历算法:
若二叉树为空树,则空操作;否则,
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树。
中(根)序的遍历算法:
若二叉树为空树,则空操作;否则,
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树。
后(根)序的遍历算法:
若二叉树为空树,则空操作;否则,
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点。
三、算法的递归描述
//preorderrecursivescanofthenodesinatree.
template
voidPreorder(TreeNode*t,
voidvisit(Elem&item))
{
if(t!
=NULL)
{
visit(t->data);//访问结点
Preorder(t->Left(),visit);//遍历左子树
Preorder(t->Right(),visit);//遍历右子树
}
}
四、中序遍历算法的非递归描述
//returnaddressoflastnodeontheleftbranch//fromt,stackingnodesontheway.usedfor
//iterativeinorderscan.templateTreeNode
*GoFarLeft(TreeNode*t,
Stack*>&S){if(t==NULL)returnNULL;while(t->Left()!
=NULL)
{
S.Push(t);
t=t->Left();
}
returnt;
}
//inorderiterativescan
template
voidInorder_I(TreeNode*t,
voidvisit(Elem&c))
{
Stack*>S;
t=GoFarLeft(t,S);
//continueuntiltisNULL
while(t!
=NULL)
{
visit(t->data);
if(t->Right()!
=NULL)
t=GoFarLeft(t->Right(),S);
elseif(!
S.StackEmpty())
t=S.Pop();
else
t=NULL;//wearedone
}
}
五、遍历算法的应用举例:
1、统计二叉树中叶子结点的个数
//先序遍历
template
voidCountLeaf(TreeNode*t,
int&count)
{
if(t!
=NULL)
{
if(t->Left()==NULL&&
t->Right()==NULL)count++;
CountLeaf(t->Left(),count);
CountLeaf(t->Right(),count);
}
}
2、求二叉树的深度(后序遍历)
template
intDepth(TreeNode*t)
{
intdepthLeft,depthRight,depthval;
if(t==NULL)
depthval=0;
else
{
depthLeft=Depth(t->Left());
depthRight=Depth(t->Right());
depthval=1+(depthLeft>depthRight?
depthLeft:
depthRight);
}
returndepthval;
}
3、复制二叉树
//生成一个二叉树的结点
templateTreeNode*GetTreeNode(Elemitem,
TreeNode*lptr=NULL,TreeNode*rptr=NULL){TreeNode*p;p=newTreeNode(item,lptr,rptr);
if(p==NULL){
exit
(1);
}
returnp;
}
template
TreeNode
*CopyTree(TreeNode*t)
{
TreeNode*newlptr,*newrptr,*newnode;
if(t==NULL)returnNULL;
if(t->Left()!
=NULL)
newlptr=CopyTree(t->Left());
elsenewlptr=NULL;
if(t->Right()!
=NULL)
newrptr=CopyTree(t->Right());
elsenewrptr=NULL;
newnode=
GetTreeNode(t->data,newlptr,newrptr);
returnnewnode;
}
4、建立二叉树的存储结构
TreeNode*crtTree(char*str,int&i)
{//建立由字符串str[i..n]定义的二叉树,str[i]为根
TreeNode*t;
charCh=str[i];
if(Ch=='')t=NULL;
else
{
t=GetTreeNode(Ch);//creattherootnode
i++;
t->AssignLeft(crtTree(str,i));
i++;
t->AssignRight(crtTree(str,i));
}
returnt;
}
建表达式的树
由先缀表示式建树
由原表达式建树
a+bc(de/f)g
TreeNode*crtTree(char*str)
{
TreeNode*t;
Stack*>SD;
StackSP;
SP.Push();
p=str;ch=*p;
while(!
SP.StackEmpty()){
if(ch是字母){建叶子结点t;SD.Push(t);
else{
switch(ch){
case(:
SP.Push(ch);break;
case):
{
c=SP.Pop();
while(c!
=()
{建二叉树t;SD.Push(t);c=SP.Pop();}
break;}
defult:
{
while(!
SP.Gettop(c)&&(precede(c,ch)))
{c=SP.Pop();建二叉树t;SD.Push(t);}
if(ch!
=)SP.Push(ch);
elsec=SP.Pop();
break;
}//defult
}//switch
}//else
if(ch!
=){p++;ch=*p;}
}//while
t=SD.Pop();returnt;
}//crtTree
由表达式的先缀和中缀表示式建树
abcdefg
abcdefg
6.5线索二叉树
一、何谓线索二叉树?
在存储结构中保存遍历所得“前驱”和“后继”的信息
线索链表:
对二叉链表的结点增加两个标志域,并作如下规定:
若该结点的左子树不空,则lchild域的指针指向其左子树,且左标志域的值为0;否则,lchild域的指针指向其“前驱”,且左标志的值为1.
若该结点的右子树不空,则rchild域的指针指向其右子树,且右标志域的值为0;否则,rchild域的指针指向其“后继”,且右标志的值为1.
线索链表的结点类:
template
classThrNode
{
protected:
PointerTagltag;
PointerTagrtag;
ThrNodelchild;
TheNoderchild;
…
}
线索链表的遍历算法:
for(p=firstNode(T);p;p=Succ(p))
Visit(p);
中序线索化链表的遍历算法:
※中序遍历的第一个结点?
※在中序线索化链表中结点的后继?
template
voidInOrderTraverse_Thr(ThrNode*T,voidVisit(Elem&item))
{
//t指向头结点,头结点的左链lchild指
//向根结点,头结点的右链rchild指向中
//序遍历的最后一个结点。
//中序遍历二叉线索链表表示的二叉树T。
p=T->Left();//p指向根结点
while(p!
=T)
{//空树或遍历结束时,p==T
while(p->LTG()==Link)p=p->Left();
Visit(p->data);
//访问其左子树为空的结点
while(p->RTG()==Thread&&p->Right()!
=T)
{
p=p->Right();Visit(p->data);
//访问后继结点
}
p=p->Right();//p进至其右子树根
}
}//InOrderTraverse_Thr
如何建立线索链表?
在中序遍历过程中保存当前访问结点的“前驱”和“后继”信息
template
ThrNode*InOrderThreading(ThrNode*T)
{
//中序遍历二叉树T,并对其进行中序线
//索化,Thrt指向线索化之后的头结点。
ThrNode*Thrt;
Thrt=GetThrNode(,Link,Thread);
//建头结点
Thrt->AssignRight(Thrt);//右指针回指
if(T==NULL)Thrt->AssignLeft(Thrt);
//若二叉树空,则左指针回指
else
{
Thrt->AssignLeft(T);pre=Thrt;
InThreading(T);//中序遍历进行中序线索化
pre->AssignRight(Thrt);
pre->AssignRTag(Thread);
//最后一个结点线索化
Thrt->AssignRight(pre);
}
returnThrt;
}//InOrderThreading
附设指针pre,并始终保持指针pre指向当前访问的、指针p所指结点的前驱
template
voidInThreading(ThrNode*p)
{
if(p)
{
InThreading(p->Left());//左子树线索化
if(!
p->Left())
{
p->AssignLTag(Thread);
p->AssignLeft(pre);
}//建前驱线索
if(!
pre->Right())
{
pre->AssignRTag(Thread);
pre->AssignRight(p);
}//建后继线索
pre=p;//保持pre指向p的前驱
InThreading(p->Right());//右子树线索化
}
}//InThreading
6.6树和森林的表示方法
树的三种存储结构
一、双亲表示法:
#defineMAX_TREE_SIZE100
结点结构:
typedefstructPTNode{
Elemdata;
intparent;//双亲位置域
}PTNode;
树结构:
typedefstruct{
PTNodenodes[MAX_TREE_SIZE];
intr,n;//根结点的位置和结点个数
}PTree;
二、孩子链表表示法:
孩子结点结构:
typedefstructCTNode{
intchild;
structCTNode*next;
}*ChildPtr;
双亲结点结构
typedefstruct{
Elemdata;
ChildPtrfirstchild;//孩子链的头指针
}CTBox;
树结构:
typedefstruct{
CTBoxnodes[MAX_TREE_SIZE];
intn,r;//结点数和根结点的位置
}CTree;
三、树的二叉链表(孩子-兄弟)存储表示法
template
classCSNode
{
protected
CSNode*firstchild,
CSNode*nextsibling;
public
Elemdata;
……
};
森林和二叉树的对应关系
设森林F=(T1,T2,…,Tn);
T1=(root,t11,t12,…,t1m);
二叉树B=(LBT,Node(root),RBT);
则由森林转换成二叉树的转换规则为:
若F=Φ,则B=Φ;
否则,
由ROOT(T1)对应得到Node(root);
由(t11,t12,…,t1m)对应得到LBT;
由(T2,T3,…,Tn)对应得到RBT.
由二叉树转换为森林的转换规则为:
若B=Φ,则F=Φ;
否则,
由Node(root)对应得到ROOT(T1);
由LBT对应得到(t11,t12,…,t1m);
由RBT对应得到(T2,T3,…,Tn)
由此,树的各种操作均可转化成二叉树的操作来完成
换言之,和树对应的二叉树,其左、右子树的概念改变成:
左是孩子,右是兄弟
求树的深度的算法:
intTreeDepth(CSTreeT){
if(!
T)return0;
else{
h1=TreeDepth(T->Left());
h