二叉树的先序、中序、后序、层序遍历方式详解,由遍历序列构造二叉树过程详解以及C++代码详细实现

二叉树的遍历

树与二叉树的定义、性质,二叉树的顺序存储结构、链式存储结构

二叉树的遍历是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。由于二叉树是一种非线性结构,每个结点都可能有两棵子树,因而需要寻找一种规律,以便使二叉树上的结点能排列在一个线性队列上,进而便于遍历。

由二叉树的递归定义可知,遍历一棵二叉树便要决定对根结点 N {\rm N} N 、左子树 L {\rm L} L 和右子树 R {\rm R} R 的访问顺序。按照先遍历左子树再遍历右子树的原则,常见的遍历次序有先序( N L R {\rm NLR} NLR )、中序( L N R {\rm LNR} LNR ) 和后序( L R N {\rm LRN} LRN )三种遍历算法,其中"序"指的是根结点在何时被访问。

image-20220712161552290
一棵二叉树

1.先序遍历

先序遍历( P r e O r d e r {\rm PreOrder} PreOrder)的操作过程如下。

若二叉树为空,则什么也不做;否则,

1)访问根结点;
2)先序遍历左子树;
3)先序遍历右子树。

对应的递归算法如下:

template<typename T>
struct BiTree {
    T data;                     //数据域
    BiTree *lchild, *rchild;    //左、右孩子指针
};

template<typename T>
void PreOrder(BiTree<T> *bt) {
    if (bt != NULL) {
        visit(bt);                //访问根结点
        PreOrder(bt->lchild);     //递归遍历左子树
        PreOrder(bt->rchild);     //递归遍历右子树
    }
}

对于上图所示的二叉树,先序遍历所得到的结点序列为1 2 4 6 3 5

2.中序遍历

中序遍历( I n O r d e r {\rm InOrder} InOrder)的操作过程如下。

若二叉树为空,则什么也不做;否则,

1)中序遍历左子树;
2)访问根结点;
3)中序遍历右子树。

对应的递归算法如下:

template<typename T>
void InOrder(BiTree<T> *bt) {
    if (bt != NULL) {
        InOrder(bt->lchild);     //递归遍历左子树
        visit(bt);               //访问根结点
        InOrder(bt->rchild);     //递归遍历右子树
    }
}

对于上图所示的二叉树,中序遍历所得到的结点序列为2 6 4 1 3 5

3.后序遍历

后序遍历( P o s t O r d e r {\rm PostOrder} PostOrder)的操作过程如下。

若二叉树为空,则什么也不做;否则,

1)后序遍历左子树;
2)后序遍历右子树;
3)访问根结点。

对应的递归算法如下:

template<typename T>
void PostOrder(BiTree<T> *bt) {
    if (bt != NULL) {
        PostOrder(bt->lchild);     //递归遍历左子树
        PostOrder(bt->rchild);     //递归遍历右子树
        visit(bt);                 //访问根结点
    }
}

对于上图所示的二叉树,后序遍历所得到的结点序列为6 4 2 5 3 1

三种遍历算法中,递归遍历左、右子树的顺序都是固定的,只是访问根结点的顺序不同。不管采用哪种遍历算法,每个结点都访问一次且仅访问一次,故时间复杂度都是 O ( n ) {\rm O(n)} O(n) 。在递归遍历中,递归工作栈的栈深恰好为树的深度,所以在最坏情况下,二叉树是有 n {\rm n} n 个结点且深度为 n {\rm n} n 的单支树,遍历算法的空间复杂度为 O ( n ) {\rm O(n)} O(n)

4.层序遍历

image-20220712192820972

上图所示为二叉树的层序遍历,即按照箭头所指方向,按照1,2,3,4的层次顺序,对二叉树中的各个结点进行访问。

要进行层序遍历,需要借助一个队列,其基本思路如下:

1)将根结点root加入队列q。
2)取出队首结点,访问它。
3)如果该结点有左孩子,将左孩子入队。
4)如果该结点有右孩子,将右孩子入队。
5)返回步骤2,直到队列为空。

对应的遍历算法如下:

template<typename T>
void LayerOrder(BiTree<T> *root) {
    queue<BiTree<T> *> q;   //注意队列里面存的是地址
    q.push(root);           //根结点地址入队
    while (!q.empty()) {
        BiTree<T> *now = q.front(); //取出队首元素
        q.pop();
        cout << now->data << "\t";  //访问队首元素
        if (now->lchild != NULL) q.push(now->lchild);   //左子树非空
        if (now->rchild != NULL) q.push(now->rchild);   //右子树非空
    }
}

5.由遍历序列构造二叉树

由二叉树的先序序列和中序序列可以唯一地确定一棵二叉树。

在先序遍历序列中,第一个结点一定是二叉树的根结点;
而在中序遍历中,根结点必然将中序序列分割成两个子序列,前一个子序列是根结点的左子树的中序序列,后一个子序列是根结点的右子树的中序序列。
根据这两个子序列,在先序序列中找到对应的左子序列和右子序列。在先序序列中,左子序列的第一个结点是左子树的根结点,右子序列的第一个结点是右子树的根结点。 如此递归地进行下去,便能唯一地确定这棵二叉树。

例子:求先序序列 A B C D E F G H I {\rm ABCDEFGHI} ABCDEFGHI 和中序序列 B C A E D G H F I {\rm BCAEDGHFI} BCAEDGHFI 所确定的二叉树。

1)首先,由先序序列可知 A {\rm A} A 为二叉树的根结点。
2)中序序列中 A {\rm A} A 之前的 B C {\rm BC} BC 为左子树的中序序列, E D G H F I {\rm EDGHFI} EDGHFI 为右子树的中序序列。
3)然后由先序序列可知 B {\rm B} B 是左子树的根结点, D {\rm D} D 是右子树的根结点。
4)以此类推,就能将剩下的结点继续分解下去,最后得到的二叉树如下图所示

image-20220712210753050
一棵二叉树的构造过程

6.下面推理由遍历序列构造二叉树的一般递归过程

构建二叉树第 1 步:

假设已知先序序列为 p r e 1 , p r e 2 , … , p r e n {\rm pre_1,pre_2, \dots, pre_n} pre1,pre2,,pren,中序序列为 i n 1 , i n 2 , … , i n n {\rm in_1,in_2, \dots, in_n} in1,in2,,inn,如下图所示。
那么由先序序列的性质可知,先序序列第一个元素 p r e 1 {\rm pre_1} pre1 是当前二叉树的根结点。
再由中序序列的性质可知,当前二叉树的根结点将中序序列划分为左子树和右子树。
因此,要做的就是在中序序列中找到某个结点 i n k {\rm in_k} ink,使得 i n k = = p r e 1 {\rm in_k==pre_1} ink==pre1 ,这样就在中序序列中找到了根结点。由此可知左子树的结点个数 n u m L e f t = k − 1 {\rm numLeft=k-1} numLeft=k1 。于是:
子树的先序序列区间就是 [ 2 , k ] {\rm [2,k]} [2,k]
子树的中序序列区间是 [ 1 , k − 1 ] {\rm [1,k-1]} [1,k1]
子树的先序序列区间是 [ k + 1 , n ] {\rm [k+1,n]} [k+1,n]
子树的中序序列区间 [ k + 1 , n ] {\rm [k+1,n]} [k+1,n]
接着只需要往左子树和右子树进行递归构建二叉树即可。

image-20220712214237590
先序序列与中序序列构建二叉树第1步示意图

构建二叉树中间步:

事实上,如果递归过程中当前先序序列的区间 [ p r e L , p r e R ] {\rm [preL,preR]} [preL,preR]中序序列的区间 [ i n L , i n R ] {\rm [inL,inR]} [inL,inR] ,那么左子树的结点个数为 n u m L e f t = k − i n L {\rm numLeft=k-inL} numLeft=kinL 。这样:
子树的先序序列区间就是 [ p r e L + 1 , p r e L + n u m L e f t ] {\rm [preL+ 1,preL + numLeft]} [preL+1,preL+numLeft]
子树的中序序列区间是 [ i n L , k − 1 ] {\rm [inL, k - 1]} [inL,k1]
子树的先序序列区间是 [ p r e L + n u m L e f t + 1 , p r e R ] {\rm [preL +numLeft +1, preR]} [preL+numLeft+1,preR]
子树的中序序列区间是 [ k + 1 , i n R ] {\rm [k+1, inR]} [k+1,inR] ,如下图所示。

那么,如果一直这样递归下去,什么时候是尽头呢?这个问题的答案是显然的,因为只要先序序列的长度小于等于0时,当前二叉树就不存在了,于是就能以这个条件作为递归边界。

image-20220712215556764
先序序列与中序序列构建二叉树中间步示意图

对应的完整二叉树构造算法代码如下:

这里构造先序序列为 A B C D E F G H I {\rm ABCDEFGHI} ABCDEFGHI 和中序序列为 B C A E D G H F I {\rm BCAEDGHFI} BCAEDGHFI 所确定的二叉树。

image-20220712232558316
#include <iostream>

using namespace std;

template<typename T>
struct BiTNode {
    T data;                     //数据域
    BiTNode *lchild, *rchild;   //左、右孩子指针
};

template<typename T>
class BiTree {
private:
    BiTNode<T> *root;   //根结点
    T *pre;             //先序序列
    T *in;              //中序序列
    int length;         //二叉树中的结点个数

    //构造二叉树
    //当前先序序列区间为[preL, preR],中序序列区间为[inL, inR],返回根结点地址
    BiTNode<T> *create(int preL, int preR, int inL, int inR) {
        if (preL > preR)
            return NULL;    //先序序列长度小于等于0时,直接返回

        BiTNode<T> *biTNode = new BiTNode<T>;   //新建一个新的结点,用来存放当前二叉树的根结点
        biTNode->data = pre[preL];  //新结点的数据域为根结点的值,根结点是当前先序序列区间的第1个结点

        int k;  //存储当前中序序列区间的根结点的索引
        for (k = inL; k <= inR; k++)
            if (in[k] == pre[preL])     //在中序序列中找到in[k] == pre[L]的结点
                break;

        int numLeft = k - inL;  //左子树的结点个数

        //左子树的先序区间为[preL+1,preL+numLeft],中序区间为[inL,k-1]
        //返回左子树的根结点地址,赋值给root的左指针
        biTNode->lchild = create(preL + 1, preL + numLeft, inL, k - 1);

        //右子树的先序区间为[preL + numLeft + 1,preR],中序区间为[k+1,inR]
        //返回右子树的根结点地址,赋值给root的右指针
        biTNode->rchild = create(preL + numLeft + 1, preR, k + 1, inR);

        return biTNode; //返回根结点地址
    }

    //后序遍历
    void PostOrder(BiTNode<T> *biTNode) {
        if (biTNode != NULL) {
            PostOrder(biTNode->lchild);
            PostOrder(biTNode->rchild);
            cout << biTNode->data << "\t";
        }
    }

public:
    //初始化先序序列,中序序列,然后根据这两个序列构造二叉树
    BiTree(T *pre, T *in, int length) {

        this->length = length;
        this->pre = new T[length + 1];
        this->in = new T[length + 1];

        //初始化先序序列,中序序列
        for (int i = 1; i <= length; i++) {
            this->pre[i] = pre[i];
            this->in[i] = in[i];
        }

        //构造二叉树
        root = create(1, length, 1, length);
    }


    //后序遍历
    void PostOrder() {
        PostOrder(root);
    }

};

int main() {
    char pre[] = {'#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'};
    char in[] = {'#', 'B', 'C', 'A', 'E', 'D', 'G', 'H', 'F', 'I'};

    BiTree<char> biTree(pre, in, 9);
    biTree.PostOrder();
    return 0;
}

运行结果:

C       B       E       H       G       I       F       D       A

继续以上面例子的二叉树为例,实现先序遍历,中序遍历,后序遍历,层序遍历,二叉树构造算法完整代码以及测试代码如下:

#include <iostream>
#include "queue"

using namespace std;

template<typename T>
struct BiTNode {
    T data;                     //数据域
    BiTNode *lchild, *rchild;   //左、右孩子指针
};

template<typename T>
class BiTree {
private:
    BiTNode<T> *root;   //根结点
    T *pre;             //先序序列
    T *in;              //中序序列
    int length;         //二叉树中的结点个数

    //构造二叉树
    //当前先序序列区间为[preL, preR],中序序列区间为[inL, inR],返回根结点地址
    BiTNode<T> *create(int preL, int preR, int inL, int inR) {
        if (preL > preR)
            return NULL;    //先序序列长度小于等于0时,直接返回

        BiTNode<T> *biTNode = new BiTNode<T>;   //新建一个新的结点,用来存放当前二叉树的根结点
        biTNode->data = pre[preL];  //新结点的数据域为根结点的值,根结点是当前先序序列区间的第1个结点

        int k;  //存储当前中序序列区间的根结点的索引
        for (k = inL; k <= inR; k++)
            if (in[k] == pre[preL])     //在中序序列中找到in[k] == pre[L]的结点
                break;

        int numLeft = k - inL;  //左子树的结点个数

        //左子树的先序区间为[preL+1,preL+numLeft],中序区间为[inL,k-1]
        //返回左子树的根结点地址,赋值给root的左指针
        biTNode->lchild = create(preL + 1, preL + numLeft, inL, k - 1);

        //右子树的先序区间为[preL + numLeft + 1,preR],中序区间为[k+1,inR]
        //返回右子树的根结点地址,赋值给root的右指针
        biTNode->rchild = create(preL + numLeft + 1, preR, k + 1, inR);

        return biTNode; //返回根结点地址
    }

    //先序遍历
    void PreOrder(BiTNode<T> *biTNode) {
        if (biTNode != NULL) {
            cout << biTNode->data << "\t";
            PreOrder(biTNode->lchild);
            PreOrder(biTNode->rchild);
        }
    }

    //中序遍历
    void InOrder(BiTNode<T> *biTNode) {
        if (biTNode != NULL) {
            InOrder(biTNode->lchild);
            cout << biTNode->data << "\t";
            InOrder(biTNode->rchild);
        }
    }

    //后序遍历
    void PostOrder(BiTNode<T> *biTNode) {
        if (biTNode != NULL) {
            PostOrder(biTNode->lchild);
            PostOrder(biTNode->rchild);
            cout << biTNode->data << "\t";
        }
    }

    //层序遍历
    void LayerOrder(BiTNode<T> *root) {
        queue<BiTNode<T> *> q;   //注意队列里面存的是地址
        q.push(root);            //根结点地址入队

        while (!q.empty()) {
            BiTNode<T> *now = q.front(); //取出队首元素
            q.pop();
            cout << now->data << "\t";   //访问队首元素
            if (now->lchild != NULL) q.push(now->lchild);   //左子树非空
            if (now->rchild != NULL) q.push(now->rchild);   //右子树非空
        }
    }


public:
    //初始化先序序列,中序序列,然后根据这两个序列构造二叉树
    BiTree(T *pre, T *in, int length) {

        this->length = length;
        this->pre = new T[length + 1];
        this->in = new T[length + 1];

        //初始化先序序列,中序序列
        for (int i = 1; i <= length; i++) {
            this->pre[i] = pre[i];
            this->in[i] = in[i];
        }

        //构造二叉树
        root = create(1, length, 1, length);
    }


    //先序遍历
    void PreOrder() {
        PreOrder(root);
    }

    //中序遍历
    void InOrder() {
        InOrder(root);
    }

    //后序遍历
    void PostOrder() {
        PostOrder(root);
    }

    //层序遍历
    void LayerOrder() {
        LayerOrder(root);
    }
};

int main() {
    char pre[] = {'#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'};
    char in[] = {'#', 'B', 'C', 'A', 'E', 'D', 'G', 'H', 'F', 'I'};

    BiTree<char> biTree(pre, in, 9);

    cout << "PreOrder:\t";
    biTree.PreOrder();
    cout << endl;

    cout << "InOrder:\t";
    biTree.InOrder();
    cout << endl;

    cout << "PostOrder:\t";
    biTree.PostOrder();
    cout << endl;

    cout << "LayerOrder:\t";
    biTree.LayerOrder();
    cout << endl;
    return 0;
}

运行结果:

PreOrder:       A       B       C       D       E       F       G       H       I
InOrder:        B       C       A       E       D       G       H       F       I
PostOrder:      C       B       E       H       G       I       F       D       A
LayerOrder:     A       B       D       C       E       F       G       I       H

小结:中序序列可以与先序序列、后序序列、层序序列中的任意一个来构建唯一的二叉树,而后三者两两搭配或是三个一起上都无法构建唯一的二叉树。原因是先序、后序、层序均是提供根结点,作用是相同的,都必须由中序序列来区分出左右子树。

7.使用扩充二叉树的遍历序列构造二叉树

二叉树对应的扩充二叉树的先序或者后序序列能够唯一确定一棵二叉树

扩充二叉树的中序序列不能唯一确定一棵二叉树

什么是扩充二叉树?

扩充二叉树是指在二叉树中出现空子树的位置增加空树叶所形成的二叉树,如下图所示。

image-20220713105813494

因为扩充二叉树包含空的叶子结点,可以用来判断子树是否为空,因此可以通过扩充二叉树的先序或者后序序列唯一构建一棵二叉树。

上图所示的扩充二叉树先序序列为:AB#C##DE##FG#H##I##

构造过程:

1)根据给定的扩充二叉树写出先序序列

2)如果当前先序序列第一个结点为空树叶,说明子树为空,返回空,否则构造根结点

3)然后使用先序序列 [ p r e L + 1 , p r e R ] {\rm [preL+1,preR]} [preL+1,preR],继续递归构造当前根结点的左子树,构造完成后,返回左孩子

4)然后使用先序序列 [ p r e L + n u m L e f t + 1 , p r e R ] {\rm [preL+numLeft+1,preR]} [preL+numLeft+1,preR],继续递归构造当前根结点的右子树,构造完成后,返回右孩子

以上面写好的代码为基础,使用扩充二叉树遍历序列构造二叉树代码,测试代码如下:

template<typename T>
struct BiTNode {
    T data;                     //数据域
    BiTNode *lchild, *rchild;   //左、右孩子指针
};

template<typename T>
class BiTree {
private:
    BiTNode<T> *root;     //根结点
    T emptyLeaf;          //空树叶
    
    //扩充二叉树遍历序列构造二叉树
    BiTNode<T> *ExtendCreate() {
        BiTNode<T> *biTNode;
        T node;
        cin >> node;            //输入根结点的数据域
        if (node == emptyLeaf)  //当前结点为空树叶
            return NULL;
        else {
            //新建根结点
            biTNode = new BiTNode<T>;
            biTNode->data = node;

            //递归构造当前根结点的左右子树
            biTNode->lchild = ExtendCreate();
            biTNode->rchild = ExtendCreate();
        }
        return biTNode;
    }
    
public:
    //使用扩充二叉树构造二叉树
    BiTree(T emptyLeaf) {
        this->emptyLeaf = emptyLeaf;
        root = ExtendCreate();
    }
}

int main() {
    BiTree<char> biTree('#');

    cout << "PreOrder:\t";
    biTree.PreOrder();
    cout << endl;

    cout << "InOrder:\t";
    biTree.InOrder();
    cout << endl;

    cout << "PostOrder:\t";
    biTree.PostOrder();
    cout << endl;

    cout << "LayerOrder:\t";
    biTree.LayerOrder();
    cout << endl;
    return 0;
}

运行结果:

AB#C##DE##FG#H##I##
PreOrder:       A       B       C       D       E       F       G       H       I
InOrder:        B       C       A       E       D       G       H       F       I
PostOrder:      C       B       E       H       G       I       F       D       A
LayerOrder:     A       B       D       C       E       F       G       I       H
  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值