二叉树的层序遍历、之字形打印问题

二叉树的层序遍历

这是一道比较经典的问题,要求逐层遍历二叉树并打印节点,而且也有些变体。其实这不仅是一道考察对树形或图结构了解程度的问题,实现一个这样的函数其实能够帮助我们直观的打印一颗二叉树,在遇到复杂二叉树的问题时如果出现bug,也可以借助这种方式可视化出来调试。这个问题本身也有一些变体问题。
在这里插入图片描述
如上图所示,这是一颗二叉树,我们以他为例讨论。

1· 最直接的层序遍历

简单的层序遍历只要从最上层(根节点)开始逐层打印即可,对于实例,我们需要打印:0, 2, -3, 6, 11, -1, 9
实际上如果我们把这棵树看做一张有向无环图的话,逐层打印实际上就是广度优先遍历的方式。根节点看做起始点,首先遍历离得最近的根节点的子节点,一层一层逐渐向外遍历,这实际上就是广度优先遍历的思路,可以借助队列这个结构实现。
新建一个队列,首先放入根节点,在队列非空时,取出头部节点,打印它的值,将他的子节点放入队列。这样,由于队列先进先出的特点,当下一层节点开始打印时,上一层已经打印完了,因为上一层第一个节点的第一个子节点也是在上一层最后一个节点后进入队列的,保证了层序的顺序。
C++代码如下:

void levelPrintTree(TreeNode *root) {
  if (root == nullptr) return;
  queue<TreeNode*>q;
  q.push(root);
  while (!q.empty()) {
    TreeNode *t = q.front();
    q.pop();
    cout << t->val << " ";
    if (t->left) q.push(t->left);
    if (t->right) q.push(t->right);
  }
  cout << endl;
}

这一问题也比较简单。

2· 分层打印的层序遍历

在前一个问题的基础上,可以稍加扩展,如果我们要求每打印一层就换一行,各层之间区分开,也就是对于实例,我们要打印:
0
2, -3
6, 11, -1
9
要怎么做呢?
其实核心的部分都不变,依然通过队列方式实现,不过可以增加变量用于标记,帮助我们区分什么时候应该换行。
我的思路简要介绍如下,在队列实现层序遍历的基础上,新建两个TreeNode*的变量,分别叫做last和nLast,用来记录当前这一层最后一个节点和下一层的最后节点。那么我们如何知道下一层节点的最后一个是什么呢?很简单,我们让nLast每次都指向队列最后一个元素,当我们打印第k层节点时,k+1层的节点会不断加入到队列尾部,nLast也依次移动,知道当前打印节点是k层最后一个节点(等于last),此时将它的子节点加入队列并调整nLast指向队尾元素,由于k层打印完毕,子节点也加入队列,因此k+1层节点全都加入了队列,此时的nLast就是k+1层最后节点。又因为此时k层打印完了,该打印k+1了,只要让last等于此时的nLast就可以继续打印k+1层了。
C++代码如下:

void levelPrintTreeRows(TreeNode*root) {
  if (root == nullptr) return;
  TreeNode*last = root;
  TreeNode*nLast = root;
  queue<TreeNode*>q;
  q.push(root);
  while (!q.empty()) {
    TreeNode *t = q.front();   
    if (t->left!= nullptr) q.push(t->left);
    if (t->right!= nullptr) q.push(t->right);
    nLast = q.back();
    q.pop();
    cout << t->val;
    if (t == last) {
      cout << endl;
      last = nLast;
    }
    else cout << " ";
  }
}

3· 之字形打印二叉树

之字形打印,是指对每一层节点,先从左到右,再从右到左,交替打印每一层,对于实例,我们要求打印:
0
-3, 2
6, 11, -1
9
那么这样的方式就不同于图的广度优先遍历了,或者说,单纯依靠一个队列就无法完成这一工作。简单来想,我们可以采用可以双向遍历的数组实现,按照层序遍历加入节点,并且记录好前一层最后节点与当前层最后节点,再使用一个bool变量区分方向,不过实现起来有点繁琐。我们可以利用栈结构实现之字形的打印。
考虑使用2个栈,分别存储当前打印的层的节点,和他们的子节点(也就是下一层节点)。
由于栈是先进后出形式,当依次从左到右压入节点(比如即为1,2,3)的时候,会按照3,2,1的顺序从右到左弹出打印,那么再按照弹出顺序加入子节点,第二个栈中就是按照3,2,1的子节点(右到左)压入,再弹出就是从左到右,这就实现了从左到右和从右到左的交替。但是一定要注意,这两种入栈顺序,对于子节点来说,同样要区分先入左子节点还是右子节点。
C++代码如下:

void levelPrintTreeZigzag(TreeNode*root) {
  if (root == nullptr) return;
  stack<TreeNode*>levels[2];
  int cur = 0, next = 1;
  levels[cur].push(root);
  while (!levels[0].empty() || !levels[1].empty()) {
    TreeNode *t = levels[cur].top();
    levels[cur].pop();
    cout << t->val;
    if (cur == 0) {
      if (t->left != nullptr) levels[next].push(t->left);
      if (t->right != nullptr) levels[next].push(t->right);
    }
    else {
      if (t->right != nullptr) levels[next].push(t->right);
      if (t->left != nullptr) levels[next].push(t->left);
    }
    if (levels[cur].empty()) {
      cout << endl;
      cur = 1 - cur;
      next = 1 - next;
    }
    else cout << " ";
  }
}

这里就是才用了两个栈,分别记录入栈从左到右和从右到左的层,要注意打印顺序与入栈顺序相反,需要根据实际调整。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值