学科分类
目录
数据结构

二叉树的遍历

1、先序遍历

先序遍历是指在遍历二叉树时先访问根结点,然后访问左子树,最后访问右子树。例如图6-26中的二叉树,使用先序遍历访问的结果是:ABFLDI。接下来我们就来详细分析这棵二叉树的先序遍历过程。

第一步:访问根结点,输出A;

第二步:访问左子树;左子树是以B为根结点的二叉树,如图1所示。

图1 以B为根结点的左子树

先遍历这棵左子树的根结点,输出B;

第三步:访问B的左子树,发现没有左子树,则访问B的右子树,右子树是以F为根结点的二叉树,先遍历这棵树的根结点,则输出F;

第四步:访问F的左子树,左子树是以L为根结点的二叉树,遍历这棵树的根结点,输出L;

第五步:访问L的左子树, L没有左子树;则访问L的右子树,L没有右子树;以L为根结点的二叉树访问完毕,即F的左子树访问完毕;

第六步:访问F的右子树,发现F没有右子树,则以F为根结点的二叉树访问完毕,即B的右子树访问完毕,那么以B为根结点的二叉树就访问完毕,即A的左子树访问完毕;

第七步:访问A的右子树,右子树是以D为根结点的二叉树,先遍历根结点,则输出D;

第八步:遍历D的左子树,左子树是以I为根结点的二叉树,先遍历根结点,则输出I;

第九步:访问I的左子树,左子树不存在;访问I的右子树,右子树不存在;则以I为根结点的二叉树访问完毕,即D的左子树遍历完毕;

第十步:访问D的右子树,D没有右子树;则以D为根结点的二叉树访问完毕,即A的右子树遍历完毕,整棵树也遍历完毕;

这就是二叉树的先序遍历的过程,按上述分析步骤输出的结果为:ABFLDI。树是递归定义的,在遍历二叉树时也用到了递归,首先将整棵二叉树当作有三个单元的结构:根结点、左子树、右子树。遍历完根结点,开始遍历左子树时,把左子树当成一棵独立的二叉树来遍历;同样,当遍历右子树时,也将右子树当成一棵独立的二叉树来遍历,这就是递归的思想。

有了递归的思想,在实现先序遍历算法时就容易多了。很容易想到函数递归调用:将代表一棵树的根结点传入函数,遍历完根结点后,再将左子树作为一棵完整的二叉树传递给函数,等左子树遍历完成后,再将右子树作为一棵完整的二叉树传递给函数。代码如下所示:

void preOrder(BitNode* T)
{
  //遍历根节点
  if (T == NULL)
​    return;
  printf("%c", T->data); //输出根结点的值
  //遍历左子树
  if (T->lchild != NULL)
​    preOrder(T->lchild); //递归调用
  //遍历右子树
  if (T->rchild != NULL)
​    preOrder(T->rchild); //递归调用
}

2、中序遍历

有了先序遍历的经验,那么中序与后序遍历就好理解了,中序遍历就是先访问树的左子树,然后访问根结点,最后访问右子树,如果使用中序遍历输出图1中的二叉树,则结果为:BLFAID。

下面来分析中序遍历的过程:

第一步:先访问根结点A的左子树;左子树是以B为根结点的二叉树,再访问B的左子树,B没有左子树;则访问根结点B,输出B;

第二步:访问B的右子树,右子树是以F为根结点的二叉树;

第三步:访问F的左子树,F的左子树是以L为根结点的二叉树;

第四步:访问L的左子树,左子树不存在,访问根结点L并输出;

第五步:访问L的右子树,右子树不存在;则以L为根结点的二叉树访问完毕,即F的左子树访问完毕;访问根结点F并输出;

第六步:访问F的右子树,右子树不存在。则F的右子树访问完毕,以F为根结点的二叉树访问完毕,即B的右子树访问完毕,以B为根结点的二叉树访问完毕,即A的左子树访问完毕;

第七步:访问根结点A并输出;

第八步:访问A的右子树,A的右子树是以D为根结点的二叉树;

第九步:访问D的左子树,D的左子树是以I为根结点的二叉树;

第十步:访问I的左子树,左子树不存在;访问根结点I并输出;

第十一步:访问I的右子树,右子树不存在;则以I为根结点的二叉树访问完毕,即D的左子树访问完毕;访问根结点D并输出;

第十二步:访问D的右子树,右子树不存在;则以D为根结点的二叉树访问完毕,即A的右子树访问完毕;整棵树访问完毕;

分析得出的遍历结果为:BLFAID。中序遍历的代码实现如下所示:

void inOrder(BitNode* T)
{
  if (T == NULL)
​    return;
  //遍历左子树
  if (T->lchild != NULL)
​    inOrder(T->lchild); //递归调用
  //遍历根结点
  printf("%c", T->data); 
  //遍历右子树
  if (T->rchild != NULL)
​    inOrder(T->rchild);
}

3、后序遍历

后序遍历就是先访问树的左子树,然后访问树的右子树,最后访问根结点。图6-26中的二叉树按后序遍历输出,则输出结果为:LFBIDA。

前面已经详细分析了树的先序遍历与中序遍历,这里就不再给出后序遍历的详细过程,读者可根据先序和中序的遍历过程思想来分析后序遍历。后序遍历同样也是利用了递归的思想,大树之中套小树。

后序遍历算法的代码实现如下所示:

void lastOrder(BitNode* T)
{
  if (T == NULL)
​    return;
  //遍历左子树
  if (T->lchild != NULL)
​    lastOrder(T->lchild); //递归调用
  //遍历右子树
  if (T->rchild != NULL)
​    lastOrder(T->rchild);
  //遍历根结点
    printf("%c", T->data); 
}

接下来构建图6-26中的二叉树,并分别用先序、中序和后序遍历来输出这棵二叉树。三种遍历算法在讲解时已经给出,在案例中直接调用即可。具体代码如例1所示。

例1

 1    #include <stdio.h>
 2    #include <stdlib.h>
 3    typedef struct BitNode
 4    {
 5        char data;             //数据类型为char
 6        struct BitNode* lchild, *rchild;
 7    }BitNode;
 8    
 9    int main()
 10    {
 11        BitNode nodeA, nodeB, nodeD, nodeF, nodeI, nodeL; //创建6个结点
 12        //将结点都初始化为空,保证叶子结点相应指针指向空
 13        memset(&nodeA, 0, sizeof(BitNode));
 14        memset(&nodeB, 0, sizeof(BitNode));
 15        memset(&nodeD, 0, sizeof(BitNode));
 16        memset(&nodeF, 0, sizeof(BitNode));
 17        memset(&nodeI, 0, sizeof(BitNode));
 18        memset(&nodeL, 0, sizeof(BitNode));
 19        //给结点赋值
 20        nodeA.data = 'A';
 21        nodeB.data = 'B'; 
 22        nodeD.data = 'D';
 23        nodeF.data = 'F';
 24        nodeI.data = 'I';
 25        nodeL.data = 'L';
 26        //存储结点之间的逻辑关系
 27        nodeA.lchild = &nodeB; //A结点左孩子是B
 28        nodeA.rchild = &nodeD; //A结点的右孩子是D
 29        nodeB.rchild = &nodeF; //B结点的右孩子是F
 30        nodeF.lchild = &nodeL; //F结点的左孩子是L
 31        nodeD.lchild = &nodeI; //D结点的左孩子是I
 32    
 33        printf("二叉树构建成功!\n"); 
 34    
 35        printf("先序遍历:");
 36        preOrder(&nodeA);
 37        
 38        printf("\n中序遍历:");
 39        inOrder(&nodeA);
 40    
 41        printf("\n后序遍历:");
 42        lastOrder(&nodeA);
 43        printf("\n");
 44        system("pause");
 45        return 0;
 46    }

运行结果如图2所示。

图2 例1运行结果

在例1中,首先构建了图1中的二叉树,然后分别用先序、中序及后序遍历来访问这棵树,对比可知,图2中先序、中序及后序遍历的输出结果与前文分析的一致。

二叉树遍历算法分析:从前面的三种遍历算法可知道,如果将printf()语句抹去,从递归的角度看,这三种算法完全相同,或都说这三种算法访问的路径是相同的,只是访问结点的时机不同而已。针对图1中构建的二叉树,以结点B为例,遍历结点时,它有3次被访问的时机,如图3所示。

图3 二叉结的遍历路径

从A结点出发,到回到A结点的遍历路径中,如图中所标识1、2、3所示,一共经过结点B 3次。其实从虚线的出发点到终点,每个结点都有3次被访问的机会:

第一次经过时访问就是先序遍历;

第二次经过时访问就是中序遍历;

第三次经过时访问就是后序遍历;

访问路径相同,只是访问结点的时机不同,这是二叉树遍历的本质。

二叉树的遍历,从时间上来说,每个结点只被访问一次,其时间效率是O(n);从空间上来说,要按照树占用的最大辅助空间来算,例如,图3中,这是一棵补全的完全二叉树,那么在计算时,要把这些辅助空间也计算在内,因此其空间效率也是O(n)。

点击此处
隐藏目录