前言
在二叉树算法题型中中最常见的一类题型是根据遍历结果来推断二叉树结构,一般分为这两类:
给定先序遍历和中序遍历,确定二叉树;
给定中序遍历和后序遍历,确定二叉树。
可以发现,给定的遍历结果中一定有中序遍历,请注意以下三条规律是设计该算法的依据:
(1)中序遍历结果负责判断左右子树,在中间节点左侧一定属于其对应左子树,在右侧一定属于其右子树;
(2)前序遍历结果第一个一定是中间节点,其后依次是其左子树节点和右子树节点;
(3)后序遍历结果最后一个一定是中间节点,其前依次是其右子树节点和左子树节点。
现在我们来分析根据遍历结果确定二叉树的算法流程,并且给出C++版本和Python版本的代码介绍,以及在实现算法时需要注意的API操作。
一、算法流程分析
本节我们以“给定先序遍历和中序遍历,确定二叉树”类型为例进行讲解,“给定中序遍历和后序遍历,确定二叉树”不再进行算法流程分析,但是也会在本文之后给出其代码实现。
1、问题描述
假设我们现在有两个数组如下:
前序遍历数组:Preorder=[3,9,20,15,7]
中序遍历数组:Inorder=[9,3,15,20,7]
现在我们想设计一套算法,输入这两个数组,返回一个二叉树结构。
2、算法分析
我们直接给出解决思路的三步流程,注意体会其流程,如下:
第一步:从前序数组中确定中间节点 前序数组Preorder的第一个元素一定是根节点,所以这个二叉树的根节点是3 第二步:切割中序数组为左右数组 根节点为3,将3带入中序数组Inorder,其左侧一定属于左子树,右侧一定属于右子树。 现在我们要将中序数组切割成左右两个数组,切割点就是根节点3所在的位置 根据Inorder中3的位置得到左数组leftInorder=[9]和右数组rightInorder=[15,20,7]。 第三步:切割前序数组为左右数组 同样,我们可以将前序数组Preorder=[3,9,20,15,7]切割成左右数组,其切割出的左数组(或右数组)一定和中序数组切割出的左数组(或右数组)的大小相同,所以首先我们去除最左侧根节点3,剩下的一定先是左子树的成员,其个数和leftInorder个数相同,所以可以以leftInorder的数组大小为切割个数,切割出左子树对应的数组leftPreorder=[9] 剩下则为右数组rightPreorderr=[20,15,7]。 |
经过以上三步流程,我们得到了一个初步的二叉树结构:
可以发现,上述三步流程帮助我们分出了二叉树的第一级结构,
而且我们惊喜的发现,虽然数组分出了4个,但是问题规模缩小了:
(1)左数组leftInorder和leftPreorder,分别对应中序数组和先序数组,求其对应左子树;
我们继续将leftInorder和leftPreorder带入以上三步流程,可以求得左子树,但是我们发现这两个数组都只有一个9,说明这个左子树只有一个根节点,我们的三步流程需要添加一个终止条件来将这个9返回,那就是在第一步得到根节点后,判断此时先序数组的个数是否为1,若为1说明此时数组中只有根节点,直接返回即可。
(2)右数组rightInorder和rightPreorderr,分别对应中序数组和先序数组,求其对应右子树;
我们继续将rightInorder=[15,20,7]和rightPreorderr=[20,15,7]带入上述三步流程:
第一步求得中间节点为20;第二步分出左中数组=[15]和右中数组=[7];第三步分出左前数组=[20],右前数组=[7];此时问题规模进一步缩小,左数组都是只有一个元素的数组[15],右数组都是只有一个元素的数组[7],继续代入三步流程则得到该二叉树结构为
3、算法流程
现在我们可以归纳上述求解二叉树的流程为如下递归程序:
输入前序数组Preorder和中序数组Inorder | |
1 | 若前序数组个数为0,直接返回NULL(终止条件) |
2 | 从前序数组中确定中间节点root |
3 | 若前序数组个数为1,直接返回root(终止条件) |
4 | 切割中序数组为leftInorder和rightInorder |
5 | 切割前序数组为leftPreorder和rightPreorder |
6 | 用leftInorder和leftPreorder调用该程序 |
7 | 用rightInorder和rightPreorder调用该程序 |
7 | 返回root |
可以简单总结为:2个终止条件,2次切割数组,2次递归调用。
二、算法代码实现
现在我们已经知晓算法流程了,可以将其用代码实现了。
1、Python版本
因为Python代码简洁,不容易受到程序语法的干扰,首先我们用Python写一下流程,直接给出如下:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 2个终止条件
if len(preorder)==0:
return None
rootValue = preorder[0]
root = TreeNode(rootValue)
if len(preorder)==1:
return root
# 2次切割数组
index = 0 # 切割点
for i in range(0,len(inorder)):# 遍历中序数组找到切割点
if inorder[i]==rootValue:
index = i
break
leftInorder = inorder[:index]
rightInorder = inorder[index+1:] # index是中间节点,不能从此开始
index = len(leftInorder) # 更改为前序数组的切割点
preorder.pop(0) # 去除中间节点
leftPreorder = preorder[:index]
rightPreorder = preorder[index:]
# 2次递归调用
root.left = self.buildTree(leftPreorder,leftInorder)
root.right = self.buildTree(rightPreorder,rightInorder)
return root
2、C++版本
接下来我们进一步给出C++版本,注意和Python版本进行对比:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
//2个终止条件
if (preorder.size() == 0) return NULL;
int rootValue = preorder[0];
TreeNode* root = new TreeNode(rootValue);
if (preorder.size() == 1) return root;
//2次切割数组
int Index;
for (Index = 0; Index < inorder.size(); Index++) {
if (inorder[Index] == rootValue) break;
}
vector<int> leftInorder(inorder.begin(), inorder.begin() + Index);
vector<int> rightInorder(inorder.begin() + Index + 1, inorder.end() );
Index = leftInorder.size();
preorder.erase(preorder.begin());//删除第一个元素
vector<int> leftPreorder(preorder.begin(), preorder.begin() + Index);
vector<int> rightPreorder(preorder.begin() + Index, preorder.end());
//2次递归调用
root->left = buildTree(leftPreorder,leftInorder );
root->right = buildTree(rightPreorder,rightInorder);
return root;
}
3、重要API
在知道算法流程后,代码实现中的难点可能就是切割数组的API选择了,我们特意将Python和C++中的相关实现单独拎出来供读者对比:
相关功能 | Python代码 | C++代码 |
去除数组第一个元素 | preorder.pop(0) | preorder.erase(preorder.begin()); |
去除数组最后一个元素 | preorder.pop() | preorder.pop_back(); |
截取[0,index) | leftPreorder = preorder[:index] | vector<int> leftPreorder(preorder.begin(), preorder.begin() + Index); |
截取[index,end) | rightPreorder = preorder[index:] | vector<int> rightPreorder(preorder.begin() + Index, preorder.end()); |
注意,以上数组概念在Python中指的是List,在C++中指的是vector。
三、附加后序版本
由于后序数组+中序数组和之前的区别主要在确定中间节点上,代码差别非常小,所以不再详解,直接给出实现结果:
1、Python版本
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
#2个终止条件
if len(postorder)== 0:
return None
rootValue = postorder[-1]
root = TreeNode(rootValue)
if len(postorder)== 1:
return root
#2步切割数组
Index=0;
for i in range(0,len(inorder)):
if inorder[i]==rootValue:
Index=i
break
leftInorder=inorder[:Index]
rightInorder=inorder[Index+1:]
Index = len(leftInorder)
postorder.pop()
leftPostorder=postorder[:Index]
rightPostorder=postorder[Index:]
#递归调用
root.left = self.buildTree(leftInorder,leftPostorder)
root.right = self.buildTree(rightInorder,rightPostorder)
return root
2、C++版本
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
//2个终止条件
if (postorder.size() == 0) return NULL;
int rootValue = postorder[postorder.size()-1];
TreeNode* root = new TreeNode(rootValue);
if (postorder.size() == 1) return root;
//2次切割数组
int Index;
for (Index = 0; Index < inorder.size(); Index++) {
if (inorder[Index] == rootValue) break;
}
vector<int> leftInorder(inorder.begin(), inorder.begin() + Index);
vector<int> rightInorder(inorder.begin() + Index + 1, inorder.end() );
Index = leftInorder.size();
postorder.pop_back();//删除最后一个元素
vector<int> leftPostorder(postorder.begin(), postorder.begin() + Index);
vector<int> rightPostorder(postorder.begin() + Index, postorder.end());
//2次递归调用
root->left = buildTree(leftInorder,leftPostorder);
root->right = buildTree(rightInorder,rightPostorder);
return root;
}