二叉树的遍历

现在决定把自己很久以前的一些文章重新markdown一下,发到简书来,先从这篇二叉树的遍历说起的。
大家都知道二叉树的遍历分为前序遍历,中序遍历,后序遍历。记得大学学习这一部分的时候,觉得用递归就可以轻易实现,简直太简单了,所以也就没有认真学,不过后来面试的时候考官要写一下二叉树的中序遍历,而且不能用递归,当时就很尴尬了,所以现在把二叉树的每种遍历思想和方法都记录一下,做个备忘

递归的解法来解决前序中序和后续的遍历

这简直是太简单不过了

递归前序遍历

public static void preTranverse(TreeNode root){
  if(root != null){
    visit(root);
    preTranverse(root.leftChild);
    preTranverse(root.rightChild);
  }
}

是不是非常言简意赅,遍历的时候先访问本身,然后遍历左节点,接着遍历右节点,而且简单易懂,接下来直接写中序遍历和后续遍历,思路是一样的

递归中序遍历

public static void midTranverse(TreeNode root){
  if(root != null){
    midTranverse(root.leftChild);
    visit(root);
    midTranverse(root.rightChild);
  }
}

递归后续遍历

public static void postTranverse(TreeNode root){
  if(root != null){
    postTranverse(root.leftChild);
    postTranverse(root.rightChild);
    visit(root);
  }
}

这三种写法都简单明了,一看就懂。下面的重点是三种遍历方式的非递归实现

要理解非递归实现,我们就要先清楚三种遍历的逻辑过程,以中序遍历为例:

  • 想要访问节点p,就要先访问p的左子节点p.leftChild.
  • 想要访问p.leftChild,同样也要先访问p的左子节点的子节点,p.leftChild.leftChild
  • 依次类推,当p的左子节点以及左子节点的左子节点都访问完的时候,我们才可以访问P。这个时候P的指向应该是原来的root节点的最后一个左子节点
  • 当访问完P的时候,我们要借助P来对P的右节点进行访问.
  • 不断重复上一个过程,就可以中序遍历完整个二叉树

知道了这些,我们就可以大致写出代码了,代码中有注释,可以参考

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  //stack用来存放我们第二和第三个步骤中遍历到的左子节点,我们要依靠这些左子节点来进入到他们对应的右树中去
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    //按照思路,先让P节点的所有左子节点入栈
    while(p!=null){
      s.push(p);
      p = p.leftChild;
    }

    //这时候P应该为空,那么我们只能根据栈内是否有元素来判断是否要出栈了
    if(!s.isEmpty()){
      //注意,这里的if不能写成while,这样的话相当于全部出栈了,又回到了root节点,但是此时我们仅仅遍历了root节点左子树的所有左节点,还没有遍历左子树的所有右节点
      p=s.pop();
      //可以访问p了,因为这时候P指向root左子树的最后一个左子节点。
      visit(p);
      //虽然P是左子树的最后一个左子节点,但是P可能还会有右子节点,如果有的话,进入p的右子节点进行遍历,如果没有的话也没有关系,因为在下次大循环中P为空,还会继续取出我们原来栈内的元素进行遍历
      p=p.rightChild();
    }
  }
}

完整的代码就是这样,我们发现在外层循环的时候内层还有循环,这样效率不是很高,不过经过我们以上的分析,发现内层循环其实是可以省略的,代码如下

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    if(p!=null){
      s.push(p);
      p=p.leftChild;
    }else{
      p = s.pop();
      visit(p);
      p=p.rightChild;
    }
  }
}

代码简洁了很多,基本思路没有变,都是当p不为空或者栈中还有元素的时候,不断循环,当p不为空的时候,我们要让p入栈,并进入p的左树,当p为空的时候,我们出栈,把栈顶元素赋给p,并进入p的右树。
这样就可以完成整个二叉树的中序遍历了。

前序遍历和中序遍历流程差不多,只是visit调用的实际不一样,前序遍历是在进入左树之前就会调用visit方法,中序遍历是在进入右树之前调用visit方法

非递归的后续遍历

后续遍历相比于前两种遍历困难的地方在于父节点必须在左右子树都遍历完的时候才能进行访问,我们需要有一个新的变量来记录是否已经访问完右子树了。

public static void postTranverse(TreeNode root){
  TreeNode p = root;
  TreeNode lastVisited = null;
  Stack<TreeNode> s = new Stack();
  
  //我们在判断是否可以访问一个节点时,都需要确保该节点的左子树和有子树都已经访问完了,为了能够确定该节点的左右子树都访问完了,先进入他左子树上每个节点是否都访问过
  while(p!=null){
    s.push(p);
    p=p.leftChild;
  }
  while(!s.isEmpty()){
    //取出栈顶元素
    p=s.pop();
    if(p.rightChild == null || p.rightChild == lastVisited){
      //当p的右子树为Null或者已经被访问过的时候,我们才能访问p
      visit(p);
      lastVisited = p;
    }else{
      //栈顶的元素现在右子树没有被访问过,则再次入栈,先不访问该元素
      s.push(p);
      p=p.rightChild;
      //然后去判断这个右子树是否被访问过
      while(p!=null){
        s.push(p);
        p=p.leftChild;
      }
    }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,442评论 1 31
  • 二叉树的三种常用遍历方式 学习过数据结构的同学都清楚,除了层序遍历外,二叉树主要有三种遍历方式: 1. 先序遍历...
    SherlockBlaze阅读 1,222评论 0 4
  • 树(tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。...
    曾大稳丶阅读 993评论 0 1
  • 二叉树的遍历想必大家都不陌生,主要有三种遍历方式:前序遍历(pre-order traversal),中序遍历(i...
    akak18183阅读 1,110评论 0 1
  • 前中后序的递归实现 前中后序的非递归标准实现 总结 整体的思路是这样的: 指针p指向root,创建栈 当栈不为空或...
    熊白白阅读 374评论 0 0