二叉树的定义
二叉树是一个由结点构成的有限集合,这个集合或者为空,或者由一个根节点及两棵互不相交的分别称作这个根节点的左子树和右子树的二叉树组成。
二叉树并非一般的树形结构的特殊形式,它们是两种不同的数据结构。
二叉树与一般树形结构的主要区别在于
- 二叉树中每个非空结点最多只有两个子女,而一般的树形结构中每个非空结点可以有0到多个子女
- 二叉树中结点的子树要区分左子树和右子树,即是在结点只有一棵子树的情况下也要明确指出是左子树还是右子树。
满二叉树
如果一棵二叉树中所有终端节点均位于同一层次,且其他非终端结点的度数均为2
完全二叉树
如果一棵二叉树扣除其最大层次那层后即成为一棵满二叉树,且层次最大的那层的所有结点均向左靠齐
二叉树常用的存储结构有两种
顺序存储结构和链式存储结构。
这里主要介绍的二叉树的相关操作全部基于链式存储
链式储存方式下二叉树结点数据结构定义如下
typedef struct node
{
char data;
struct node *lchild,*rchild;
}bintnode;
根据前序遍历结果创建一棵二叉树
bintnode *createbintree()
{
char ch;
bintnode *t;
if((ch=getchar())=='#')
return NULL;
else
{
t=(bintnode*)malloc(sizeof(bintnode));
t->data=ch;
t->lchild=createbintree();
t->rchild=createbintree();
}
return t;
}
则输入:abd#e##fg###c##
二叉树的遍历
递归实现
1、前序遍历(根、左、右)abdefgc
void preorder(bintnode *t)
{
if(t)
{
printf("%c",t->data);
preorder(t->lchild);
preorder(t->rchild);
}
}
2、中序遍历(左、根、右)debgfac
void inorder(bintnode *t)
{
if(t)
{
inorder(t->lchild);
printf("%c",t->data);
inorder(t->rchild);
}
}
3、后序遍历(左、右、根)edgfbca
void postorder(bintnode *t)
{
if(t)
{
postorder(t->lchild);
postorder(t->rchild);
printf("%c",t->data);
}
}
非递归实现
在采用非递归的方式遍历二叉树时,需要使用一个栈来记录回溯点,以便将来进行回溯。
顺序栈的定义及部分操作
typedef struct stack
{
bintnode *data[100];
int tag[100];//为栈中每个元素设置标记,用于后序遍历
int top;
}seqstack;
void push(seqstack *s,bintnode *t)//进栈
{
s->data[s->top]=t;
s->top++;
}
bintnode *pop(seqstack *s)//出栈
{
if(s->top!=0)
{
s->top--;
return (s->data[s->top]);
}
else
{
return NULL;
}
}
1、前序遍历(根、左、右)
对于一棵树t,如果t非空,访问完t的根结点后,就进入t的左子树,但此时必须将t保存下来,以便访问完其左子树后,进入其右子树进行访问,即应该在t处设置一个回溯点,并将该回溯点保存于栈中。
void preorder(bintnode *t)
{
seqstack s;
s.top=0;
while(t||(s.top!=0))
{
if(t)
{
printf("%c",t->data);
push(&s,t);
t=t->lchild;
}
else
{
t=pop(&s);
t=t->rchild;
}
}
}
2、中序遍历(左、根、右)
对于一棵树t,如果t非空,首先应该进入t的左子树访问,此时由于t的根节点及右子树尚未被访问,因此必须将t保存起来放入栈中,以便访问完其左子树后,从栈中取出t,进行其根结点及右子树的访问。
void inorder(bintnode *t)
{
seqstack s;
s.top=0;
while(t||(s.top!=0))
{
if(t)
{
push(&s,t);
t=t->lchild;
}
else
{
t=pop(&s);
printf("%c",t->data);
t=t->rchild;
}
}
}
3、后序遍历(左,右,根)
因此对于一棵树t,如果t非空,首先应进入t的左子树访问,此时由于t的右子树及根结点尚未访问,因此必须将t保存起来,放入栈中,以便访问完其左子树后,从栈中取出t,进行其右子树及根结点的访问。这里需要注意的是,当一个元素置于栈顶即将处理时,其左子树的访问一定已经完成,如果其右子树不为空,则下面对右子树进行访问,而此时栈顶元素是不能出栈的,因为它作为根结点,其本身的值还尚未被访问;只有等到右子树也访问完成后,该栈顶元素才能出栈,并输出它的值。
所以必须用到seqstack类型中的tag数组,数组有0和1,由于标识栈中每个元素的状态。当一个元素进栈时,其对应的tag值为0;当它第一次处于栈顶即将被处理时,其tag值为0,意味着应该访问其右子树,于是将其右子树作为当前处理的对象,此时该栈顶元素仍然保留在栈中,并将其对应的tag值改为1;当其右子树访问完成以后, 该元素又一次的位于栈顶,而此时其tag值为1,意味着其右子树已经访问完成,接下来应该访问它本身,并将其出栈。
void postorder(bintnode *t)
{
seqstack s;
s.top=0;
while(t||(s.top!=0))
{
if(t)
{
push(&s,t);
t=t->lchild;
}
else
{
if(s.tag[s.top-1]==0)//还没有访问右子树
{
t=s.data[s.top-1];
s.tag[s.top-1]=1;
t=t->rchild;
}
else//已经访问完右子树,现在访问根结点
{
t=pop(&s);
printf("%c",t->data);
t=NULL;
}
}
}
}
注意一下,在else里的if语句中,最后有t=NULL,可以这样理解,在最后一个结点出栈以后是保存在t中的,如果没有把t置空,那么就不会退出while循环,导致死循环。
二叉树的其他运算实现(大部分为递归的方法,之后补充非递归的方法。)
二叉树的查找 locate( t,x)
返回的是二叉树t中值为x的结点的位置
首先与根结点的值进行比较,若相等,则返回指向根结点的指针;否则,进入t的左子树查找,若查找仍未成功,则进入t的右子树查找;在查找过程中,如果找到值为x的结点,则返回指向该结点的指针,否则就意味着该棵二叉树中没有无值为x的结点。(注意:在主函数中使用该函数时,或许要用到getchar()吸收前面的空格)
bintnode *locate(bintnode *t,char x)
{
bintnode *p;
if(t==NULL)
return NULL;
else
{
if(t->data==x)
return t;
else
{
p=locate(t->lchild,x);
if(p)
return p;
else
return locate(t->rchild,x);
}
}
}
统计二叉树中的结点个数 numofnode()
int numofnode(bintnode *t)
{
if(!t) return 0;
else return numofnode(t->lchild)+numofnode(t->rchild)+1;
}
判断二叉树是否等价 isequal(t1,t2)
二叉树等价当且仅当其根结点的值相等,且其左右子树对应等价。判断两棵二叉树的左子树是否等价及判断两棵二叉树的右子树是否等价与判断两棵二叉树是否等价过程完全相同。
等价返回1,不等价返回0
int isequal(bintnode *t1,bintnode *t2)
{
int t=0;
if(t1==NULL && t2==NULL) return 1;
else
{
if(t1!=NULL && t2!=NULL)
{
if(t1->data==t2->data)
{
if(isequal(t1->lchild,t2->lchild))
{
return isequal(t1->rchild,t2->rchild);
}
}
}
}
return 0;
}
求二叉树的深度 depth(t)
如果该树是空树,那么返回0,否则其高度就是其左子树和右子树高度的最大值再加1。
int depth(bintnode *t)
{
int h,lh,rh;
if(t==NULL) h=0;
else
{
lh=depth(t->lchild);
rh=depth(t->rchild);
h=lh>rh?lh+1:rh+1;
}
return h;
}
本文介绍了二叉树的定义,包括满二叉树和完全二叉树的概念,以及二叉树的链式存储结构。重点讲解了二叉树的遍历方法,包括递归和非递归实现的前序、中序、后序遍历,并提到了二叉树的查找、结点计数、等价判断和深度计算等操作。

1万+

被折叠的 条评论
为什么被折叠?



