【概述】
树中最基本的操作是遍历,即从根结点出发,按照某种次序访问树中的所有结点,使得每个结点仅被访问一次
在二叉树中,有两种遍历方式,一种是基于树的递归特性的前/中/后序遍历,一种是基于树的层次特性的层次遍历
【访问函数】
通常来说,二叉树都是使用二叉链表实现的,即:
1 | typedef struct BiTNode { //二叉链表 |
在使用二叉链表实现的二叉树中,从构建二叉树,到求二叉树的深度,以及求叶结点个数等操作,都是在遍历操作基础上来进行的,因此在实际应用时,在遍历操作的基础上修改访问函数 visit()
即可得到大部分操作
1 | void visit(BiTree p) { //访问 |
【前中后序遍历的递归实现】
前序遍历
前序遍历,是按照根结点、左子树、右子树的顺序对二叉树进行遍历
1 | void preOrder(BiTree root) { //递归先序遍历 |
中序遍历
中序遍历,是按照左子树、根结点、右子树的顺序对二叉树进行遍历
1 | void inOrder(BiTree root) { //递归中序遍历 |
后序遍历
后序遍历,是按照左子树、右子树、根结点的顺序对二叉树进行遍历
1 | void postOrder(BiTree root) { //递归后序遍历 |
【前中后序遍历的非递归实现】
前序遍历
对于前序遍历,其非递归算法如下:
1)每遇到一个结点,若其不空,首先访问该结点,然后将其压入栈中,再访问它的左孩子,若当前结点为空且栈空时,算法结束
2)若其左孩子不空,转到步骤 1)
3)若其左孩子为空,说明已经到达该子树的最左底端,此时从栈中取出其父结点,访问父结点的右孩子,转到步骤 1)
1 | void preOrderByStack(BiTree root){//非递归前序遍历 |
中序遍历
对于中序遍历,其非递归算法如下:
1)每遇到一个结点,若其不空,首先将其压入栈中,再访问它的左孩子,若当前结点为空且栈空时,算法结束
2)若其左孩子不空,转到步骤 1)
3)若其左孩子为空,说明已经到达该子树的最左底端,此时从栈中取出其父结点,访问该结点,再访问父结点的右孩子,转到步骤 1)
1 | void inOrderByStack(BiTree root) { //非递归中序遍历 |
后序遍历
后序遍历要求先访问左子树和右子树后再访问根结点,在进行后序遍历时,首先访问左孩子,将父结点存于栈中,但当元素弹出时,如果利用非递归的前序中序遍历的思想,无法判断此时该访问右孩子还是访问父结点,因此其非递归实现需要一个标记,用于记录该结点的右孩子是否被访问过
后序遍历的非递归算法如下:
1)每遇到一个结点,若其不空,将其标记设为 0
,代表其右孩子未被访问,然后将其压入栈中,再访问其左孩子,若当前结点为空且栈空时,算法结束
2)若其左孩子不空,转到步骤 1)
3)若其左孩子为空,说明已经到达该子树的最左底端,此时从栈中取出一个结点,通过标记判断右孩子是否被访问过
4)若取出结点的标记为 0
,说明该结点的右孩子未被访问过,那么将标记改为 1
,重新压入栈中,访问该结点的右孩子,转到步骤 1)
5)若取出结点的标记为 1
,说明该结点的左右孩子均被访问过,访问该结点
6)若当前结点为空,但栈非空,说明已到达该子树的最右底端,此时从栈中取出一个结点,判断该结点的标记,若标记为 0
,转到步骤 4),若标记为 1
,转到步骤 5)
1 | void postOrderByStack(BiTree root) { //非递归后序遍历 |
说明:在后序遍历的非递归实现中,当访问一个结点 p
时,栈中的结点恰好是 p
的所有祖先,从栈底到栈顶的结点再加上 p
,就构成了从根到结点 p
的一条路径,利用该思想,可以求解根到某结点的路径、求两结点的最近公共祖先
【层次遍历】
层序遍历,是按层序从小到大逐个访问,同一层次按照从左到右的顺序访问
1 | void levelOrder(BiTree root) { //层序遍历 |