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