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,因为此时栈顶元素不处理,将要处理他的右孩子了,要退出这个循环,到外层循环再次去遍历他的左子树,如此重复知道栈空。。。。

运行结果图:

image

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");
}
image

 

//进一步优化后的算法
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;
}