1.构造二叉树:
采用先序遍历的顺序来读入数据,递归构造二叉树。相应代码:
int CreateBiTree(TREE **t)
{
char ch;
scanf("%c",&ch);
if(ch=='#') (*t)=NULL;
else
{
*t=(TREE *)malloc(sizeof(TREE));
if(t==NULL)
return 0;
(*t)->data=ch;
CreateBiTree(&((*t)->lchild));
CreateBiTree(&((*t)->rchild));
}
return 1;
}
在这个函数中传入的TREE**t是指针的指针,由于在一颗书中要创建新的节点放到根节点(TREE *)下,这必然要改变指针的指向,因此用指针的指针,否则这里的t只是一个指针,调用完后就失效了,root这棵树还是一颗空树。这里用‘#’来表示空,每次读入都要判断,若不空就为*t开辟一个内存空间,将值存到该节点,然后再递归构造它的左子树和右子树。在调用时注意参数是&((*t)->lchild),而不是(*t)->lchild)。
2.遍历二叉树(先序、中序、后序):
2.1.先序遍历递归算法:
void PreOrder1(TREE *t)
{
TREE *p=t;
if(t==NULL)
printf("空树!!!");
else
{
printf("%c",t->data);
PreOrder1(t->lchild);
PreOrder1(t->rchild);
}
}
该递归算法很形象地体现了先序遍历的思想,先访问,再遍历左子树,后遍历右子树。可见递归的确非常简洁易于理解。
2.2、先序遍历非递归算法:
void PreOrder2(TREE *t)
{
TREE *st[100],*p;//顺序栈st中保存的是节点指针,而不是节点值
int top=-1;
if(t==NULL)
printf("空树!!!");
else
{
top++;
st[top]=t;//根节点进栈,由于栈中保存的是节点指针,因此不能直接放t->data
while(top>-1)//在栈不空的时候循环遍历该节点和左子树右子树
{
p=st[top];//栈顶元素出栈
top--;
printf("%c ",p->data);//体现了先序遍历,先访问该节点
//此处注意入栈顺序,考虑到栈的特点后进先出,所以你先要遍历的是左子树,左子树后入栈
if(p->rchild!=NULL)//若有右孩子,右孩子入栈
{
top++;
st[top]=p->rchild;
}
if(p->lchild!=NULL)
{
top++;
st[top]=p->lchild;
}
}
printf("\n");
}
}
该算法应用栈来存放数据,由于栈本身的特点就是只对一段进行操作,后进先出,用它来存放节点数据,由先序遍历的过程可知,先访问根节点,再访问左子树后访问右子树。因此先将根节点进栈,在栈不空的时候循环:出栈p,访问*p节点,将其右孩子节点进栈,再将左孩子节点进栈。
2.3、中序遍历递归算法:
//中序遍历的递归算法
//与先序遍历类似,变的只是节点访问的次序
void InOrder1(TREE *t)
{
TREE *p=t;
if(t==NULL)
printf("空树!!!");
else
{
printf("%c",p->data);
InOrder1(t->rchild);
InOrder1(t->lchild);
}
}
中序遍历和先序遍历类似,只是访问的顺序变了一下。
2.4、中序遍历的的非递归算法:
void InOrder2(TREE *t)
{
TREE *st[100],*p;
int top=-1;
if(t==NULL)
printf("空树!!!");
else
{
p=t;//p指向根节点
while(top>-1 || p!=NULL)//当栈不空或者数不空的时候循环
{
while(p!=NULL)//当树不空就一直遍历它的左子树,直到左子树为空停止
{
top++;
st[top]=p;
p=p->lchild;
}
if(top>-1)
{
p=st[top]; //出栈*p节点,它没有右孩子或右孩子已访问
top--;
printf("%c ",p->data);//访问它
p=p->rchild;//访问栈顶节点的右孩子,并将它入栈,若右孩子还有左子树返回到while直到左子树为空
}
}
printf("\n");
}
}
由中序遍历的过程可知,采用一个栈保存需要返回节点的指针。先扫描(并非访问)根节点的所有左节点并将他们一一进栈。然后出栈一个节点*p,显然*p节点没有左孩子节点或者左孩子节点已经访问过(表明该节点的左孩子均已访问过),然后扫描该节点的右孩子,将其进栈,再扫描该右孩子节点的所有左节点一一进栈,如此这样,直到栈空。
2.5、后序遍历的递归算法:
void PostOrder1(TREE *t)
{
if(t==NULL)
printf("空树!!!");
else
{
PostOrder1(t->lchild);
PostOrder1(t->rchild);
printf("%c ",t->data);
}
}
2.6、后续遍历的非递归算法:
void PostOrder2(TREE *t)
{
TREE *st[100],*p=t,*q;
int top=-1;
int flag;
do
{
while(p!=NULL)
{
top++;
st[top]=p;
p=p->lchild;
}
q=NULL;//q指向栈顶节点的前一个已经访问过的节点
flag=1;//设置flag=1表示处理栈顶元素
while( flag==1 && top!=-1)
{
p=st[top];
if(p->rchild==q)//该右孩子不存在,或右孩子已经被访问,就访问该节点
{
printf("%c ",p->data);//访问它
top--;//退栈
q=p;//让q指向刚刚访问过的节点,以便下一次判断
}
else//右孩子没有被访问
{
p=p->rchild;//p指向右孩子
flag=0;//表示要退出这个循环,重新进入上一个循环去判断这个节点的左孩子
}
}
}while(top!=-1);
printf("\n");
}
后续遍历的非递归算法,采用一个栈保存需要返回的节点指针,先扫描根节点的所有左节点并一一进栈,出栈一个*p,即当前节点,然后扫描该节点的右孩子节点并进栈,再扫描该右孩子节点的所有左节点进栈,当一个节点的左、右孩子均访问过后在访问该节点,直到栈为空。
从上述过程可知,栈中保存的是当前节点*p的所有祖先节点,均为访问过。该算法比先序和中序复杂,由于它不像先序那样直接访问根节点不必保存,它必须保存所有节点,在访问坐姿束河右子树时,该节点会被遍历到两次,在寻找他的左子树时遍历到一次,然后退栈,当它的右子树没有被访问过且是存在的,先访问他的右子树,最后再次访问该节点。所以这里需要一个变量来专门记录这种情况,就是这里的flag,首先将一棵树的所有左孩子进栈,q用来保存指向栈顶的前一个已经访问过的节点,flag设为1,表示要处理栈顶元素,再进入循环,读栈顶元素,判断他是否有或是否访问过右子树,若已经访问(这里就用到了q,若刚刚访问的q就是现在这个节点的右孩子,那说明访问过了,没有右孩子的情况也一样,因为q就是NULL),那么就取出栈顶元素并输出。若没有访问过,直接访问指向他的右孩子,将flag设为0,因为此时栈顶元素不处理,将要处理他的右孩子了,要退出这个循环,到外层循环再次去遍历他的左子树,如此重复知道栈空。。。。
运行结果图:
2.7、层次遍历的非递归算法:
void LevelOrder(TREE *b)
{
TREE *p;
TREE *qu[100];
int front,rear;
front=rear=0;
rear++;
qu[rear]=b;
while(front!=rear)
{
front=(front+1)%100;
p=qu[front];
printf("%c ",p->data);
if(p->lchild!=NULL)
{
rear=(rear+1)%100;
qu[rear]=p->lchild;
}
if(p->rchild!=NULL)
{
rear=(rear+1)%100;
qu[rear]=p->rchild;
}
}
}
首先是定义一个环形队列,用来存放节点指针(NOTE:是节点的指针)第一步将根节点指针入队,队列不空的时候开始循环(就是front!=rear)首先取出队头元素,先访问它,然后判断是否有左孩子,若有就入队,再判断是否有右孩子,有再入队,这个和先序遍历的思想有点类似,但值得注意的是,这里是先将左孩子入队,再右孩子,而先序遍历是先将右孩子入栈再左孩子,这是因为这里用队列存储,先进先出,每次访问对队头元素,这样顺序就是层次遍历的顺序了。
3、括号表示法输出二叉树:
//采用括号表示法输出该二叉树
/*
f(b)==不输出任何内容 当b=NULL
f(b)==输出:b->data(f(b->rchild)) 当左子树不空、右子树为空
f(b)==输出:b->data(,f(b->rchild)) 当左子树空、右子树不空
f(b)==输出:b->data(f(b->lchild),f(b->rchild)) 当左子树不空、右子树不空
f(b)==输出:b->data 当左右子树都为空
*/
void DisBTNode1(TREE *b)
{
if(b!=NULL)
{
if(b->lchild!=NULL && b->rchild==NULL)//左不空右空
{
printf("%c(",b->data);
DisBTNode1(b->lchild);
printf(")");
}
else if(b->lchild==NULL && b->rchild!=NULL)//左空右不空
{
printf("%c(",b->data);
printf(",");
DisBTNode1(b->rchild);
printf(")");
}
else if(b->lchild!=NULL && b->rchild!=NULL)//左右不空
{
printf("%c(",b->data);
DisBTNode1(b->lchild);
printf(",");
DisBTNode1(b->rchild);
printf(")");
}
else
printf("%c",b->data);
}
printf("\n");
}
//进一步优化后的算法
void DisBTNode2(TREE *b)
{
if(b!=NULL)
{
printf("%c",b->data);
if(b->lchild!=NULL || b->rchild!=NULL)
{
printf("(");
DisBTNode2(b->lchild);
if(b->rchild!=NULL)
printf(",");
DisBTNode2(b->rchild);
printf(")");
}
}
}
4、求第k个节点的值:
//假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第k个节点的值
/*
递归模型如下:
f(b,k)='' 当b=NULL时返回特殊字符''
f(b,k)=b->data 当k=n
f(b,k)=((ch=f(b->lchild,k))==''?f(b->rchild,k):ch) 其他情况
*/
int n=1;
char PreNode(TREE *b,int k)
{
char ch;
printf("n=%d ",n);
if(b==NULL)
return ' ';
if(k==n)
return b->data;
n++;
ch=PreNode(b->lchild,k); //递归寻找左子树
if(ch!=' ')//当返回的是一个值而不是' '的时候说明找到了,就返回这个值
return ch;
ch=PreNode(b->rchild,k);//当找遍了左子树后返回的最终还是' '说明没找到,就要往右子树开始找
return ch;
}
该算法是先序遍历的一个应用,首先从根节点开始,判断该节点是否就是要找的节点,若是,直接返回该节点值,若为空,就用特殊符号' '代替(这是为了下次递归遍历的时候用来判断是否找到该节点,因为返回来的值要么就是' ',要么是找到的节点值,就两种情况),若根节点不是要找的节点,就使n递增1,说明要往下一层寻找,先递归他的左子树,在左子树中就找到了该节点,就返回节点值,不再执行下面的操作,若所有的左子树都不是要找的节点,就从最后一层的左节点开始递归他的右子树,直到找到返回为止.
5、求各种节点的个数:
5.1、计算一颗给定二叉树的所有叶子节点的个数:
/*
f(b)=0 若b=NULL
f(b)=1 若*b为叶子节点
f(b)=f(b->lchild)+f(b->rchild) 其他情况
*/
int LeafNodes(TREE *b)
{
if(b==NULL)
return 0;
if(b->lchild==NULL &&b->rchild==NULL)
return 1;
return LeafNodes(b->lchild)+LeafNodes(b->rchild);
}
5.2、计算一颗给定二叉树的所有单分支节点个数:
/*
f(b)=0 若b=NULL
f(b)=f(b->lchild)+f(b->rchild)+1 若*b为单分支
f(b)=f(b->lchild)+f(b->rchild) 其他情况
*/
int SSonNode(TREE *b)
{
if(b==NULL)
return 0;
if((b->lchild==NULL && b->rchild!=NULL) || (b->lchild!=NULL && b->rchild==NULL))
return 1+SSonNode(b->lchild)+SSonNode(b->rchild);
return SSonNode(b->lchild)+SSonNode(b->rchild);
}5.3、计算一颗二叉树的所有双分支节点个数:
/*
f(b)=0 若b=NULL
f(b)=f(b->lchild)+f(b->rchild)+1 若*b为双分支
f(b)=f(b->lchild)+f(b->rchild) 其他情况
*/
int DSonNode(TREE *b)
{
if(b==NULL)
return 0;
if(b->lchild!=NULL && b->rchild!=NULL)
return 1+DSonNode(b->lchild)+DSonNode(b->rchild);
return DSonNode(b->lchild)+DSonNode(b->rchild);
}5.4、计算一颗二叉树的所有分支节点个数:
/*
f(b)=0 若b=NULL
f(b)=f(b->lchild)+f(b->rchild)+1 若*b为分支
f(b)=f(b->lchild)+f(b->rchild) 其他情况
*/
int FSonNode(TREE *b)
{
if(b==NULL)
return 0;
if(b->lchild!=NULL || b->rchild!=NULL)
return 1+FSonNode(b->lchild)+FSonNode(b->rchild);
return FSonNode(b->lchild)+FSonNode(b->rchild);
}
5.5、计算一颗二叉树的所有节点个数:
/*
f(b)=0 若b=NULL
f(b)=f(b->lchild)+f(b->rchild) 其他情况
*/
int TSonNode(TREE *b)
{
if(b==NULL)
return 0;
return TSonNode(b->lchild)+TSonNode(b->rchild)+1;
}
5.6、计算一颗对给定二叉树中值为k的节点个数:
/*
计算一颗二叉树b中值为k的节点个数的递归模型f(b,k)如下;
f(b,k)=0 当b=NULL
f(b,k)=1+f(b->lchild,k)+f(b->rchild,k) 当b->data=k
f(b,k)=f(b->lchild,k)+f(b->rchild,k) 其他情况
*/
int Countk(TREE *b,char k)
{
if(b==NULL)
return 0;
if(b->data==k)
return 1+Countk(b->lchild,k)+Countk(b->rchild,k);
return Countk(b->lchild,k)+Countk(b->rchild,k);
}
6、判断一颗二叉树是否为满二叉树:
/*
由满二叉树的定义可知,若一颗二叉树满足n=2^h-1,则为满二叉树
*/
int height(TREE *b)
{
int lchilddep=0,rchilddep=0;
if(b==NULL)
return 0;
lchilddep=1+height(b->lchild);//递归左子树
rchilddep=1+height(b->rchild);//递归右子树
return lchilddep>rchilddep?lchilddep:rchilddep;//取左右子树深度大的就是树的高度
}
7、复制二叉树:
//假设二叉树采用二叉链表存储结构,设计一个算法把二叉树b复制到二叉树t中。
/*
递归模型如下:
f(b,t)==t==NULL 若b为NULL
f(b,t)==复制根节点*b产生*t节点 其他情况
f(b->lchild,t->lchild);
f(b->rchild,t->rchild);
*/
int Copy(TREE *b,TREE **t)
{
if(b==NULL)
{
t=NULL;
return 0;
}
else
{
*t=(TREE *)malloc(sizeof(TREE));
*t=b;
Copy(b->lchild,&(*t)->lchild);
Copy(b->rchild,&(*t)->rchild);
}
return 1;
}这里TREE **t就是上面介绍过的指针的指针,要改变指针的指向而用,这算法个是基于先序遍历的,首先复制根节点,再左子树、右子树。
8、交换二叉树的左右子树:
//假设二叉树采用二叉链存储结构,设计一个算法把二叉树b的左右子树进行交换,要求不破坏原二叉树
/*
本题要求不破坏原有二叉树,实际上是建立一颗新的二叉树t,它交换了二叉树b的左右子树。递归模型:
f(b,t)==t==NULL 若b为NULL
f(b,t)==复制根节点*b产生*t节点 其他情况
f(b->lchild,t->rchild);
f(b->rchild,t->lchild);
*/
//基于先序遍历
int swap(TREE *b,TREE **q)
{
if(b==NULL)
{
*q=NULL;
return 0;
}
else
{
(*q)=(TREE *)malloc(sizeof(TREE));
(*q)->data=b->data;
swap(b->lchild,&((*q)->rchild));
swap(b->rchild,&((*q)->lchild));
}
return 1;
}
/*如果本题没有要求不破坏原二叉树的条件,或者要求空间复杂数O(1)。这样就不需要另外建立二叉树t,
只需将原叉数的左右子树交换即可,递归模型:
f(b,t) 不做任何事 若b为NULL
f(b,t)== f(b->lchild) 其他情况
f(b->rchild)
将*b节点的左右子树交换
*/
//基于后序遍历
void swap1(TREE **b)
{
TREE *q;
if(*b!=NULL)
{
swap1(&((*b)->lchild));//递归交换b的左子树
swap1(&((*b)->rchild));//递归交换b的右子树
q=(*b)->lchild; //交换b所指的节点的左右指针
(*b)->lchild=(*b)->rchild;
(*b)->rchild=q;
}
}
9、判断二叉树是否相似:
/*假设二叉链采用二叉链存储结构,设计判断两颗二叉树是否相似的算法,所谓二叉树t1和t2相似是指t1和t2都是空二叉树或者t1和t2的根节点是相似的,且t1的左子树和t2的左子树相似,t1的右子树和t2的右子树也相似*/
/*
f(t1,t2)==true 若t1,t2均为NULL
f(t1,t2)==false 若t1t2之一为NULL
f(t1,t2)==f(t1->lchild,t2->lchild)
&f(t1->rchild,t2->rchild) 其他情况
*/
int Like(TREE *t1,TREE *t2)
{
int like1,like2;
if(t1==NULL && t2==NULL)
return 1;
if((t1==NULL && t2!=NULL) ||(t1!=NULL && t2==NULL))
return 0;
like1=Like(t1->lchild,t2->lchild);
like2=Like(t1->rchild,t2->rchild);
return like1&like2;
}
10、判断二叉树是否相等:
//假设二叉树采用二叉链存储结构,设计判断两颗二叉树是否相等
/*
f(t1,t2)==true 若t1,t2均为NULL、
f(t1,t2)==false 若t1,t2之一为NULL
f(t1,t2)==t1->data==t2->data
&f(t1->lchild,t2->lchild)
&f(t1->rchild,t2->rchild) 其他情况
*/
int same(TREE *t1,TREE *t2)
{
int same1,same2;
if(t1==NULL && t2==NULL)
return 1;
if((t1==NULL && t2!=NULL) ||(t1!=NULL && t2==NULL))
return 0;
same1=same(t1->lchild,t2->lchild);
same2=same(t1->rchild,t2->rchild);
if(t1->data==t2->data && same1==1 && same2==1)
return 1;
else
return 0;
}

浙公网安备 33010602011771号