西南科技大学 SWUSTOJ #971-#979

这些题都是对二叉树的一些操作,现在我根据我对二叉树的理解来给大家讲讲吧。

我这里主要讲二叉树的各种操作,至于二叉树的定义请看这篇文章。

二叉树的定义及其性质

好,现在咱们来讲二叉树的创建。

我们要知道二叉树有父结点,左孩子结点,右孩子结点,像这样

052110881e5b4ff8b8d20a18aaa8c7d7.png

A是B,C的父节点,B是A的左孩子结点,C是A的右孩子结点。

我们要创建二叉树就采用递归的方式创建。

先创建一个结构体

typedef struct tree{

        int data;//data是数据域,数据类型依题而论。

        tree *Lchild,*rchild;//创建左右孩子结构体指针。

} tree,*butree;

 创建好结构体后,开始递归创建二叉树,这里我们根据西科大OJ的题目,采用先序创建二叉树

tree creat(){
	char a;
	cin >> a;
	node *p;
	p=NULL;
	if(a=='#'){
		return NULL;
	}
	else {
		p=(node*)malloc(sizeof(node));
		p->data=a;
		p->Lchild=creat();
		p->rchild=creat();
		return p;
	}
}

直接看代码我相信你们肯定会有不懂,我画图来说明。

我们先将先序表达式依次输入,比如现在我们要输入ABC####

我们将要构建出这样的二叉树:

53f65f54b3514944a9964e53936f26da.png

 我们要实现这个目标就采用递归的的方式,依次读取输入的符号。

先读入一个符号A,这时创建结点A,40cee6d160ec477da15db161895e228d.png

 随后进入递归,让A结点的左孩子结点为递归返回的结果。

输入符号B,创建结点,再次进入递归,找B的左孩子结点。

输入符号C,创建结点,再次进入递归,找C的左孩子结点。

这时我们读入‘#’,所以返回一个NULL值,回到上一层递归,得到C的左结点为‘#’即为NULL。

这时进入C的右孩子结点递归,我们读入‘#’,告诉我们该结点为NULL,即C的右结点为NULL,

随后返回C,这里相当于是让B的左节点成为C。

然后进入B的右孩子结点递归,我们再次读入‘#’,告诉我们B的右孩子结点为空。

随后返回上一层递归,即A的左结点为B,这时进入A的右孩子结点递归,

我们再次读入‘#’,即A的右孩子结点为NULL,最后将A结点返回,递归结束,成功创建二叉树。

这里我们没有用void定义这个creat函数,是因为我们返回的值为结构体指针,所以用tree来定义这个函数。

至此,我们完成了创建二叉树,接下来就是对二叉树进行遍历。

遍历的方式有三种

  1. 先序遍历。
  2. 中序遍历。
  3. 后续遍历。

现在我们来具体看看这三个遍历分别是指什么。

比如咱们对于这样一个二叉树

fcbdb5fb600649bfb7e04df04453063a.png

 如果我们采用先序遍历,输出结果为A,B,C。

即输出顺序为根结点,左孩子结点,右孩子结点。

0a75506204e740c4817acef6cd534365.png

 

如果我们采用中序遍历,即为B,A,C;

输出顺序为左孩子结点,跟结点,右孩子结点

74de6d4895254eb4be98c78dc057eb3a.png

 如果我们采用后序遍历,那么序列为B,C,A

即先左孩子结点,再右孩子结点,最后根结点

01192c4aff0a414a943ce4c6b391ab42.png

 我们要做到这种的话我们可以采用递归方式和非递归方式。这里我只讲递归方式。

void xian(node *p){//先序遍历
	if(p!=NULL){
		cout << p->data << " ";
		xian(p->Lchild);
		xian(p->rchild);
	}
	
}
void hou(node *p){//后序遍历
	if(p!=NULL){
		hou(p->rchild);
		hou(p->Lchild);
		cout << p->data << " ";
	}
}
void zhong(node *p){//中序遍历
	if(p!=NULL){
		zhong(p->Lchild);
		cout << p->data << " ";
		zhong(p->rchild);
	}
}

a0df8ee602254f6e9b804b0e4fb56f6e.png

 求先序时,我们从根结点出发,先输出该结点的数据,然后进入左孩子结点递归,进入左孩子结点后,我们将左孩子结点看作是根结点,然后输出该结点数据,再递归进入该节点的左孩子结点,以此类推,直到找到一个结点为NULL,退回上一层递归,然后找右孩子结点,一层一层回溯,完成先序输出。

求中序也是类似,不过就是将输出的位置换一下,即不停的找左结点,直到不存在左孩子结点后,开始回溯,每回溯以此输出以此该结点的值,并且查看是否在该结点存在右孩子结点,如果有就再次进入递归,以此类推,即完成中序输出。

求后序也是,先左结点递归,完了后再进行右结点递归,随后依次回溯进行输出,完成后序输出。

咱们完成遍历后还有几个操作,分别是

  1. 求二叉树深度。
  2. 求二叉树宽度。
  3. 求二叉树叶子结点。
  4. 求二叉树度为x的结点。

我们依次来看吧,首先,我们看求二叉树深度。

在求二叉树深度时,我们先说一下二叉树深度是什么。

二叉树深度就是二叉树元素最多的那一支的长度。

比如:

335603c70ac941159b1e7af3f9f14a88.png

 元素最多的那一只为A,B,D,F,长度为4,即二叉树深度为4.

了解了这个概念后,我们就可以开始实现了,对于求深度的方法任仍然有两种方式,一种为递归,第二种为非递归,采用队列来求,我这里就只讲递归的方式了,这样方便大家理解。

int deep(node *p){//求二叉树深度
	if(p==NULL){
		return 0;
	}
	else{
		int L=deep(p->Lchild);
		int R=deep(p->rchild);
		int maxx=max(L,R);
		return maxx+1;
	}
}

这里我们要定义全局变量maxx来存储深度值。

开始,我们判断该结点是否为NULL,如果不是,那么就进入递归,我们定义一个L表示左孩子结点长度,随后进入左孩子结点,继续操作。

如果遇到结点为NULL,我们返回0,回到上一层函数,上一层L就为0,随后我们看R,也是进行递归,直到结点NULL,然后开始回溯,每次都对L和R进行比较,取最大值,每层都将maxx+1.

我们这相当于是从下往上数,而不是从上往下数,最终得到一个最大的maxx,即为二叉树深度。

各位如果还是不理解,我这里建议大家画图来理解。

 

好,我们完成了求二叉树深度后,开始求二叉树宽度。

老样子,我们得知道二叉树宽度是什么。

通俗点说,二叉树宽度就是二叉树有多宽。即二叉树每层存在结点的数量最大值。

79ecf4b8f252478ea9e30b1d5e8df9fc.png

 如图所示,这样想必大家一定就比较清楚了。

好,概念知道了,咱们就来把他实现。

int maxx=0;
int width_[1000000];
int width(node *p,int k){//求二叉树宽度
	if(p==NULL){
		return 0;
	}
	else{
		width_[k]++;
		maxx=max(maxx,width_[k]);
		width(p->Lchild,k+1);
		width(p->rchild,k+1);
		return maxx;
	}
}

我们利用数组下标来代表二叉树层数,数组里的数据的多少代表该层有多少个结点。

老样子,我们仍然是递归。

如果该结点不为NULL,那么我们先给该层结点数量加一,用maxx实时获取最大宽度。

然后进入左节点递归,并且将层数加一,即k+1;

直到出现P为NULL,然后开始回溯,找右孩子结点,如此层次递归回溯,即可得到最大值,即二叉树宽度。

画图看比较清楚。

 

了解完求二叉树宽度后,那我们来做最简单的找叶子结点数量和度为x的结点数量。

那这叶子结点和度是什么呢?

我们要懂得联想,既然它叫叶子结点,那么我们能想到什么?

没错,叶子只与树枝相连,然后就没有其他东西与它相连了。

放到二叉树里来说,就是只有根结点,而没有左孩子结点和右孩子结点的结点。

好,我们在说度,通俗点讲,这度就相当于是连接的结点数量,如果为1,则代表该结点只连了一个子节点,也就是说要么存在左孩子结点,要么存在右孩子结点。

度为2就不说了,两个都有嘛。

好,知道这些,我觉得大家应该脑海中就有思路了吧。

int yezi(node *p){//求二叉树叶子结点个数
	if(p==NULL){
		return 0;
	}
	else if(p->Lchild==NULL&&p->rchild==NULL){
		return yezi(p->Lchild)+yezi(p->rchild)+1;
	}
	else{
		return yezi(p->Lchild)+yezi(p->rchild);
	}
}
int yezi2(node *p){//求度为2的结点个数
	if(p==NULL){
		return 0;
	}
	else if(p->Lchild!=NULL&&p->rchild!=NULL){
		return yezi2(p->Lchild)+yezi2(p->rchild)+1;
	}
	else{
		return yezi2(p->Lchild)+yezi2(p->rchild);
	}
}

简简单单采用递归的方式解决,这么简单的递归我相信大家都理解,我就不再多做解释了。

好啦,如果你把这篇文章读完,相信你能够轻松解决OJ上的题了,如果这样你都解决不了,那你可以私聊我,感谢各位阅读

 

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值