问题1: 给定一棵二叉树,要求分层遍历该二叉树,即从上到下按层次访问该二叉树(每一层将单独输出一行),每一层要求访问的顺序为从左到右,并将节点一次编号。那么遍历如下图所示的二叉树,正确输出应为:
1 1
2 3 / \
4 5 6 2 3
7 8 / \ \
4 5 6
/ \
7 8
问题2:写另外一个函数,打印二叉树中的某一层次的节点(从左到右),其中根节点为第0层,函数原型为int PrintNodeAtLevel(Node* root, int level),成功返回1,失败则返回0。
定义二叉树的数据结构为:
struct Tree {
int value;
Tree *left;
Tree *right;
};
解法一:关于二叉树的遍历,根据其本身固有的递归特性,可以用递归来解决。
问题2里要求打印某一层的节点,例如打印二叉树中第K层的节点,可以理解为打印以该二叉树根节点的左右子节点为根节点的两棵二叉树的第K-1层的节点。
如图,要打印第 k = 2层的节点4、5、6,相当于打印以2、3为根节点的两棵二叉树的第k – 1 = 1层的节点。
基于以上分析,问题2的实现代码为:
// 分层遍历二叉树
int printTreeAtLevel(Tree *root, int level) {
if(root == NULL || level < 0) {
return 0;
}
if (level == 0) {
cout << root->value << " ";
return 1;
} else {
return printTreeAtLevel(root->left, level - 1) + printTreeAtLevel(root->right, level - 1);
}
}
如果已知树的深度,问题1得以解决,实现代码如下:
// 层次遍历二叉树
void printTreeByLevel(Tree *root, int depth) {
for(int level = 0; level < depth; level ++) {
printTreeAtLevel(root, level);
cout << endl;
}
}
如果不知道树的 深度,对于问题1,仍然调用
int printTreeAtLevel(Tree *root, int level)。假设有N层[0…N-1],先从第0层开始打印,然后第2层,…,循环直到第N-1层,当要打印第N层是,调用
printTreeAtLevel(root, N),在执行递归的过程中,函数返回0。故,可以根据函数是否返回0作为循环结束的条件。
如果树的深度未知,问题1实现代码如下:
// 层次遍历二叉树,未知深度
void printTreeByLevel2(Tree *root){
for(int level = 0; ; level ++ ) {
if(!printTreeAtLevel(root, level)) {
break;
}
cout << endl;
}
}
解法二:在解法1中,对于问题1,分层遍历二叉树,每遍历一层都要从根节点开始访问,直到访问完所有的层次。这样以来,效率就降下来。下面来讨论一个效率更优的实现。
访问第K层,如果已知第K-1层,那么第K层也就可以获得了,也就不用从根节点开始遍历了。如何获取第K层的节点?可以借助一个辅助空间来存储已经遍历过的节点。
从根节点出发,依次将每层的节点从左到右压入一个数组,同时用一个游标cur记录当前访问的节点,另一个游标last指示当前层次的最后一个节点的下一个位置,当cur==last,当前层访问结束。
在访问某一层时同时将该层所有节点的子节点压入数组;在某一层访问结束后,检查是否有新的层次可以访问,知道访问完所有的层次。
例如(对于题目中给出的二叉树):
首先将1压入数组,并将cur置为0,last置为1;
cur<last,说明此层(第0层)尚未被访问,因此依次访问cur到last之间的所有节点,并依次判断被访问节点是否有孩子节点,如果有,则依次将左右子节点压入数组(注意左右子节点压入数组的顺序),那么当访问完第一层后,游标和数组的状态如下:
由于cur==last,说明该层已经访问完了,此时数组中因为有该层子节点的压入,数组中还有未访问的元素,则输出换行符,为输出新一行做准备,并将last定于为当前一行的末尾,也就是数组当前最后一个元素的下一个位置,游标和数组的状态如下:
继续依次向后访问其他层的节点,知道访问完所有层次:
void printTreeByLevel3(Tree *root) {
if(root == NULL) {
return;
}
vector<Tree *> vec; // 动态扩展的vector存放各节点
vec.push_back(root); // 首先root压入vector
int cur = 0;
int last = 1;
while(cur < vec.size())
{
last = vec.size(); // 新一行访问开始,重新定位last于当前行最后一个节点的下一个位置
while(cur < last) {
cout << vec[cur]->value << " "; // 访问当前节点值
if(vec[cur]->left != NULL) { // 当前节点的左孩子不为空,左孩子压入vector
vec.push_back(vec[cur]->left);
}
if(vec[cur]->right != NULL) { // 当前节点的右孩子不为空,右孩子压入vector
// (注意,左右孩子压入vector的顺序很重要)
vec.push_back(vec[cur]->right);
}
cur ++;
}
cout << endl; //cur==last, 说明该层访问结束,换行
}
}
扩展题目:
1、如果要求按深度从下到上访问二叉树,每层的访问顺序仍然是从左到右,即访问顺序变为:
7 8
4 5 6
2 3
1
如何改进算法?
void printTreeByLevel_B2T_L2R(Tree *root) {
if(root == NULL) {
return;
}
vector<Tree *> vec;
vec.push_back(root);
int cur = 0;
int last = 1;
while(cur < vec.size()) {
int last = vec.size();
vec.push_back(NULL); // 插入一个NULL,标识换行
while(cur < last) {
if(vec[cur]->right != NULL) {
vec.push_back(vec[cur]->right);
}
if(vec[cur]->left != NULL) {
vec.push_back(vec[cur]->left);
}
cur ++;
}
cur ++; // 因为加入一个NULL,cur要再+1才能访问到数据
}
vector<Tree *>::reverse_iterator iter = vec.rbegin();
for(; iter != vec.rend(); iter ++) {
if(!*iter) cout << endl;
else cout << (*iter)->value << "\t";
}
cout << endl;
}
2、如果按深度从下到上访问,每层的访问顺序变为从右到左,即访问顺序变为:
8 7
6 5 4
3 2
1
如何改进算法?
void printTreeByLevel_B2T_R2L(Tree *root) {
if(root == NULL) {
return;
}
vector<Tree *> vec;
vec.push_back(root);
int cur = 0;
int last = 1;
while(cur < vec.size()) {
int last = vec.size();
vec.push_back(NULL); // 插入一个NULL,标识换行
while(cur < last) {
if(vec[cur]->left != NULL) {
vec.push_back(vec[cur]->left);
}
if(vec[cur]->right != NULL) {
vec.push_back(vec[cur]->right);
}
cur ++;
}
cur ++; // 因为加入一个NULL,cur要再+1才能访问到数据
}
vector<Tree *>::reverse_iterator iter = vec.rbegin();
for(; iter != vec.rend(); iter ++) {
if(!*iter) cout << endl;
else cout << (*iter)->value << "\t";
}
cout << endl;
}
扩展题代码整理:
// 如果要求按照深度从下到上访问二叉树,每层的访问顺序从左到右(从右到左)
void printTreeByLevel4(Tree *root, bool flag)
{
if(root == NULL)
{
return;
}
vector<Tree *> vec; // 动态扩展的vector存放各节点
vec.push_back(root); // 首先root压入vector
int cur = 0;
int last = 1;
while(cur < vec.size())
{
last = vec.size(); // 新一行访问开始,重新定位last于当前行最后一个节点的下一个位置
vec.push_back(NULL);
int p =cur;
while(cur < last)
{
if(flag) // 每层的访问顺序从左到右
{
if(vec[cur]->right != NULL) // 当前节点的右孩子不为空,右孩子压入vector
{
vec.push_back(vec[cur]->right);
}
if(vec[cur]->left != NULL) // 当前节点的左孩子不为空,左孩子压入vector
{ // (注意,左右孩子压入vector的顺序很重要)
vec.push_back(vec[cur]->left);
}
}
else // 每层的访问顺序从右到左
{
if(vec[cur]->left != NULL) // 当前节点的左孩子不为空,左孩子压入vector
{
vec.push_back(vec[cur]->left);
}
if(vec[cur]->right != NULL) // 当前节点的右孩子不为空,右孩子压入vector
{ // (注意,左右孩子压入vector的顺序很重要)
vec.push_back(vec[cur]->right);
}
}
cur ++;
}
cur ++;
cout << endl; //cur==last, 说明该层访问结束,换行
}
// 从下到上,访问每一层
for(vector<Tree *>::reverse_iterator i = vec.rbegin(); i != vec.rend(); i ++)
{
if(!*i) cout << endl;
else cout << (*i)->value << " ";
}
cout << endl;
}