- 从前序与中序遍历序列构造二叉树_leetcode105
- 从中序与后序遍历序列构造二叉树_leetcode106
- 根据前序和后序遍历构造二叉树_leetcode889
- 二叉树的序列化与反序列化_leetcode297
1.从前序与中序遍历序列构造二叉树
思路:
第一步:
前序遍历的第一个节点,即为根节点。
第二步:
由根节点,可以在中序遍历中,找到左子树对应的长度。
第三步:
根据左子树的长度,
可以在先序序列和中序序列中,找到左子树和右子树对应的先序序列和中序序列。
代码如下:
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTree(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
public TreeNode buildTree(int[] preorder, int startPre,int endPre,
int[] inorder,int startIn,int endIn) {
if(startPre>endPre){
return null;
}
TreeNode root = new TreeNode(preorder[startPre]);//创建根节点
int i = startIn; // i最终定位的是中序序列中,根节点的下标。
for(;i<=endIn;i++){
if(inorder[i]==preorder[startPre]){
break;
}
}
root.left = buildTree(preorder,startPre+1,startPre+i-startIn,inorder,startIn,i-1);
root.right = buildTree(preorder,startPre+i-startIn+1,endPre,inorder,i+1,endIn);
return root;
}
}
2.从中序和后序遍历序列构造二叉树
思路分析:
与上题类似,由后序遍历,来确定根节点的值。
然后,从中序遍历中,找到左右子树的长度及对应的序列,
递归构造即可。
代码如下:
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder==null || postorder==null){
return null;
}
TreeNode root = buildTree(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
return root;
}
public TreeNode buildTree(int[] inorder,int startIn,int endIn,int[] postorder,int startP,int endP){
if(startIn > endIn){
return null;
}
int rootVal = postorder[endP];
TreeNode root = new TreeNode(rootVal);
// 求中序序列中root节点的下标
int rootIndex = startIn;
for(;rootIndex<=endIn;rootIndex++){
if(inorder[rootIndex]==rootVal){
break;
}
}
int leftLen = rootIndex - startIn;// 左子树的长度
root.left = buildTree(inorder,startIn,startIn+leftLen-1,postorder,startP,startP+leftLen-1);
root.right = buildTree(inorder,rootIndex+1,endIn,postorder,startP+leftLen,endP-1);
return root;
}
}
3.从前序和后序遍历序列构造二叉树
思路1:根据前序和后序序列及起始位置来构建二叉树。
前序遍历的第一个节点可以确定根节点.
- 前序序列中,根节点的下一个节点,为左子树的根节点。
- 由前序序列中的根节点的值,可以在后序序列中,找到对应的根节点的位置,从而确定左子树的位置。
依次迭代,便可以递归构造出二叉树。
代码实现:
class Solution {
public TreeNode constructFromPrePost(int[] pre, int[] post) {
if(pre==null || post==null){
return null;
}
TreeNode res = constructFromPrePost(pre,0,pre.length-1,post,0,post.length-1);
return res;
}
public TreeNode constructFromPrePost(int[] pre,int startPre,int endPre,int[] post,int startP,int endP) {
if(startPre > endPre){
return null;
}
int rootVal= pre[startPre];
TreeNode root =new TreeNode(rootVal);
if(startPre+1 <=endPre){
int leftRootVal = pre[startPre+1];// 左子树的根节点
int leftRootIndex = startP;
for(;leftRootIndex<endP;leftRootIndex++){
if(post[leftRootIndex]==leftRootVal){
break;
}
}
int leftLen = leftRootIndex - startP; // 左子树的长度
root.left = constructFromPrePost(pre,startPre+1,startPre+1+leftLen,post,startP,startP+leftLen);
root.right = constructFromPrePost(pre,startPre+1+leftLen+1,endPre,post,startP+leftLen+1,endP-1);
}
return root;
}
}
拓展:
仅仅知道前序遍历和后序遍历的顺序,不能唯一确定一棵二叉树。如下图所示:
二叉树遍历情况为
二叉树1的前序序列为[1,2,3,4],后序序列为[3,4,2,1]
二叉树2的前序序列为[1,2,3,4],后序序列为[3,4,2,1].
两棵二叉树的结构不同,但是其前序和后序的序列的结果是一样的。
所以,根据前序序列和后序序列的结果,不能唯一确定一棵二叉树。
上述题解默认的是构造二叉树1的这种情况。由于leetcode中的测试用例是按照构建二叉树的层次遍历结构来验证,构造的结果,所以,上述代码能够通过。
思路二:leetcode官方题解,易于理解
前序遍历为:
(根结点) (前序遍历左分支) (前序遍历右分支)
而后序遍历为:
(后序遍历左分支) (后序遍历右分支) (根结点)
例如,如果最终的二叉树可以被序列化的表述为 [1, 2, 3, 4, 5, 6, 7],
那么其前序遍历为 [1] + [2, 4, 5] + [3, 6, 7],而后序遍历为 [4, 5, 2] + [6, 7, 3] + [1].
果我们知道左分支有多少个结点,我们就可以对这些数组进行分组,并用递归生成树的每个分支。
算法
我们令左分支有 L 个节点。我们知道左分支的头节点为 pre[1],但它也出现在左分支的后序表示的最后。
所以 pre[1] = post[L-1](因为结点的值具有唯一性),因此 L = post.indexOf(pre[1]) + 1。
现在在我们的递归步骤中,左分支由 pre[1 : L+1] 和 post[0 : L] 重新分支,
而右分支将由 pre[L+1 : N] 和 post[L : N-1] 重新分支。
这里,巧妙的利用数学中的设未知数,设有L个节点,使得编程变得简单。是上述思路一种的简化版。
代码实现如下:
class Solution {
public TreeNode constructFromPrePost(int[] pre, int[] post) {
int N = pre.length;
if(N==0) return null;
TreeNode root = new TreeNode(pre[0]);
if(N==1) return root;
int L = 0;
for(int i=0;i<N;i++){
if(post[i]==pre[1])
L = i+1;
}
root.left = constructFromPrePost(Arrays.copyOfRange(pre,1,L+1),
Arrays.copyOfRange(post,0,L));
root.right = constructFromPrePost(Arrays.copyOfRange(pre,L+1,N),
Arrays.copyOfRange(post,L,N-1));
return root;
}
}
4.二叉树的序列化和反序列化
思路:
序列化,就是层次遍历二叉树,将遍历结果用字符串来表示。若某一节点为null,则用特殊的字符来表示,如“#”
反序列化,根据层次化遍历序列,来重构二叉树。
代码实现:
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node==null){
sb.append("#,");
}else{
sb.append(node.val).append(",");
queue.add(node.left);
queue.add(node.right);
}
}
sb.deleteCharAt(sb.length()-1); // 删除最后一位的字符“,”
return sb.toString();
}
// Decodes your encoded data to tree. // 将上述的层次遍历的字符串,重构为二叉树
public TreeNode deserialize(String data) {
String[] strings = data.split(",");
if(strings.length==1 && strings[0].equals("#")){
return null;
}
// 使用一个队列来保存下一层节点
Queue<TreeNode> queue = new LinkedList<>();
TreeNode root = new TreeNode(Integer.parseInt(strings[0]));
queue.offer(root);
int index = 1; // 字符串数组的下标
while(index < strings.length){
TreeNode node = queue.poll();
if(node != null){
TreeNode left = strings[index].equals("#")?null:new TreeNode(Integer.parseInt(strings[index]));
index++;
queue.offer(left);
node.left = left;
// 因为字符串是由层次遍历拼接组成的,所以能保证index+1,和index+2都在字符的长度范围内
TreeNode right = strings[index].equals("#")?null:new TreeNode(Integer.parseInt(strings[index]));
index++;
queue.offer(right);
node.right = right;
}
}
return root;
}
}
结语
以上,便是关于二叉树与二叉树结构之间的四类题目总结。
从上面可知,有序列化的某一排序序列,可以反序列化,重构实际的二叉树。因为在序列化的过程中,是用了特殊的字符,来记录null节点的信息。
如果没有用特殊的符号来记录空信息,则需要通过两种方式的遍历序列,才能重构二叉树。
前序遍历+中序遍历,可以唯一确定一棵二叉树。
后序遍历+中序遍历,也可以唯一确定一棵二叉树。
但是,前序遍历+后序遍历,不能唯一确定一棵二叉树。