高级数据结构 | 二叉树层次遍历 —从左至右的顺序层次遍历 & 左右交替的“之“字型遍历 ...

对于一个二叉树而言,通常有两种遍历方式,一种是深度优先遍历(Depth First Search),一种是广度优先搜索(Breadth First Search)。


在前文中提到的,二叉树的先序遍历、中序遍历、后序遍历都属于深度优先遍历,而我们今天所讲的层次遍历就属于广度优先遍历。

graph TD
    C((A)) --- c((B))
    C((A)) --- 1c((G))
    c((B)) --- .c((C))
    c((B)) --- .C((D))
   .C((D)) --- 1C((E))
   .C((D)) --- 2C((F))
   1c((G)) -.- 0c((.))
   1c((G)) --- 2c((H))
   end

  • 对于以上二叉树我们使用层次遍历可得到序列:{A, B,G, C,D,H, E,F}
  • 而我们所说的“之”字型层序遍历可得到序列:{A, G,B, C,D,H, F,E}
1、顺序层次遍历

对于顺序的层次遍历,我们可以利用队列实现。
在这里插入图片描述
如图所示,A在出队时,将A的左右孩子入队到队尾。 B在出队时,将B的左右孩子入队到队尾。 G在出队时将G的左右孩子入队到队尾。 … 直到队列为空,遍历完整个二叉树。

我们模拟这种方式,使用队列进行二叉树的层次结构的遍历。

void LeveOrder(struct BtNode* p)
{
	if (NULL == p) return;
	std::queue<struct BtNode*> que;
	que.push(p);
	while (!que.empty())
	{
		struct BtNode* pCur = que.front(); que.pop();
		std::cout << pCur->data << " ";
		if (pCur->leftchild != NULL)
		{
			que.push(pCur->leftchild);
		}
		if (pCur->rightchild != NULL)
		{
			que.push(pCur->rightchild);
		}
	}
	std::cout << std::endl;
}
2、左右交替的层序遍历

除了我们的顺序层次结构遍历之外,还有一种“之”字型的左右交替遍历。
在这里插入图片描述
从上图可以看出

  • 第一层A结点处引入两个子节点B、G 。
  • 第二层按照自右向左的顺序遍历G、B结点,引入H、D、C。
  • 第三层按照的自左向右顺序遍历C、D、H结点,同时引入E、F结点。
  • 最后一层,按照自右向左的顺序遍历F、E结点。

思路

  1. 我们发现出队的顺序是左右交替进行的。比如奇数层是自左向右出队,偶数层自右向左出队。因此我们可以使用双端队列进行遍历操作,只需控制每一层队列的出队方向即可。
  2. 我们发现在一、三等层,遍历规则与顺序层次遍历相同,而第二、四层只是从队头出队的规则临时变成队尾出队 。队尾出对——我们可以联想到栈这种数据结构,那么,我们考虑是否可以把那些从队尾出的特殊层储存在栈中,这样我们使用两个不同的数据结构也可以完成遍历操作。
  3. 使用两个栈。俗话说“栈在手,天下我有”,把两个栈的栈底合并就是一个低配版的双端队列(中端受限的双端队列,在队列两段数据不共享),不过对于此题刚好可以完美解决。
2.1 双端队列实现

我们人为的在队列中加入一个标志位,用以判断一层的结束和另一层的开始。
在这里插入图片描述

void Z_LeveOrder_duque(struct BtNode* p)
{
	if (NULL == p) return;
	std::deque<struct BtNode*> deq;
	deq.push_front(p);
	deq.push_back(NULL);	// 标志位,划分不同层
	while (!deq.empty())
	{
		struct BtNode* pCur = NULL; 
		if (deq.front() == deq.back()) break;	// 队头 == 队尾 == NULL ,所有元素已经遍历完
		if(deq.back() == NULL)
		{					
			while (deq.front() != NULL)
			{			
				pCur = deq.front();						
				deq.pop_front();	
				std::cout << pCur->data << " ";
				if (pCur->leftchild != NULL)
				{
					deq.push_back(pCur->leftchild);
				}
				if (pCur->rightchild != NULL)
				{
					deq.push_back(pCur->rightchild);
				}
			}

		}
		if (deq.front() == NULL)
		{					
			while (deq.back() != NULL)
			{
				pCur = deq.back();						
				deq.pop_back();	
				std::cout << pCur->data << " ";
				if (pCur->rightchild != NULL)
				{
					deq.push_front(pCur->rightchild);	// 头删尾插
				}
				if (pCur->leftchild != NULL)
				{
					deq.push_front(pCur->leftchild);
				}
			}
		}
	}
	std::cout << std::endl;
}
2.2 两个栈实现

此方法与双端队列实现方法相同,这里就不再赘述。

void Z_LeveOrder_stack(struct BtNode* p)
{
	if (NULL == p) return;
	std::stack<struct BtNode*> st1;
	std::stack<struct BtNode*> st2;
	st1.push(p);
	while (!st1.empty() || !st2.empty())
	{
		while (!st1.empty())
		{
			struct BtNode* pCur = st1.top();
			st1.pop();
			std::cout << pCur->data << " ";
			if (pCur->leftchild != NULL)
			{
				st2.push(pCur->leftchild);
			}
			if (pCur->rightchild != NULL)
			{
				st2.push(pCur->rightchild);
			}
		}
		while (!st2.empty())
		{
			struct BtNode* pCur = st2.top();
			st2.pop();
			std::cout << pCur->data << " ";
			if (pCur->rightchild != NULL)
			{
				st1.push(pCur->rightchild);
			}
			if (pCur->leftchild != NULL)
			{
				st1.push(pCur->leftchild);
			}
		}
	}

	std::cout << std::endl;
}
2.3 栈与队列实现

这里需要注意的是,栈与队列实现,必须让根结点入栈,然后不论是队列或是栈都以先右孩子后左孩子的顺序存储。栈负责正向从左到右访问,队列负责反向从右向左访问。

void Z_LeveOrder_StackAndQueue(struct BtNode* p)
{
	if (NULL == p) return;
	std::stack<struct BtNode*> st;
	std::queue<struct BtNode*> que;
	st.push(p);		// 入栈
	while (!st.empty() || !que.empty())
	{
		while (!st.empty())
		{
			p = st.top(); st.pop();
			std::cout << p->data << " ";
			if (p->rightchild != NULL)
			{
				que.push(p->rightchild);
			}
			if (p->leftchild != NULL)
			{
				que.push(p->leftchild);
			}
		}
		while (!que.empty())
		{
			p = que.front(); que.pop();
			std::cout << p->data << " ";
			if (p->rightchild != NULL)
			{
				st.push(p->rightchild);
			}
			if (p->leftchild != NULL)
			{
				st.push(p->leftchild);
			}
		}

	}
	std::cout << std::endl;
}
3、层序遍历 且 按层存储在二维数组中

此题在LeetCode上《剑指 Offer 32 - II. 从上到下打印二叉树 II》 ,是层次遍历的另一种形式。

根据先前分析的使用队列进行层序遍历,这里只需要根据队列中剩余元素个数,判断“换层”的时机即可。

如图所示为二叉树层序过程。
在这里插入图片描述
当我们每次入队后都记录队列中有多少个元素,即该层有多少个元素,那么上图的步骤可抽象成下图。
在这里插入图片描述

代码如下:

   vector<vector<int>> levelOrder(TreeNode* root) {
       if(root == nullptr) return {};
       vector<vector<int>> ans;

       queue<TreeNode*> que;       // 辅助队列
       vector<int> tmp;            // 保存每一层的元素
       que.push(root);
       while(!que.empty())
       {
           int cnt = que.size();       // 当前层的结点数目
           while(cnt > 0)
           {
               TreeNode *p = que.front(); que.pop();
               tmp.push_back(p->val);
               if(p->left != nullptr){
                   que.push(p->left);
               }
               if(p->right != nullptr){
                   que.push(p->right);
               }
               cnt--;      // 消耗了本层一个结点
           }
           ans.push_back(tmp);
           tmp.clear();
       }
       return ans;
   }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值