二叉树(续) 三种层次遍历算法

前言

   关于二叉树我们在前面已经细致的讨论,具体的二叉树实现请参考前面的链接,这篇博客重点放在二叉树的三种遍历算法上面。


解法一:

   对于二叉树的层次遍历,我们很容易想到,如果能够输出每一层的节点,那么整棵树的层次遍历就只需要不断调用它即可,如下图:

《二叉树(续) 三种层次遍历算法》


层次遍历的结果为:

     12

     5 18

     2 9 15 19

     17

   假设现在要求访问第i层节点,根据二叉树的递归定义特性,我们应该能联想到采用递归的方式解决问题。访问第i层节点,可以转换为访问根的左右子树的第i – 1层节点,访问第i – 1节点转换为访问该两棵子树的左右子树的第i – 2层节点,直到层数为0,此时正是我们要访问的层了。根据这个思路,很容易就可以写出程序:

template <typename T>
bool BSTree<T>::traversalAtLevel(size_t level)const
{//从左到右输出level层的数据
	if(root == NULL) return false;
	if(level == 0)
	{//如果已经递归到当前层,输出
		cout << root->data << ' ';
		return true;
	}
	BSTree<T> LEFT(root->left),RIGHT(root->right);//否则继续向下递归
	bool left_status = LEFT.traversalAtLevel(level - 1),
		right_status = RIGHT.traversalAtLevel(level - 1);
	return left_status || right_status;//返回递归执行的状态
}

    解决了如何输出某一层的节点,那么我们只要直到层数,就可以轻松的利用循环解决层次遍历遍历的问题了。二叉树的层数相当于就是树的深度,同样的,利用递归性质,很容易可以写出如下算法:

template <typename T>
size_t BSTree<T>::height()const
{//求树高
	if (root == NULL) return 0;
	size_t left_height = 0, right_height = 0;
	if (root->left != NULL)
	{
		BSTree<T> LEFT(root->left);
		left_height = LEFT.height();
	}
	if (root->right != NULL)
	{
		BSTree<T> RIGHT(root->right);
		right_height = RIGHT.height();
	}
	return left_height > right_height ? left_height + 1 : right_height + 1;
}

    解决了层数计算和输出某层节点,很容易就可以得到下列层次遍历的算法:

template <typename T>
void BSTree<T>::levelTraversal_h()const
{//二叉树的层次遍历,解法1
	size_t h = height();//先求树高,即层数
	for (size_t i = 0; i <= h; ++i)
	{
		traversalAtLevel(i);//然后在输出每一层的数据
		cout << endl;
	}
}

解法二:

    分析解法一,我们可以得到如下几个问题:

    1、计算树高时,需要访问树的每一层;

    2、输出第i层节点时,需要遍历前面的所有层;

    3、输出i + 1层时,有需要从第0开始迭代直到第i + 1层。

    可以看出,效率比较低,很多有用的信息没有得到应用,那么有没有什么办法可以解决这三个问题呢?可以的,我们可以避免计算树高。

    在我们访问某层的程序中,保存了递归执行左右子树的状态,返回的是两者的或值。因而,如果返回为真,则说明至少存在一棵子树,有必要继续迭代下去;否则为假的话,说明左右子树都不存在,可以终止了。改进后程序如下:

template <typename T>
void BSTree<T>::levelTraversal_b()const
{//层次遍历解法2,不要求先明确层数
	for (size_t level = 0;; ++level)
	{
		if (!traversalAtLevel(level)) break;//根据当前层的返回结果判断,是否已经遍历完
		cout << endl;
	}
	return;
}

解法三:

    解决了上述的问题1,可还有问题2和3呢?继续分析并结合动态规划中的某些思想,我们可以将访问第i层时得到的信息保留下来,那么在进行第i + 1层的访问时,则不需要再次从头开始了,可以大大减少时间复杂度。

    保存上层的信息我们可以采用队列,也可以用数组,在这里我们使用数组解决,并且用两个指针curr和last标示出上层节点的范围,当curr=last时标明上层信息利用完毕(即当前层访问结束),然后将last调整到下一个适当位置继续搜索,直到数组末尾。假设节点编号一次为1,2,3….,从左到右,从上到下编号。


首先,访问第0层,显然只有一个节点,之后将其压入数组(实际压入的是节点地址,我们这里用编号代替),状态如下:

《二叉树(续) 三种层次遍历算法》

由于curr < last,说明此层还有节点没有访问,因而遍历curr到last之间的所有元素,以访问这些节点的左右孩子,直到curr = last。根据上图的二叉树,将压入节点2、3,状态如下:

《二叉树(续) 三种层次遍历算法》

按前所述,继续下去直到呈现如下状态,即访问完毕。

《二叉树(续) 三种层次遍历算法》


根据上述步骤,我们可以的如下程序:

template<typename T>
void BSTree<T>::levelTraversal_q()const
{//层次遍历解法3
	if (root == NULL) return;
	cout << root->data << endl;
	vector<node<T>*> pvec;//利用一个数组(或者队列)存储已访问过的节点
	pvec.push_back(root);
	size_t curr = 0, last = 1;
	while (curr != pvec.size())
	{//只要数组中还存在元素
		size_t num = 0;//记录新添加的元素数
		for (; curr != last;++curr)
		{//遍历当前层的元素
			if (pvec[curr]->left != NULL)
			{//左孩子
				cout << pvec[curr]->left->data << ' ';
				pvec.push_back(pvec[curr]->left);
				++num;
			}
			if (pvec[curr]->right != NULL)
			{//右孩子
				cout << pvec[curr]->right->data << ' ';
				pvec.push_back(pvec[curr]->right);
				++num;
			}
		}
		cout << endl;
		last += num;
	}
}



    原文作者:bravepam
    原文地址: https://blog.csdn.net/zilingxiyue/article/details/45768237
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞