[编程之美] PSet3.10 分层遍历二叉树

问题描述:

        1.给定一棵二叉树,要求分层遍历二叉树(从上到下按层次访问二叉树,每一层单独输出一行),访问顺序为由左至右。

        2.打印二叉树某层次节点(从左至右),其中根节点为第0层,函数原型为int PrintNodeAtLevel(Node *root , int level),成功返回1,失败返回0

思路与解答:

         二叉树由于本身结构的递归性,常常用递归来解决。这两个问题其实是一个问题,只要解决了问题二,然后按不同层遍历一遍就可以得到问题一的结果。

         那么为什么不直接用类似于先序、中序、后序遍历的递归算法直接求解问题一呢?这是因为层序遍历一棵树本身不能直接由递归算法解出,可以用反证法证明。如果能实现对 A 节点的层序递归,在对 A 节点处理的过程中,应该递归的对两个儿子 B 和 C 分别调用了层序遍历。在这种情况下,我们无法让 B 和 C 的同一个层级的儿子在集中的时间中被遍历到,换言之,B 的第一层儿子在对 B 的调用中被遍历,而 C 的第一层儿子,则在对 C 的调用中遍历,这是分离开的。不成立,得证。

//方法:输出从根节点开始算起的第k层相当于输出从根节点左右孩子算起的k-1层
void printNodeAtLevel(Node *root , int level)
{
	if(root == NULL || level<0)
		return ;//失败
	if(level == 0)//叶子节点
		cout<<root->data<<" ";
	if(root->pLChild)
		printNodeAtLevel(root->pLChild , level-1);
	if(root->pRChild)
		printNodeAtLevel(root->pRChild,level-1);
}
        下面如果知道该二叉树的深度n,可以直接经过n次调用得到结果:
void printNodeByLevel(Node *root , int nDepth)
{
	for(int i=0 ; i<nDepth ; i++){
		printNodeAtLevel(root , i);
		cout<<endl;
	}
}
       如果事先不知道二叉树的深度,依旧可以用递归的思想求二叉树的深度,如下:
int Max(int a , int b)
{
	return a>b?a:b;
}
int calcTreeDepth(Node *root)
{
	if(!root)
		return -1;//依本例定义,根节点为第0层,空树的深度为-1
	int maxDepth = Max(calcTreeDepth(root->pLChild) , calcTreeDepth(root->pRChild));
	return maxDepth+1;
}
      但是求解深度需要遍历一次二叉树,与问题二岂不是同等复杂度的问题?能不能不求深度呢?可以,只需要访问二叉树某层次失败时候返回就行了。下面是代码:
int printNodeAtLevel(Node *root , int level)
{
	if(!root || level<0)
		return 0;//未打印节点
	if( level == 0){
		cout<<root->data<<" ";
		return 1;//打印一个节点
	}
	/*
	加上if语句和不加if语句:
	加上if语句,不会递归到叶子的左右孩子,仅仅判断左右孩子如果都为空就返回了,搭配递归截止条件为叶子处返回。
	不加if语句,将会递归到叶子的左右孩子,此时需要在递归截止条件进行判定是否为NULL进行判断或赋值。
	*/
	//if(root->pLChild)
	//if(root->pRChild)仅当所有该层遍历结果为0时才返回0
	return printNodeAtLevel(root->pRChild,level-1)+printNodeAtLevel(root->pLChild,level-1);
}
void printNodeByLevel(Node *root )
{
	for(int i=0 ;  ; i++){
		if(printNodeAtLevel(root , i) == 0)//整层都未打印节点,返回
			break;
		cout<<endl;
	}
}
        事实上这种方法的弊端在于每一层的访问都需要重新从根节点开始,而我们在访问第k层的时候,只需要知道它的前面一层(k-1)层的节点信息就够了。依照这种思路,考虑设定一个数组来存放每一层的信息,一旦遍历完该层,就开始遍历下一层,而下一层的信息可以通过压入当前层(k-1层)左右孩子节点(k层)来构造,而不要重新从根节点开始。下面是代码:
void printNodeByLevel(Node *root)
{
	vector<Node*> vec;
	int cur , last;//设定该层的起始点和结束点
	vec.push_back(root);
	cur = 0;last = vec.size();
	while(cur<vec.size()){//循环截止条件:没有更多层了
		last = vec.size();
		while(cur<last){//遍历一层
			cout<<vec[cur]->data<<" ";
			if(vec[cur]->pLChild)//有左孩子
				vec.push_back(vec[cur]->pLChild);
			if(vec[cur]->pRChild)//有右孩子
				vec.push_back(vec[cur]->pRChild);
			cur++;
		}
		cout<<endl;//该层访问结束,输出换行
	}
}
扩展问题:
       1.如果要求深度从下到上访问二叉树,每层的顺序仍然是从左到右,应该如何改进算法?(提示:可考虑左右节点的访问顺序)
       答:比较容易想到的方法是先按照层序弄好一个待遍历数组,层与层之间考虑用一个标识NULL作为分割,这样只需要反向遍历这个数组就行了,不过要注意的是弄这个遍历数组一定要先压入右节点,再压入左节点。 代码如下:
void printNodeByLevel(Node *root)
{
	vector<Node*> vec;
	int cur , last;//设定该层的起始点和结束点
	vec.push_back(root);
	cur = 0;last = vec.size();
	while(cur<vec.size()){//循环截止条件:没有更多层了
		last = vec.size();
		vec.push_back(NULL);//作为层间分割标识
		while(cur<last){//遍历一层
			if(vec[cur]->pRChild)//有右孩子
				vec.push_back(vec[cur]->pRChild);
			if(vec[cur]->pLChild)//有左孩子
				vec.push_back(vec[cur]->pLChild);
			cur++;
		}
		cur++;//跳过NULL标志位
	}
	vec.pop_back();//去掉最后一个NULL
	//---逆序遍历数组
	for(int i=vec.size()-1 ; i>=0 ; i--){
		if(vec[i] == NULL){//碰到层间分割
			cout<<endl;
			continue;
		}
		cout<<vec[i]->data<<" ";
	}
	cout<<endl;
}
          2.如果把问题一中的左右次序颠倒,改为从右到左,应该怎么改进?
          答:只需要稍加修改,将“先压入右孩子再压入左孩子”的次序改为“先压入左孩子再压入右孩子”,其他均不变。
          3.[百度面试题] 要求输出二叉树第m层第k个节点值(m,k均从0开始计数)
          答:思路与本例非常相似,也是考虑第m层节点相当于考虑从根孩子节点出发的第m-1层节点,然后进行引用计数或使用全局变量。代码如下:
//寻找从根节点开始第m层的第k个节点也就是寻找从根节点的孩子节点开始的第m-1层第k个节点
int findKNode(Node *root , int m , int k , int &count)//找到root为根的子树第m层第k个节点值
{
	if(m<0 || !root)
		return 0;
	if(m==0){//找到第m层
		if(k == count){//找到第k个数
			cout<<root->data<<endl;
			return 1;
		}
		count++;//如果count是指针传入,注意*count++是count指针++后再解引用,正确做法是(*count)++
		return 0;//未找到第k个数
	}
	if(findKNode(root->pLChild,m-1,k,count) || findKNode(root->pRChild,m-1,k,count))
		return 1;
	return 0;
}
int nCount = 0;
int findKNode(Node *root , int m , int k )//找到root为根的子树第m层第k个节点值
{
	if(m<0 || !root)
		return 0;
	if(m==0){//找到第m层
		if(k == nCount){//找到第k个数
			cout<<root->data<<endl;
			return 1;
		}
		nCount++;
		return 0;//未找到第k个数
	}
	if(findKNode(root->pLChild,m-1,k) || findKNode(root->pRChild,m-1,k))//左子树和右子树的第m层是否找到第k个数
		return 1;
	return 0;
}








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值