boxmoe_header_banner_img

What should we build today?

文章导读

二叉树的遍历


avatar
moonhalo 2026年10月26日 38

遍历(traverse),按照一定顺序依次访问二叉树各个节点,在二叉树的各个operation中均有重要应用.

下面我来总结一下课程内教到的各种二叉树遍历算法.

一.遍历顺序

二叉树遍历的顺序可分为以下四种:

  • 1.先序遍历(preorder)
  • 2.中序遍历(inorder)
  • 3.后序遍历(postorder)
  • 4.层序遍历

前三者是以”左子树,右子树,root”先后访问的思想来执行的,由于默认先左后右,所以有三种顺序.层序遍历则是按照一层一层地来遍历.

二.先序遍历

1.先序遍历序列与其特点

以这么一棵树为例1

先序遍历是一个递归定义:若结点非空,先访问结点,再先序访问左子树,再先序访问右子树.

按照这种定义,将上面的二叉树先序遍历,得到:ABDECFHIGJ

注意到:先序遍历序列的第一个结点一定是root.

2.先序遍历递归算法

二叉树的先序算法定义是一个递归定义,按照定义可以写出如下递归算法:

  • 1.若root不为空,访问
  • 2.先序遍历左子树
  • 3.先序遍历右子树

3.先序遍历非递归算法

可将上述的递归算法改成非递归算法

3.1.双循环非递归

借用一个栈的辅助空间,将未处理的结点先入栈,然后再依次出栈.以下是详细算法:

  • 1.p一开始指向root,栈一开始为空
  • 2.while(p!=null),访问p,将p入栈,同时p=p->leftchild
  • 3.若栈不为空,pop(top),同时p指向该元素右子树
  • 4.重复2,3,直到p=null且栈为空.

以例1为例

  • P指向A,然后p不为空,A入栈,p指向A左子树B;
  • p不为空,B入栈,p指向D;
  • p不为空,D入栈,p指向D左子树(空);
  • p为空,栈不空,栈顶元素D出栈,p指向D右子树(也为空)
  • ……

该算法时间复杂度是2n,空间复杂度最差需要n(一条线,所有元素都要入栈),最优需要log2n(类似完全二叉树,只要入栈层数个元素)

3.2.单循环非递归

上述循环中有两个循环:while(p!=NULL)和while(stack!=NULL),可将上面的算法改成一个循环,具体实现如下:

  • 1.p一开始指向root,栈一开始为空
  • 2.p不为空,将p入栈
  • 3.若栈不空,pop(top),p指向该元素,访问p,将p的右子树入栈,再将p的左子树入栈
  • 4.循环执行3,直到栈为空,结束

以例1为例:

  • p指向A
  • p不为空,A入栈
  • 栈不空,pop(A),p指向A,访问A,将C入栈,再将B入栈
  • 栈不空,pop(B),p指向B,访问B,将E入栈,再将D入栈
  • 栈不空,pop(D),p指向D,访问D,D左右子树都为空,不入栈
  • ……

时间也是O(n),空间是O(logn)到O(n)

三.中序遍历

1.中序遍历序列与其特点

中序遍历与先序一样,也是递归定义:先中序遍历左子树,再访问结点,再中序访问右子树

例1的序列应该是这样:DBEAHFICGJ

中序序列的顺序十分有趣:

  • 1.中序序列写成a1,a2,.,an,中间存在一个ai为root,从a1到ai-1,是root左子树中序序列;从ai+1到an,是root右子树中序序列
  • 2.对中序序列的图像作画做点规定:每个结点的左子树整体位于它左侧,右子树整体位于它右侧.这样一来整棵树结点的水平位置就能区分.将这个树上的结点一一投影到横坐标上,形成的序列即为中序序列(可以用数学归纳法证明,证明略)

2.中序遍历递归算法

按照定义,可得出以下算法:

  • 1.若结点不空,中序遍历左子树
  • 2.访问root
  • 3.中序遍历右子树

3.中序遍历非递归算法

可将上述算法改成非递归模式:

3.1 双循环实现

实际上和先序的基本一致,只是visit结点的时间不同.详细如下:

  • 1.p一开始指向root,栈一开始为空
  • 2.while(p!=null),将p入栈,同时p=p->leftchild
  • 3.若栈不为空,pop(top),访问p,同时p指向该元素右子树
  • 4.重复2,3,直到p=null且栈为空.

时间空间复杂度一样

3.2 单循环实现

由于要先要处理左子树,再访问结点,我们需要一个标号来记录结点的状态.

比如采用如下的标号策略:0表示未访问左子树,1表示已访问左子树.

  • 当程序读到X0的时候,先将X右子树Y0入栈,然后将X改成X1再次入栈,再将X左子树Z0入栈;
  • 当读到X1的时候,程序会访问X结点;

具体算法如下:

  • 1.p一开始指向root,栈一开始为空
  • 2.将root0入栈
  • 3.若栈不空,pop(top),p指向该元素
  • 4.若p的标号是0,将p->lch以标号0入栈,p以标号1入栈,p->lch以标号0入栈
  • 5.若p的标号是1,visit(p)
  • 5.循环执行3,4,5,直到栈为空,结束

四.后序遍历

1.后序遍历序列与其特点

还是以这么一棵树为例

后序遍历也是一个递归定义:若结点非空,先后序访问左子树,再后序访问右子树,最后访问节点.

按照这种定义,将上面的二叉树后序遍历,得到:DEBHIFJGCA

注意到:后序遍历序列的最后一个结点一定是root

2.后序遍历递归算法

按照定义,给出以下算法:

  • 1.后序遍历左子树
  • 2.后序访问右子树
  • 3.访问root

3.后序遍历非递归算法

3.1 双循环后序遍历

在采用类似于先序和中序的显式栈方法时,我们会发现存在一个问题:在p=null后,我们pop(top)来访问top的右子树,但是此时还并未访问top,直接出栈会导致记录的信息缺失.

为此,我们同样用对入栈元素标号来解决这个问题:

  • 标号为0,表示右子树还没有访问.指针指向它时,应访问右子树;
  • 标号为1,表示右子树已经访问,指针指向它时,应该访问节点;

按照这种标号,我们给出如下算法:

  • 1.p一开始指向root,栈一开始为空
  • 2.while (p!=null),将p以标号0入栈,同时p=p->leftchild
  • 3.若栈不为空,pop(top),p指向top
  • 4.若p的标号是0,将p以标号1再次入栈,然后p=p->rch
  • 5.若p的标号是1,visit(p),p=null
  • 6.重复2,3,4,5,直到p=null且栈为空.

时间O(n),空间O(logn)到O(n)

3.2 单循环后序遍历



评论(0)

查看评论列表

暂无评论


发表评论

Moonhalo's little home