树
定义
树(tree)是包含n(n>0)个结点的有穷集,其中:
(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。
树也可以这样定义:树是由根结点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个 层次结构 。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或称为树根。
我们可以形式地给出树的递归定义如下:
单个结点是一棵树,树根就是该结点本身。
设T1,T2,..,Tk是树,它们的根结点分别为n1,n2,..,nk。用一个新结点n作为n1,n2,..,nk的父亲,则得到一棵新树,结点n就是新树的根。我们称n1,n2,..,nk为一组兄弟结点,它们都是结点n的子结点。我们还称T1,T2,..,Tk为结点n的子树。
空集合也是树,称为空树。空树中没有结点。
如上图所示,黑色的是树形结构,粉色框起来的部分是整棵树,箭头所指是整棵树根结点,它的每一个子节点都可以作为它子树的根,如图,蓝色和橙色是粉色的子树,绿色是蓝色的子树。
相关术语
结点的度:一个结点含有的子结点的个数称为该结点的度;
叶结点或终端结点:度为0的结点称为叶结点;
非终端结点或分支结点:度不为0的结点;
双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点;
孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点;
兄弟结点:具有相同父节点的结点互称为兄弟结点;
树的度:一棵树中,最大的结点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中结点的最大层次;
堂兄弟结点:双亲在同一层的结点互为堂兄弟;
节点的祖先:从根到该结点所经分支上的所有结点
子孙:以某结点为根的子树中任一结点都称为该结点的子孙。
森林:由m(m>=0)棵互不相交的树的集合称为森林;
二叉树
定义
与树的区别
1. 树对于结点的度没有限制,而二叉树每个结点的度最大为2。
2. 树的结点可以无序,但二叉树结点有左右之分。
完全二叉树
我们不难发现,对于二叉树来说,每一层都有最大结点数量,比如第2层最多2个结点,第3层最多4个,第4层最多8个,第i层最多2i-1个。
完全二叉树的定义,就是假设树的高度为h,则除了第h层以外,每一层都达到了最大结点数,并且第h层的结点都是从左到右依次排列的,如图所示,除了(a),其他都不是完全二叉树:
满二叉树
满二叉树是完全二叉树的特殊情况,顾名思义就是对于高度为h的二叉树,每一层都“满了”,都达到了最大结点数量,如下图就是一棵满二叉树,当然,它也是完全二叉树:
二叉树的基本性质
1. 在非空二叉树中,第i层的结点总数不超过2i-1 ,i>=1;
2. 深度为h的二叉树最多有2h-1个结点(h>=1),最少有h个结点;
3. 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
4. 具有n个结点的完全二叉树的深度为log2(n+1)
5. 有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若I为结点编号则 如果I>1,则其父结点的编号为I/2;
(1) 如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;
(2) 如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。
6. 给定N个节点,能构成h(N)种不同的二叉树。h(N)为卡特兰数的第N项。h(n)=C(2*n,n)/(n+1)。
7. 设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i[4]
二叉树的存储结构
1,二叉树的顺序存储结构
图1 完全二叉树的顺序存储结构
如图1所示,可以很直观地看到完全二叉树的顺序存储结构。
2,二叉树的链式存储结构
二叉树的链式存储结构称为二叉链表,每个结点最多有两个孩子,所以为其设计一个数据域和两个指针域是比较自然的想法,我们称之为二叉链表。
图2 二叉链表的结点结构
结构定义代码为:
图3 二叉树的链式存储结构
三,二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。二叉树的遍历方式看可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为以下四种:
--前序遍历
--中序遍历
--后序遍历
--层序遍历
1,前序遍历
如果二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
图4 前序遍历图
2,中序遍历
若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
图5 中序遍历图
3,后序遍历图
若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点。
图6 后序遍历图
4,层序遍历图
若树为空,则空操作返回,否则从树的第一层,也就是从根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
图7 层序遍历图
举例:
1503.输出以二叉树表示的算术表达式
时限:1000ms 内存限制:10000K 总时限:3000ms
描述
编写一个程序,输出以二叉树表示的算术表达式,若表达式中有括号,则应在输出时加上
输入
按先序输入一行字符,其中#表示取消建立子树节点
输出
输出以二叉树表示的算术表达式
输入样例
*+a(###b#)##c##
输出样例
(a+b)*c
提示
注意,在本题中'('和')'也作为二叉树中的节点值,在程序编写过程中可以以课本中算法6.4为基础。
typedef struct ctree
{
char data;
struct ctree *lchild;
struct ctree *rchild;
}CTREE;
CTREE* createtree()
{
char a;
CTREE *T;
scanf("%c",&a);
if(a=='#')
T=NULL;
else
{
T=(CTREE *)malloc(sizeof(CTREE));!!!!主要问题在这里
T->data=a;
T->lchild=createtree();
T->rchild=createtree();
}
return T;
}
void vist(CTREE *T)
{
if(T!=NULL)
{
vist(T->lchild);
printf("%c",T->data);
vist(T->rchild);
}
}
int main()
{
CTREE *T;
T=createtree();
vist(&T);
return 0;
}
下面这种方法有误:
typedef struct ctree
{
char data;
struct ctree *lchild;
struct ctree *rchild;
}CTREE,*btree;
void createtree(CTREE *T)
{
char a;
scanf("%c",&a);
if(a=='#')
T=NULL;
else
{
T=(CTREE *)malloc(sizeof(CTREE));
T->data=a;
createtree(T->lchild);
createtree(T->rchild);
}
}
void vist(CTREE *T)
{
if(T!=NULL)
{
vist(T->lchild);
printf("%c",T->data);
vist(T->rchild);
}
}
int main()
{
CTREE p;
//btree T 与CTREE *T;相同
createtree(&p);
vist(p);
return 0;
}
这段没有结果的原因在与:地址的问题,将结构体型变量P的地址传入createtree函数后,指针T指向p的地址,但是,在分配内存是,T又指向了分配的内存区域,导致了P地址的丢失。使得遍历时找不到根节点。
我进行了debug来观察:
可以看到第一次调用函数时T指向的是P的地址
但是在分配内存后我们发现T的地址发生了改变,导致了P地址的丢失,使得遍历时传入P地址没有结果!
线索二叉树
前序建立二叉树
中序遍历,则中序线索化
typedef enum
{
link,thread
}pointernag;
//线索存储标志位
//link(0) 表示指向左右孩子的指针
//thread(1) 表示指向前驱后继的线索
typedef struct bitree
{
char data;
struct bitree *lchild,*rchild;
pointernag ltag;
pointernag rtag;
}Btree;
//创建一个二叉树,一般用前序遍历的方式
Btree *pre;
Btree *createbitree()
{
char c;
scanf("%c",&c);
Btree *T;
if(c==' ')
{
T=NULL;
}
else
{
T=(Btree *)malloc(sizeof(Btree));
T->data=c;
T->ltag=link; //建树的时候初始化它都有左右孩子
T->rtag=link;
T->lchild=createbitree();
T->rchild=createbitree();
}
return T;
}
//中序遍历线索化
void InThreading(Btree *T)
{
if(T)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->ltag=thread;
T->lchild=pre;
}
if(!pre->rchild )
{
pre->rtag=thread;
pre->rchild=T;
}
pre=T;
InThreading(T->rchild);
}
}
Btree *Inorderthreading(Btree *T)
{
Btree *p;
p=(Btree *)malloc(sizeof(Btree));
p->ltag=link;
p->rtag=thread;
p->rchild=p;
if(!T)
{
p->lchild=p;
}
else
{
p->lchild=T;
pre=p;
InThreading(T);
pre->rchild=p;
pre->rtag=thread;
p->rchild=pre;
}
return p;
}
//中序遍历二叉树,非递归
void InorderTraverse(Btree *T)
{
Btree *p;
p=T->lchild;//这里的T就是头结点
while(p!=T)
{
while(p->ltag==link)
{
p=p->lchild;
}
printf("%c",p->data);
while(p->rtag==thread&&p->rchild!=T)
{
p=p->rchild;
printf("%c",p->data);
}
p=p->rchild;
}
}
int main()
{
Btree *p,*T;
T=createbitree();
p=Inorderthreading(T);
InorderTraverse(p);
printf("\n");
return 0;
}