在二叉树的知识中,查找或者遍历二叉树是十分重要的知识点,由于笔者之前只重视二叉树遍历的过程,而忽视了从遍历恢复二叉树的逆过程的重要性,在某一天看到一道算法题的时候,才发现原来前中后序遍历还有逆过程!于是打开相应的书籍,并结合算法题写下了这一篇笔记
定理1:任何一个二叉树的先序遍历和中序遍历能唯一的确定这颗二叉树
其实这个理解起来可能会有一点绕,我来说一下我的想法吧,二叉树的中序遍历的顺序为左-根-右,先序遍历的顺序为根-左-右,那么这样的顺序来说,先序遍历的首个结点必为根节点,中序遍历的根节点必在中间,并且左右两侧分别为左右子树的中序遍历,根据中序遍历根节点位置处的结点分布,可以通过递归的方式来还原一颗二叉树,每一步递归的步骤可以为
1 、 首 先 找 到 中 序 遍 历 根 节 点 的 位 置 2 、 中 序 遍 历 根 节 点 左 边 的 ( 共 k 个 ) 数 据 为 根 节 点 左 子 树 的 中 序 遍 历 3 、 右 边 的 ( n − k − 1 个 ) 数 据 为 根 节 点 右 子 树 的 中 序 遍 历 1、首先找到中序遍历根节点的位置\\ 2、中序遍历根节点左边的(共k个)数据为根节点左子树的中序遍历\\ 3、右边的(n-k-1个)数据为根节点右子树的中序遍历 1、首先找到中序遍历根节点的位置2、中序遍历根节点左边的(共k个)数据为根节点左子树的中序遍历3、右边的(n−k−1个)数据为根节点右子树的中序遍历
将问题拆分到子树中,子树又拆分到子树中,逐层递归,依次回溯,直到返回最后的结果,这里可以画一个图来理解这个分配的过程,比如有一棵树的中序遍历和先序遍历分别为:
请看代码:
typedef struct node{
char data;
node* left;
node* right;
} BTNode; //二叉树的结点数据组织类型,采用二叉链式存储结构,并且下面从后序遍历和中序遍历来恢复二叉树也是这个结构组织
BTNode* CreateBT1(char* pre,char *in,int n){
BTNode* b;
char *p;
int k;
if(n<=0)
return nullptr;
b = (BTNode*)malloc(sizeof(BTNode));
b->data = *pre;
for(p = in;pleft = CreateBT1(pre+1,in,k);
b->right = CreateBT1(pre+k+1,p+1,n-k-1);
}
//这里假设二叉树里面存储的元素为char
leedCode上面也有同样的一个算法题,也是同样的思路,其题目为
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:3
/ \
9 20
/ \
15 7
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。代码(c++实现)
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: TreeNode* buildTree(vector
& preorder, vector & inorder) { TreeNode* roots = nullptr; if(preorder.size()!=inorder.size()) //保证传入的先序遍历,后续遍历的数组长度一样 return roots; if(preorder.size()<=0) //保证传入的数组长度有效 return roots; roots = buildTree(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1); //通过递归的方法对roots进行过赋值 return roots; } TreeNode* buildTree(vector & preorder,vector & inorder,int pre_start,int pre_end,int in_start,int in_end){ TreeNode* roots = new TreeNode(); //初始化一棵树的结点 roots->val = preorder[pre_start]; //先序遍历数组的第一个值为根节点的值 int k = in_start; //从中序遍历的起始点开始,找到中序遍历中根节点的位置 for(;k right = buildTree(preorder,inorder,pre_start+1,pre_end,k+1,in_end); }else if(k==in_end){ //此根节点没有右子树,根结点出现在中序遍历的最后 roots->left = buildTree(preorder,inorder,pre_start+1,pre_end,in_start,k-1); }else{ //既有左子树又有右子树的情况 roots->left = buildTree(preorder,inorder,pre_start+1,pre_start+k-in_start,in_start,k-1); roots->right = buildTree(preorder,inorder,pre_start+k-in_start+1,pre_end,k+1,in_end); } } return roots; } }; Java实现
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { TreeNode roots = new TreeNode(); if(preorder.length!=inorder.length) return roots; if(preorder.length<=0) return roots; roots = buildTree(preorder,inorder,0,(preorder.length-1),0,(inorder.length-1)); return roots; } private TreeNode buildTree(int[] preorder,int[] inorder,int pre_start,int pre_end,int in_start,int in_end){ TreeNode roots = new TreeNode(); roots.val = preorder[pre_start]; int k = in_start; for(;k<in_end;k++){ if(preorder[pre_start]==inorder[k]) break; } if(pre_start==pre_end) return roots; else{ if(k==in_start) roots.right = buildTree(preorder,inorder,pre_start+1,pre_end,k+1,in_end); else if(k==in_end) roots.left = buildTree(preorder,inorder,pre_start+1,pre_end,in_start,k-1); else{ roots.left = buildTree(preorder,inorder,pre_start+1,pre_start+k-in_start,in_start,k-1); roots.right = buildTree(preorder,inorder,pre_start+k-in_start+1,pre_end,k+1,in_end); } } return roots; } }
定理2:任何一个二叉树的中序遍历和后序遍历能唯一的确定这颗二叉树
上面的先序遍历和中序遍历恢复一颗二叉树的思路理解之后,那么从后序遍历和中序遍历恢复二叉树也是同样的思路,只是顺序会有一点小小的变化,因为后序遍历根节点在最后,此时需要从后序遍历的最后往前出发,找到中序遍历中的根节点位置,如果将一棵二叉树只理解为根结点和左右子树时,那么它的后序遍历可以理解为左子树的后序遍历+右子树的后序遍历+根结点,中序遍历也即前面说过的左子树的中序遍历+根节点+右子树的中序遍历,这样一看来,将其中对应的部分拆分出来,又得到了一个思路逻辑简化了的子问题!,按照这种思路,同样可以画出上面出现的那颗树通过中后序遍历恢复的流程图
中序遍历:DBEAFCG
后序遍历:DEBFGCA
BTNode* CreateBT2(char *post,char *in,int n){
BTNode *b;
char r,*p;
int k;
r = *(post+n-1);
b = (BTNode*)malloc(sizeof(BTNode));
b->data = r;
for(p = in;pleft = CreateBT2(post,in,k);
b->right = CreateBT2(post+k,p+1,n-k-1);
}