数据结构基础:根据先序、中序遍历结果构造二叉树

栏目: 数据库 · 发布时间: 4年前

内容简介:由先序遍历,我们可以确定树根。在上例中,3是先序遍历的第一个结果,所以3肯定是树根;然后再中序遍历的结果中找到3,在3的左边的肯定是根的左子树,在3的右边的肯定是根的右子树。这样我们就将中序遍历的结果分成三个部分,3的左边(左子树)、3(根)、3的右边(右子树),然后对左右递归处理,递归的结果返回左右子树的根,然后根结点连接左右节点的根就得到结果了。大致的思路有了,接下来我们深入细节。当我们向左右递归的时候,怎么知道左右子树的根呢?还是要回到先序遍历,我们已经知道左子树和右子树了,我们根据先序遍历的特点——

由先序遍历,我们可以确定树根。在上例中,3是先序遍历的第一个结果,所以3肯定是树根;然后再中序遍历的结果中找到3,在3的左边的肯定是根的左子树,在3的右边的肯定是根的右子树。

这样我们就将中序遍历的结果分成三个部分,3的左边(左子树)、3(根)、3的右边(右子树),然后对左右递归处理,递归的结果返回左右子树的根,然后根结点连接左右节点的根就得到结果了。

大致的思路有了,接下来我们深入细节。当我们向左右递归的时候,怎么知道左右子树的根呢?还是要回到先序遍历,我们已经知道左子树和右子树了,我们根据先序遍历的特点——在前面的结果在树中的位置肯定是靠左的——也就是说左子树的先序遍历结果肯定在右子树先序遍历结果的前面。

通过上面的分析,我们的思路就清楚的了,构造步骤如下:

  1. 取出先序遍历的结果中未使用的第一个节点作为根
  2. 在中序遍历的结果中查找上一步得到的根,然后以改根为支点,将中序遍历的结果分成左右两部分,分别对应左子树和右子树
  3. 用上一步得到的结果,将先序遍历的结果分成左右两部分,具体操作是:得到上一步左边部分的长度,在先序遍历的结果中截取相同的长度——左子树的先序遍历结果,然后右边做相同的操作
  4. 将左右递归处理,回到第一步

以上面的例子来执行该算法:

  1. 拿先序遍历的第一个结果,得到根:3
  2. 在中序遍历的结果中搜索3,将中序遍历的结果:[9,3,15,20,7],分成左右两个部分:[9],[15,20,7]
  3. 将先序遍历的结果:[3,9,20,15,7],也分成左右两个部分(不包括3了,已经处理过的就不再处理):[9],[20,15,7]。现在我们得到的结果看起来向下面这样:
    3
      / \
     9   20,15,7
    复制代码
  4. 然后我们对左右分别递归,先拿到左递归的根——取先序遍历中的左边的结果:[9]中的第一个节点9,所有左子树的根就是9,处理中序遍历的左边结果:[9],发现只有一个节点,直接返回该节点。
  5. 然后递归右子树,先拿到右递归的根——取先序比阿尼中有边的结果:[20,15,7]中的第一个节点20,所有右子树的根就是20,现在得到的结果看起来向下面这样:
    3
     / \
    9   20
        |
      15,7 (15,7是20的左右子树,但是现在还不确定谁是左谁是右,需要下一步来确定)
    复制代码
  6. 回到第二步,在中序遍历的结果中搜索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
  }
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编码整洁之道

编码整洁之道

罗伯特·C.马丁 / 电子工业出版社 / 2012-8 / 59.00元

忍受各种不确定性及不间断的压力并能够获取成功的程序员有一个共通特征:他们都深度关注软件创建实践。他们都把软件看做一种工艺品。他们都是专家。在“鲍勃大叔”看来“专业”的程序员不仅应该具备专业的技能,更应该具备专业的态度,这也是本书阐述的核心。专业的态度包括如何用带着荣誉感、自尊、自豪来面对进行软件开发,如何做好并做得整洁,如何诚实地进行沟通和估算,如何透明并坦诚地面对困难做抉择,如何理解与专业知识相......一起来看看 《编码整洁之道》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具