二叉树:已知前序序列与后序序列建树

article/2025/6/24 3:22:50

二叉树:已知前序与后序建树

已知前序与中序、后序与中序建树是常遇到的算法问题。若已知前序序列与后序序列,要求输出满足条件的树的个数或者输出所有可能的树的中序序列,该怎么解决?下面我们一步步讨论这个问题。

首先,已知一个树的结点数n,共有多少种树形?(或者,已知一颗树的先序/中序/后序序列,共有多少种树形?)
答案是 C m n / ( n + 1 ) C_{m}^{n}/(n+1) Cmn/(n+1)种。点这里了解一下卡特兰数
那么,这里就有一种暴力解法,就是建立所有可能的树形,将先序序列填入某一树形,比较此树的后序序列是否与给定后序序列相同,若相同则找到一个解,若不相同,则填入下一个树形,直到填完所有可能树形。当树的结点为10时,那么总共有16796种树形,因此这种暴力解法时间复杂度相当高
那么我们换一种思考方式,我们先来看看先序与后序序列的排布规律。以下面这棵树来举例:

在这里插入图片描述

其先序序列为: 1 2 3 4 6 7 5
后序序列为:2 6 7 4 5 3 1

首先我们要知道:

先序序列遍历顺序是:根结点-左子树-右子树

后序序列遍历顺序是:左子树-右子树-根结点

很明显,我们可以看出结点在先、后序列中的排布有以下这些特征
【1】、在先序序列中,根结点在子树中的结点前面,在后序序列中,根结点在子树中的结点后面
【2】、以任一节点为根结点时,其子树在后序序列中排布都是先左子树后右子树而根结点排在最后
那么,反过来思考,已知这个先序与后序序列所确定的树是唯一的吗?进一步推广:怎么通过先序与后序序列判断是否存在唯一的树呢?

现在,我们来一步步分析已知先序与后序的建树过程:

①、根据特征【1】可知:根结点为先序序列第一个节点以及后序序列最后一个结点,因此根结点为1
在这里插入图片描述
②、先序序列中第二个结点为2,其在后序序列中的位置是第一个,那么根据特征【2】我们可以知道结点2是没有子树的,而且结点2要么在根结点的左子树,要么在右子树。假设结点2在右子树,那么由特征【2】可知根结点1没有左子树,而且先序序列中结点2后面的结点全部为结点2的子树上的结点。再看后序序列,由特征【2】可知,结点2后面的结点不可能是其子树上的结点。因此,假设显然与已知矛盾。这样,我们又知道结点2是结点1的左孩子,且结点2没有子结点
在这里插入图片描述
③、先序序列第三个位置上的结点为3,该结点在后序序列中排倒数第二个。由②可知,结点3必然是根结点1的右孩子。
在这里插入图片描述
④、先序序列第四个位置上的结点为4,该结点在后序序列中排第四个。因为结点4在先序序列中排在结点3后面,又因为结点3是根结点1的右孩子,所以结点4只可能在结点3的子树上。结点3的子树可能出现的情况是:只有左子树,只有右子树,左右子树都有。因为在后序序列中,结点4左边是结点6、7,右边是结点5。所以结点5并不在结点4的子树上,所以结点3既存在左子树又存在右子树。这样的话,出现在结点3后面的第一个结点必然为结点3的左子树,所以结点4是结点3的左孩子,且由特征【2】可知,结点6、7在以结点4为根结点的子树,上,结点5是结点3的右孩子
在这里插入图片描述
⑤再看结点6,其在先序序列中排在第五位,在后序序列中排在第二位。同理,结点4的子树可能出现的情况是:只有左子树,只有右子树,左右子树都有。假设,接4只有左子树,那么结点6必然为结点4的左孩子,结点7必然为结点6的孩子,而根据特征【2】,因为结点7在后序序列中排在结点6后面,因此结论与假设矛盾。同样可以排除只有右子树的情况。那么我们可以得到:结点4既有左子树又有右子树,由特征【2】可知结点6为结点4的左孩子,结点7为结点4的右孩子

至此,我们已经分析了一遍如何通过二叉树的先、后序序列建立一个二叉树,但是由这个例子中的先、后序序列确定的树是唯一的。现在我们再从分析过程中提炼一些排列特征出来:
【3】在后序序列中,若一个结点的左侧没有在先序序列中排在该结点后面的结点(如结点2,在先序序列中排在其后面的结点有3 4 6 7 5 但在后序序列中结点2前面没有这些结点),那么这个结点必然没有孩子。
【4】

再来看看这样一个例子:

先序序列:1 2 3 4
后序序列:2 4 3 1

我们再来建立一棵满足上述条件的树:
通过上一个例子,我们可以很快得到结点1为根结点,结点2为结点1的左孩子,结点3为结点1的右孩子,但是我们发现:结点4既可以是结点3的左孩子,又可以是右孩子。我们可以看到,之所以结点4的位置无法确定,是因为结点3只有一个子节点,而这个子节点既可以是左孩子,又可以是右孩子。
至此,我们可以得到确定一个结点位置是否唯一的方法:检测其父节点是否只有一个子节点
因此可以得到特征:
【4】在先序序列中,若根结点的下一个位置上的结点在后序序列中的位置在后序序列根结点的左边一个位置,那么说明树不唯一,树的个数与出现这种情况相关(2^n)。

为了更好的解释特征【4】,我们可以看这样一个例子:

先序序列:1 2 4 5 6 3
后序序列:4 2 3 6 5 1

当我们对这棵树进行划分时,我们可以得到以结点1为根结点时的左右子树:
左子树(先序/后序):2 4 / 4 2
右子树(先序/后序):5 6 3 / 3 6 5

我们看这里的右子树的先序序列与后序序列:当结点5作为结点1的右子树的根结点时,在先序序列中结点5下一个位置的结点6在后序序列中位于后序序列中结点5的左边一个位置,因此说明对于结点5而言,树不唯一。

Some practices for you

利用上面所阐述的方法,我们来计算一下下面这些例子可以确定多少种树:

先序序列:1 2 3 4 5 6
后序序列:3 2 5 6 4 1

先序序列:1 2 4 5 6 3
后序序列:4 2 3 6 5 1

先序序列:1 3 4 5 6 2 7
后序序列:5 4 3 2 7 6 1

答案分别为:2,8,1
树形可以为:
在这里插入图片描述

Code

c/c++代码实现:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 50;int n;
int pre[maxn],post[maxn],pos[maxn];
bool isunique = true;// 判断是否唯一struct node{int data;node *lchild,*rchild;
};void inorder(node *&root,int prel,int prer,int postl,int postr){if(prel == prer){root = new node;root->data = pre[prel];root->lchild = root->rchild = NULL;return;}else if(prel>prer || postl>postr){return;}if(pre[prel] == post[postr]){root = new node;root->data = pre[prel];root->lchild = root->rchild = NULL;int numchild = postr-pos[pre[prel+1]];if(numchild == 1){isunique = false;}int postloc = pos[pre[prel+1]];int numleft = postloc-postl; inorder(root->lchild,prel+1,prel+numleft+1,postl,postl+numleft);inorder(root->rchild,prel+numleft+2,prer,postl+numleft+1,postr-1);}
}int main(){scanf("%d",&n);for(int i=0;i<n;i++){scanf("%d",&pre[i]);}for(int i=0;i<n;i++){scanf("%d",&post[i]);pos[post[i]] = i;}node *root = NULL;inorder(root,0,n-1,0,n-1);return 0;
}

说明一下:
1、调用inorder()函数得到root便获取到了建成的树,其中位置不确定的结点都被认为是左结点。
2、若想统计不同的树形个数,可以对以下语句进行修改:

:
bool isunique = true;
...
if(numchild == 1){isunique = false;
}
改为:
int isunique = 0;
...
if(numchild == 1){isunique++;
}
此时树形个数为2^isunique个,想一想为什么是2为底数?

PAT上有类似的题目:
PAT Advancedlevel 1119 Pre- and Post-order Traversals
本题也可以不建树直接得到中序遍历,这里就不再讨论,想继续了解的朋友请狠狠的点击这里,查看柳神的方法

感觉推导过程写的不清楚的请留言,我会不定期来修改。


http://chatgpt.dhexx.cn/article/l1CY7TRw.shtml

相关文章

LeetCode 每日一题331. 验证二叉树的前序序列化

331. 验证二叉树的前序序列化 序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时&#xff0c;我们可以记录下这个节点的值。如果它是一个空节点&#xff0c;我们可以使用一个标记值记录&#xff0c;例如 #。 _9_/ \3 2/ \ / \4 1 # 6 / \ / \ / …

试证明:已知二叉树的前序序列和中序序列,可以唯一确定该二叉树

假设二叉树BT的总的结点个数为n&#xff0c;前序序列pre为&#xff0c;中序序列pin为&#xff0c;现用数学归纳法证明pre和pin可以唯一确定这棵二叉树&#xff1a; 1. n 1时&#xff0c;前序和中序序列都各只有一个元素&#xff0c;&#xff0c;此时等于&#xff0c;为BT的根…

验证二叉树的前序序列化[抽象前序遍历]

抽象前序遍历 前言一、验证二叉树的前序序列化二、抽象前序遍历总结参考文献 前言 难题要么复杂&#xff0c;要么未知&#xff0c;要么就是抽象考察&#xff0c;这三种类型都比较锻炼分析问题的能力。 一、验证二叉树的前序序列化 二、抽象前序遍历 package everyday.tree;p…

通过前序序列和中序序列或中序序列和后序序列还原二叉树(Java)

首先看一下这个二叉树的结构&#xff0c;回忆一下前序序列的输出方式&#xff08;中前后&#xff09;&#xff0c;中序序列的输出方式&#xff08;前中后&#xff09;&#xff0c;后序序列的输出方式&#xff08;前后中&#xff09;。 前中序列还原二叉树 此二叉树的前中序列…

已知前序序列和中序序列重建二叉树

一.解决方法&#xff1a; 在相关的书籍中描述了一个递归的解决方法&#xff0c;其算法思想如下&#xff1a; 1.从前序序列中第一个元素开始&#xff0c;取出一个元素&#xff0c;索引后移一位&#xff08;preIndex1&#xff09; 2.根据选择到的数值创建一个树节点newNode 3.然…

7-3 前序序列创建二叉树 (25 分) PTA

编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以二叉链表存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;代表一棵空树。然后再对二叉树进行中序遍…

C++实现 利用前序序列和中序序列构建二叉树

前言&#xff1a;已知一个二叉树的中序序列和前序序列&#xff0c;或者中序序列和后序序列就可以唯一确定一个二叉树&#xff08;必须知道中序序列&#xff09;&#xff0c;只知道前序和后序不能创建唯一的二叉树。 1.引例 已知下列某二叉树(8个结点)的两个序列&#xff0c;如…

7-2 前序序列创建二叉树

7-2 前序序列创建二叉树 (25 分) 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以二叉链表存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;代表一…

由序列确定二叉树:前序序列和中序序列构造二叉树 后序序列和中序序列构造二叉树 层次遍历序列和中序遍历序列构造二叉树 代码实现(c语言)

下面三种序列可以唯一的构造唯一的一棵二叉树&#xff1a; 前序序列和中序序列构造二叉树 后序序列和中序序列构造二叉树 层次遍历序列和中序遍历序列构造二叉树 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> #…

python实现根据前序序列和中序序列求二叉树的后序序列

根据前序序列求根结点&#xff0c;根据中序序列求左右子树。 如上图&#xff1a;根据前序序列&#xff0c;谁在前面谁就是根结点&#xff1b;根据根结点&#xff0c;确定左右子树。 class BiTreeNode:def __init__(self, data):self.data dataself.lchild Noneself.rchild …

已知二叉树的前序序列跟中序序列求后序序列(C语言)

原理是&#xff0c;比如 先序序列决定二叉树的根结点&#xff08;比如上图的“1”&#xff09;&#xff0c;后序序列决定二叉树的左右子结点&#xff08;比如上图的“4”&#xff0c;“7”&#xff0c;“2”为左子树的那部分&#xff0c;而“8”&#xff0c;“5”&#xff0c;“…

验证二叉树的前序序列化

缩点法验证二叉树的前序序列化 题目解决思路代码说明 题目 &#xff08;1&#xff09;序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时&#xff0c;我们可以记录下这个节点的值。如果它是一个空节点&#xff0c;我们可以使用一个标记值记录&#xff0c;例如 …

验证二叉树的前序序列化Python解法

序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时&#xff0c;我们可以记录下这个节点的值。如果它是一个空节点&#xff0c;我们可以使用一个标记值记录&#xff0c;例如 #。 例如&#xff0c;上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,…

7-1 前序序列创建二叉树

7-1 前序序列创建二叉树 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以二叉链表存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;代表一棵空树。…

超容易理解的数据结构前序,中序和后序序列

今天听了我们数据结构老师讲三序序列&#xff0c;发现有很多同学不能根据二叉树推&#xff0c;特发此文讲解。 对于三序序列&#xff0c;我们可以选择构造一个表格并创造一个指针&#xff0c;将指针指于二叉树根结点&#xff0c;对应表格则如下所示&#xff0c;若某节点无左孩…

IIC通信协议详解[转载]

IIC的基本介绍 IIC的简介 IIC&#xff08;Inter&#xff0d;Integrated Circuit&#xff09;总线是一种由PHILIPS公司在80年代开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。它是半双工通信方式。 IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件…

IIC通信协议学习

1.IIC简介 IIC&#xff1a;Inter-Integrated Circuit&#xff08;内部集成电路&#xff09; 需要两个管脚&#xff1a;SDA、SCL SPI协议&#xff1a;一个主设备对多个从设备&#xff0c;每增加一个从设备&#xff0c;需要增加一个端口CS* 特点&#xff1a;速度较快&#xff…

IIC总线设计①——IIC通信协议

目录 一、IIC&#xff08;Inter-Integrated Circuit&#xff09;总线介绍 二、IIC协议 &#xff08;一&#xff09;IIC通信过程 &#xff08;二&#xff09;起始信号和停止信号 程序 &#xff08;三&#xff09;应答信号和非应答信号 程序 &#xff08;四&#xff09;数…

STM32基于IIC通信协议的OLED模块使用(详解)

目录 前言 一、项目内容 实验简介 二、IIC模块 1、IIC协议简介 2、物理层 3、协议层 4、硬件IIC代码配置 5、软件模拟IIC配置 1、起始信号与停止信号 2、从机应答信号 3、数据的有效性 4、数据传输 三、OLED模块 1、软件配置 2、OLED原理 1、OLED初始化函数 …

IIC 通信协议 (一)

目录 引言 IIC协议 1、历史 2、特点 3、信号线 4、主从关系 5、通信过程 6、协议规范 地址 1、器件地址 2、存储器地址 读写时序 1、写时序 1.1、单字节写时序 1.2、连续写时序 2、读时序 2.1、单字节读时序 2.2、连续读时序 参考声明 引言 这个专栏闲置好…