内容简介:由先序遍历,我们可以确定树根。在上例中,3是先序遍历的第一个结果,所以3肯定是树根;然后再中序遍历的结果中找到3,在3的左边的肯定是根的左子树,在3的右边的肯定是根的右子树。这样我们就将中序遍历的结果分成三个部分,3的左边(左子树)、3(根)、3的右边(右子树),然后对左右递归处理,递归的结果返回左右子树的根,然后根结点连接左右节点的根就得到结果了。大致的思路有了,接下来我们深入细节。当我们向左右递归的时候,怎么知道左右子树的根呢?还是要回到先序遍历,我们已经知道左子树和右子树了,我们根据先序遍历的特点——
由先序遍历,我们可以确定树根。在上例中,3是先序遍历的第一个结果,所以3肯定是树根;然后再中序遍历的结果中找到3,在3的左边的肯定是根的左子树,在3的右边的肯定是根的右子树。
这样我们就将中序遍历的结果分成三个部分,3的左边(左子树)、3(根)、3的右边(右子树),然后对左右递归处理,递归的结果返回左右子树的根,然后根结点连接左右节点的根就得到结果了。
大致的思路有了,接下来我们深入细节。当我们向左右递归的时候,怎么知道左右子树的根呢?还是要回到先序遍历,我们已经知道左子树和右子树了,我们根据先序遍历的特点——在前面的结果在树中的位置肯定是靠左的——也就是说左子树的先序遍历结果肯定在右子树先序遍历结果的前面。
通过上面的分析,我们的思路就清楚的了,构造步骤如下:
- 取出先序遍历的结果中未使用的第一个节点作为根
- 在中序遍历的结果中查找上一步得到的根,然后以改根为支点,将中序遍历的结果分成左右两部分,分别对应左子树和右子树
- 用上一步得到的结果,将先序遍历的结果分成左右两部分,具体操作是:得到上一步左边部分的长度,在先序遍历的结果中截取相同的长度——左子树的先序遍历结果,然后右边做相同的操作
- 将左右递归处理,回到第一步
以上面的例子来执行该算法:
- 拿先序遍历的第一个结果,得到根:3
- 在中序遍历的结果中搜索3,将中序遍历的结果:[9,3,15,20,7],分成左右两个部分:[9],[15,20,7]
-
将先序遍历的结果:[3,9,20,15,7],也分成左右两个部分(不包括3了,已经处理过的就不再处理):[9],[20,15,7]。现在我们得到的结果看起来向下面这样:
3 / \ 9 20,15,7 复制代码
- 然后我们对左右分别递归,先拿到左递归的根——取先序遍历中的左边的结果:[9]中的第一个节点9,所有左子树的根就是9,处理中序遍历的左边结果:[9],发现只有一个节点,直接返回该节点。
-
然后递归右子树,先拿到右递归的根——取先序比阿尼中有边的结果:[20,15,7]中的第一个节点20,所有右子树的根就是20,现在得到的结果看起来向下面这样:
3 / \ 9 20 | 15,7 (15,7是20的左右子树,但是现在还不确定谁是左谁是右,需要下一步来确定) 复制代码
-
回到第二步,在中序遍历的结果中搜索20,得到左右子树[15]、[7],此时我们就得到了完整的二叉树:
3 / \ 9 20 / \ 15 7 复制代码
javascript代码实现
看到这里应该完整的理解了构造过程了吧~接下来上代码,使用javascript:
/** * preleft,preright是preorder的左右边界,left和right是inorder的左右边界,因为左右子树在两个遍历结果中的位置不同,所以要区分 */ function construct(preorderResult,inorderResult,preleft,preright,left,right) { if(right-left <= 1) return { val: preorderResult[preleft], left: null, right: null } // 取根结点 var troot = preorderResult[preleft]; // 找到根结点在中序遍历结果中的位置 var trootIndexInorder = inorderResult.indexOf(troot); var lchild=null,rchild=null; // 确定左子树的节点数量 var llength = trootIndexInorder-left; // 确定先序遍历结果中左右子树的边界 var tpreright = preleft+1+llength; // 下面的两个if处理边界条件,至少有一个节点时才递归处理 if(trootIndexInorder > left) { lchild = construct(preorderResult,inorderResult,preleft+1,tpreright,left,trootIndexInorder); } if(trootIndexInorder+1 < right) { rchild = construct(preorderResult,inorderResult,tpreright,preorderResult.length,trootIndexInorder+1,right); } // 返回根结点和左右子树 return { val: troot, left: lchild, right: rchild } } 复制代码
扩展思考
那现在假如已知中序遍历和后续遍历的结果,要构造二叉树怎么办呢?是不是也可以用类似的思路解决这个问题呢呢?其实思路和前面的是一样,只是要结合后序遍历的特点(与前序相反,根结点在结果的最后面),我这里就只给代码了,如果前面的理解了,这个so easy了。 javascript:
function construct(inorder, postorder, inleft,inright,postleft,postright) { if(inright - inleft <= 1) { return { val: postorder[postright-1], left: null, right: null } } var troot = postorder[postright-1]; var trootInorderIndex = inorder.indexOf(troot); var lchild = null, rchild = null; var rlength = inright-1-trootInorderIndex; var tpostright = postright-1-rlength; if(trootInorderIndex > inleft) { lchild = construct(inorder,postorder,inleft,trootInorderIndex,0,tpostright) } if(trootInorderIndex+1 < inright) { rchild = construct(inorder,postorder,trootInorderIndex+1,inright,tpostright,postright-1); } return { val: troot, left: lchild, right: rchild } } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 数据结构-二叉树遍历
- 【PHP 实现数据结构】遍历二叉查找树
- 【图解数据结构】 一组动画彻底理解二叉树三种遍历
- 数据结构 2 字符串 数组、二叉树以及二叉树的遍历
- 数组常见的遍历循环方法、数组的循环遍历的效率对比
- C++拾趣——STL容器的插入、删除、遍历和查找操作性能对比(Windows VirtualStudio)——遍历和删除
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。