二叉树经典问题——已知中序和前序重建二叉树

二叉树经典问题——已知中序和前序重建二叉树运用前序和中序序列重建二叉树及其相关应用重建过程1,在二叉树的学习中经常会遇到一类问题,就是给出一棵二叉树的前序和中序序列(后序和中序类似)然后求树的深度、树的后序序列、树的各种遍历等等问题,这个时候如果能根据相关的序列把其代表的二叉树重建出来,那么所有的问题便会迎刃而解。博文的第一部分就给出相关的重建步骤。2,重建中最关键的一点是从前序中找根然后在后序中用相应的根把树‘分解’。举个例子:

大家好,又见面了,我是你们的朋友全栈君。

运用前序和中序序列重建二叉树及其相关应用##

重建过程
1,在二叉树的学习中经常会遇到一类问题,就是给出一棵二叉树的前序和中序序列(后序和中序类似)然后求树的深度、树的后序序列、树的各种遍历等等问题,这个时候如果能根据相关的序列把其代表的二叉树重建出来,那么所有的问题便会迎刃而解。博文的第一部分就给出相关的重建步骤。
2,重建中最关键的一点是从前序中找根然后在后序中用相应的根把树‘分解’。举个例子:
这里写图片描述
上面这个二叉树对应的3种遍历序列如下:

PreOrder: GDAFEMHZ

InOrder: ADEFGHMZ

PostOrder: AEFDHZMG
1,因为前序遍历的第一个节点一定是一个二叉树的根,所以从前序的第一个数据开始也就是G,把G映射到中序序列中,并记下在中序序列中的位置(这个位置十分重要!这个位置如果不在中序序列的最左端说明:前序序列中G的下一个数据必定是G的左子树),又因为在中序序列中是按照leftChild—root—rightChild的方式遍历的所以在中序中以上面记下的位置为分界,得到以G为根的左右子树(分别是ADEF和HMZ)。(结合上面的图示容易理解)
2,上面第一步只是把整个二叉树分出左右子树,然后再在前序中找到下一个数据也就是D,再把D在中序中对应的位置记录下来,此时,D的位置并不在中序序列的最左端(最左端是A),也就说明D还可以继续向下‘派生’左子树,那么继续访问前序序列中的下一个元素也就是A。
3,同样的步骤,A在中序序列中的位置处于最左端(即A的左子树长度为0),这说明A不能够再有左子树,此时便可以把A的左子树置为nullptr,这时候再考虑A的右子树是否存在,因为在中序序列中A的右面是D,但是D是A的父节点(这个地方看代码会更易于理解,因为代码中明确的显示出A的右子树长度也为0),所以A的右子树置为nullptr。
4,类似的方式再考虑D的右子树存在问题,如果右子树长度不是0,那么就在前序中选出相应的数据……
5,……
整体看上述步骤,其实是一个递归的过程(结合下面的代码看上面的解释更能体会出递归的思想),下面结合代码做简要的解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLQughVW-1571447476348)(https://img-blog.csdn.net/20171110120448475?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2VpeGluXzM3ODE4MDgx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]
理解上面这个递归函数有两种方式,一种是通过编译器的调试功能,辅助你进入整个递归过程(毕竟测试数据长度有限,浪费不了你多长时间),我这里用的是Visual Studio 2013的调试,例如像下面这样:
这里写图片描述
通过调用堆栈的窗口你可以看到递归具体的过程和细节,递归深度及各个函数的执行顺序,图中的递归深度已经到达3.
这里写图片描述
通过变量的监视窗口,你可以实时的锁定某个变量当前的状态从而可以验证你的一些猜测与想法。甚至你可以打开某个变量的层级目录,通过层级目录的方式来理解二叉树的指针指向关系。
上面这几个工具窗口非常实用,尽管我下面要用另一种方式来理解这个递归函数,但是我依旧鼓励你用这种方式来自己总结出递归的真正细节,这是一个极棒的体验!这样你会在之后的递归程序中迅速掌握各种递归过程,而不需要来我这里浪费时间(_)。
另一种方式是不深入递归的具体函数、变量调用的过程,就是只看最外层的函数使用。这样会有一个缺点,就是感觉自己不能真正掌控整个程序,有一种模糊的感觉。当然这摆脱了让大脑遭受超强的负荷,更易于理解递归的实现和功能。
像上面的那个函数,我们只来想整个二叉树
1)如果二叉树的长度是0,毫无疑问直接返回nullptr。
2)不是0,我们应该开辟一块节点的内存,并且把前序序列中的第一个数据(必定是根)装进去。
3)然后我们要通过循环找出这个根数据在中序序列中的位置,并记录在rootIndex里面。
4)我们终于来到了递归的门口,函数要开始调用他自己了!rootIndex不在中序的最左端,也就是根存在左子树了,我们让node–>leftChild开辟并装入前序序列的根的下一个数据。我才不会继续想他的下一层递归呢!
5)挺快,整个二叉树的左子树我们完成了(你也许感觉有些唐突,那不怪我,我已经在第一种方式中说过),我们接下来要看G的右子树是否存在。注意看
node->rightChild = CreateBinTreeByPreInOrder(preSeq + rootIndex + 1, InSeq + rootIndex + 1, subStrLen – (rootIndex + 1));
这个函数的参数,对于整个二叉树来说,rootIndex此时是4(即G在中序序列中的下标),preSeq作为一个头指针加上左子树的长度(即rootIndex + 1),当然是右子树的第一个节点的数据,自然也应该把搜索的区间缩减到只在右子树的部分(即InSeq + rootIndex + 1),最后确定长度,整个序列的长度减去左子树的长度自然是右子树的总长度(即subStrLen – (rootIndex + 1));这样只要想象每次进入一棵子树他都会像对待整棵树那样对待每一棵子树,那么我们的目的就达到了!
下面是完整的代码

/*
*已知前序遍历序列和中序遍历序列建立二叉树
*并求其后序遍历序列和层序遍历序列
*/
#include <iostream>
#include <string>
#include <queue>
#include <string.h>
//二叉树节点结构定义
struct BIN_NODE {
	char data;
	BIN_NODE *leftChild, *rightChild;
};

//序列存储变量
//用户输入字符串测试
//char preSequence[100];
//char inSequence[100];
//指定字符串测试
char *preSequence = "GDAFEMHZ";
char* inSequence = "ADEFGHMZ";
//树根定义
BIN_NODE *tree;
//存储下一层节点的队列
std::queue<BIN_NODE *> memoryNextLevel;

//函数声明
void PrintBinTreeByPostOrder(BIN_NODE *subTree);		//后序打印二叉树数据域
void PrintBinTreeByLevelOrder(BIN_NODE *subTree);		//层序打印二叉树数据域
//通过前序、中序序列建立二叉树
BIN_NODE * CreateBinTreeByPreInOrder(char* preSeq, char* InSeq, int subStrLen);

int main()
{
	int groupsAmount = 0;
	std::cin >> groupsAmount;
	while (groupsAmount--) {
		//std::cin >> preSequence >> inSequence;
		tree = CreateBinTreeByPreInOrder(preSequence, inSequence, strlen(inSequence));
		PrintBinTreeByPostOrder(tree);
		std::cout << std::endl;
		PrintBinTreeByLevelOrder(tree);
		std::cout << std::endl;
	}

	return 0;
}

//函数定义

BIN_NODE * CreateBinTreeByPreInOrder(char* preSeq, char* InSeq, int subStrLen) {
	if (0 == subStrLen) {
		return nullptr;
	}
	BIN_NODE *node = new BIN_NODE;
	if (node == nullptr) {
		std::cerr << "error" << std::endl;
		exit(1);
	}
	node->data = *preSeq;
	//前序相应元素在中序中的下标索引值
	int rootIndex = 0;					
	//求解这个索引值
	for (; rootIndex < subStrLen; rootIndex ++) {
		if (InSeq[rootIndex] == *preSeq) {
			break;
		}
	}
	node->leftChild = CreateBinTreeByPreInOrder(preSeq + 1, InSeq, rootIndex);
	node->rightChild = CreateBinTreeByPreInOrder(preSeq + rootIndex + 1, 
		InSeq + rootIndex + 1, subStrLen - (rootIndex + 1));
	return node;
}

相信你也觉得我说的相当玄乎(哈哈),但是我的水平也只能讲到这个程度,我没跟你说过你可以用方式一吗?(自力更生的方法)。到此我们便可以重建出这两个序列所代表的二叉树。下面我们来看看有哪些简单的二叉树操作问题在等着我们。
请看下一遍博文<<<<
参考及引用出自:http://blog.csdn.net/feliciafay/article/details/6816871

公众号:Dawo

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/131994.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 传统波束形成的算法实现「建议收藏」

    传统波束形成的算法实现「建议收藏」最近学习了传统波束形成(CBF)的原理,尝试着写出识别一个单声源的波束形成程序。下面按照程序说明一下。1、初始化设置一些常数,例如抽样频率,所要计算的频率,时间步等。clearall;closeall;clc;%—————-初始化—————-%c=1500;%声速cfs=10000;%抽样频率fsT=0.1…

    2022年6月29日
    22
  • pycharm中导入pandas_新电脑安装软件特别慢

    pycharm中导入pandas_新电脑安装软件特别慢pandas标红,导入库发现pandas库迟迟不能安装后网络寻找方法:进行换源找到ManageRepositories(如果找不到这个,可以查看我的《Pycharm2019安装第三方库》)点击“+”添加第二行点击OK若成功便会如下图会有两个url(注意pilapse只是个例子,你若没有也正常,重点是有pythontuna…)…

    2022年8月27日
    7
  • LLDP简介

    LLDP简介1.1.1LLDP产生背景目前,网络设备的种类日益繁多且各自的配置错综复杂,为了使不同厂商的设备能够在网络中相互发现并交互各自的系统及配置信息,需要有一个标准的信息交流平台。LLDP(LinkLayerDiscoveryProtocol,链路层发现协议)就是在这样的背景下产生的,它提供了一种标准的链路层发现方式,可以将本端设备的信息(包括主要能力、管理地址、设备标识、接口标识等)组织成不同的TLV(Type/Length/Value,类型/长度/值),并封装在LLDPDU(Lin…

    2022年5月28日
    91
  • 谈谈API接口安全性设计思路

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 作者:道长 www.jianshu.com/p/c6518a8f4040 接口的安全性主要围绕Token、Times…

    2021年6月24日
    98
  • Pytest(1)安装与入门「建议收藏」

    Pytest(1)安装与入门「建议收藏」pytest介绍pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍,它

    2022年7月29日
    8
  • 项目中更新Stimulsoft组件的方法

    项目中更新Stimulsoft组件的方法我们正在不断开发我们的软件。我们的主要目标是成为软件工程的前沿。每个版本均包含新功能,组件优化和错误修复。这就是为什么新发行版始终是先前版本的产品改进的原因。但是,并非所有用户都知道在他们的项目中更新

    2022年7月4日
    26

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号