二叉树的层次遍历及其变型

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/Apollon_krj/article/details/79353001

一、二叉树的层次遍历题目分析:

题目简述:
给定一棵二叉树的根节点root,请编写函数方法,实现二叉树的层次遍历。如,有一棵二叉树如下:
这里写图片描述
其不同的遍历方式的遍历结果分别为:

先序遍历:A、B、D、E、C、F、G
中序遍历:D、B、E、A、F、C、G
后序遍历:D、E、B、F、G、C、A
层次遍历:A、B、C、D、E、F、G

那么所谓层次遍历,顾名思义就是遵循“从上往下、从左往右”的规则逐层遍历。

我们设定二叉树节点结构:

typedef struct binaryTree{
    char data;
    struct binaryTree * left;
    struct binaryTree * right;
}BTree;

该如何去做?首先我们知道,要遍历第一层(即根节点),就必须知道其根节点地址(即root)。那么遍历下一层就必须知道其上一层所有节点的左右指针域,对于第二层来说,就是要知道root->left和root->right。

上面我们说从左往右,每一层是具有顺序性质的,稍加思索,或许我们就可以得到一种最为常见的遍历方式:借助队列这个辅助的结构来遍历。由于下一层要借助上一层的地址来遍历(如第二层要借助第一层root来获取root的两个指针域),所以我们先把上一层的节点地址全部入队,再接着依次出队,每出一个节点,判断该节点的左右指针域是否为空,如果不为空依次将左右子树的根节点地址入队(即出队节点的左右指针入队)。上述显然是一个循环的过程,其循环体开始前先将root入队,之后开始循环,那么循环的结束依据则是队列是否为空,不为空就一直进行循环过程。

二、算法流程剖析:

下面我们就上题所述结构的层次遍历算法进行具体分析:

为了方便表示,我们作出以下表示:

a_root = root;          //其所标识地址的数据域为'A'
b_root = a_root->left;  //其所标识地址的数据域为'B'
c_root = a_root->right; //其所标识地址的数据域为'C'
d_root = b_root->left;  //其所标识地址的数据域为'D'
e_root = b_root->right; //其所标识地址的数据域为'E'
f_root = c_root->left;  //其所标识地址的数据域为'F'
g_root = c_root->right; //其所标识地址的数据域为'G'

①、首先,我们将root(a_root)入队:
这里写图片描述

②、判断队列是否为空,不为空,则出队,用temp接收出队的节点地址,并输出其数据:temp->data。判断接收到的temp的左右指针域与是否为空,不为空则将左右子树的根节点地址入队

这里写图片描述

a_root出队,输出a_root->data为‘A’其左右指针域b_root和c_root入队:

这里写图片描述

③、第②步是一个完整的循环,将第②步继续执行:

这里写图片描述

b_root出队,输出b_root->data为‘B’则其左右指针域d_root和e_root入队:
这里写图片描述

④、c_root出队,输出c_root->data为‘C’则其左右指针域f_root和g_root入队:

这里写图片描述

这里写图片描述

⑤、继续循环,由于剩余的d_root、e_root、f_root、g_root都不存在左右子树,因此d_root、e_root、f_root、g_root以此出队,并输出’D’、’E’、’F’、’G’,此时队列为空,循环结束。整个层次遍历也就结束。

三、函数实现(借助C++标准库的queue):

/*
    queue类属于std命名空间
    empty()如果队列空则返回真
    back()返回最后一个元素
    front()返回第一个元素
    pop()出队:直接删除
    push()入队
    size()返回队列中元素的个数
*/
//层次遍历(一层一层从左往右输出,输出本层完毕,输出下一层)
void level_traversal(BTree * root)
{
    std::queue<BTree> Queue; //队列里存放的是节点值,因此一个元素大小为sizeof(BTree) = 12Byte

    if(root == NULL){
        return;
    }
    //先再循环外入队root
    Queue.push((const BTree &)(*root));//传递的是一个节点的引用
    BTree temp;//temp接收节点值
    while(!Queue.empty()){
        temp = Queue.front();//返回第一个元素
        Queue.pop();//将该元素出队删除
        if(temp.left)
            Queue.push((const BTree &)(*temp.left));//左子树根节点压入队列
        if(temp.right)
            Queue.push((const BTree &)(*temp.right));//右子树根节点压入队列
        std::cout << temp.data << " ";//输出出队的元素值
    }
}

修改优化:

void level_traversal(BTree * root)
{
    std::queue<BTree *> Queue; //队列里存放的是节点地址,因此一个元素大小为4Byte
    if(root == NULL){
        return;
    }
    Queue.push((BTree * const &)(root));//传递的是一个地址的引用(指针的引用)
    BTree * temp = NULL;//temp用来接收节点地址
    while(!Queue.empty()){
        temp = Queue.front();//返回第一个值(地址)
        Queue.pop();//将该地址出队删除
        if(temp->left)
            Queue.push((BTree * const &)(temp->left));//左子树根节点的地址压入队列
        if(temp->right)
            Queue.push((BTree * const &)(temp->right));//右子树根节点的地址压入队列
        std::cout << temp->data << " ";//输出出队的指针所指的元素值
    }
}

对于入队的元素类型,优化为指针的引用,节省内存空间与程序执行效率。

//优化前传值的引用:
std::queue<BTree> Queue; 
Queue.push((const BTree &)(*root));

//优化后传指针的引用:
std::queue<BTree *> Queue;
Queue.push((BTree * const &)(root));

很多人对于指针的引用不知道如何去传,是怎样的形式去写。即使不太清楚也没关系,我们可以用编译器的提示来进行修改:

//正确的传指针引用的写法:
std::queue<BTree *> Queue;
Queue.push((BTree * const &)(root));
//假设我们不知道是这种写法,我们可以随便写一个类型,比如:
std::queue<BTree *> Queue;
Queue.push((BTree const * &)(root));

//编译器检测到类型不匹配,就会进行错误报告,如下为VC++6.0自带编译器所检测并报告的错误类型与提示:
//error C2664: 'push' : cannot convert parameter 1 from 'const struct binaryTree *' to 'struct binaryTree *const & '
//    Conversion loses qualifiers

//我们发现错误检测中所说,目标类型是“struct binaryTree * const & ”,即“BTree * const &”,而我们所写代码中的类型是“BTree const * &”(“const BTree * &”),因此只要按照提示修改正确即可。

四、二叉树的层次遍历变型:

对于二叉树的层次遍历还有很多变化,比如:①层次遍历每遍历一层加一次换行;又如②统计节点数最多的一层,记录该层的层数与节点数;以及③从上到下依次遍历从左边能看到的二叉树的所有节点(每一层的第一个)。

对此种变化,其核心都是借助队列来操作,至于细节各不相同,有兴趣的读者可以参考以下代码进行交流学习:

1、层次遍历并且每输出完一层换行一次:

void hierarchical_line_feed(BTree * root)
{
    std::queue<BTree *> Queue; 
    if(root == NULL){
        return;
    }
    Queue.push((BTree * const &)(root));//根节点不为空直接入队
    int count1 = 1, count2 = 0;//count1是第n层的节点数,初始值为1,count2是n+1层的节点数
    BTree * temp = NULL;
    while(!Queue.empty()){
        while(count1--){
            temp = Queue.front();//返回第一个元素
            Queue.pop();//将该元素出队删除
            if(temp->left){
                Queue.push((BTree * const &)(temp->left));
                count2++;
            }
            if(temp->right){
                Queue.push((BTree * const &)(temp->right));
                count2++;
            }
            std::cout << temp->data << " ";//输出出队的元素值
        }
        std::cout << std::endl;//每一层结束时(即内层循环结束时)换行
        count1 = count2;//更新count1为count2
        count2 = 0;//count2置为0
    }
}

2、统计节点元素数最多的一层,记录该层的层数和该层节点总数:

//max为最大值,level为层数,均以引用方式传递
void most_nodes_layer(BTree * root, int &max, int &level)
{
    std::queue<BTree *> Queue; 
    if(root == NULL){
        return;
    }
    Queue.push((BTree * const &)(root));
    int count1 = 1, count2 = 0, n = 1;
    max = 1, level = 1;//初始值为第一层即root所在层,max为1,level为1
    BTree * temp;
    while(!Queue.empty()){
        while(count1--){
            temp = Queue.front();//返回第一个元素
            Queue.pop();//将该元素出队删除
            if(temp->left){
                Queue.push((BTree * const &)(temp->left));
                count2++;
            }
            if(temp->right){
                Queue.push((BTree * const &)(temp->right));
                count2++;
            }
        }
        count1 = count2;
        count2 = 0;
        n++;
        //比较该层的节点数与当前最多节点数的层,并且更新
        if(max < count1){
            max = count1;
            level = n;
        }
    }
}

3、遍历输出从上到下依次遍历从左边能看到的二叉树的所有节点(每一层的第一个):

//触类旁通:依次遍历从右边能看到的所有节点,即每一层的最后一个
void left_can_see_node(BTree * root)
{
    std::queue<BTree *> Queue; 
    if(root == NULL){
        return;
    }
    Queue.push((BTree * const &)(root));
    int count1 = 1, count2 = 0, n = 1;
    BTree * temp;
    while(!Queue.empty()){
        std::cout << Queue.front()->data << " ";
        while(count1--){
            temp = Queue.front();//返回第一个元素
            Queue.pop();//将该元素出队删除
            if(temp->left){
                Queue.push((BTree * const &)(temp->left));
                count2++;
            }
            if(temp->right){
                Queue.push((BTree * const &)(temp->right));
                count2++;
            }
        }
        count1 = count2;
        count2 = 0;
    }
}

以上所有算法的时间复杂度均为O(N),N为二叉树的节点个数。对于二叉树的层次遍历是此种解决方式,对于非二叉形式的树,其层次遍历也应该举一反三。
如:

typedef struct tree{
    char data;
    struct binaryTree * point[10];
}Tree;
//该树一个节点最多有10个子树,那么每个节点的指针域判断次数就是10次
//从point[0]~point[9]

猜你喜欢

转载自blog.csdn.net/Apollon_krj/article/details/79353001
今日推荐