二叉树的层次遍历及其变型
2018-02-23 16:06
369 查看
一、二叉树的层次遍历题目分析:
题目简述:给定一棵二叉树的根节点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]
相关文章推荐
- LintCode-剑指Offer-(69)二叉树的层次遍历
- 第十一周项目1(1)二叉树的层次遍历算法
- 第十一周项目1 - 二叉树层次遍历算法的验证
- 二叉树的层次遍历
- 由二叉树的后序和中序求层次遍历
- 【C++】 二叉树的基本知识及其遍历
- 二叉树的层次遍历
- 根据层次遍历和中序遍历的结果还原一颗二叉树
- LeetCode基础--二叉树--层次遍历
- 【数据结构】二叉树的层次遍历
- 二叉树的水平(层次)遍历 -- Python
- 二叉树(一) 先序遍历、中序遍历、后续遍历、层次遍历的递归与非递归实现
- 二叉树的前中后层次遍历(递归+非递归)、创建树(数组、前序+中序、中序加后序)
- 二叉树层次遍历
- LintCode 二叉树的层次遍历
- 数据结构之 二叉树的构造与遍历(先序,中序,后序,层次)
- [置顶] 数据结构之 二叉树的构造与遍历(先序,中序,后序,层次)
- 二叉树层次遍历
- 二叉树的遍历(前序、中序、后序、层次)
- 二叉树的层次遍历