剑指面试题32. 从上到下打印二叉树系列 (算法详细图解和STL队列/双向队列实现)

18 篇文章 0 订阅
13 篇文章 0 订阅

题目1:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

1.1 思路解析

        为了更好地讲解从上到下打印二叉树系列的题目,我们所选的二叉树示例如图1所示。

                                          

                                                      图1: 二叉树示例

        根据题意,我们需要一个一边压入节点一边输出节点的容器,也就是可以选择队列或者双向队列。这里一边只压入,一边只输出,所以队列就可以满足要求。

1.2  基本知识温习

         首先复习一下STL中队列的基本知识,主要有以下:

1:头文件

#include <queue>

2:声明

queue<数据类型> 队列名称;  //队列名称如que

3:基本方法

front()     :返回第一个元素(队顶元素);复杂度为O(1)

back()     :返回最后被压入的元素(队尾元素);复杂度为O(1)

empty()  :当队列为空时,返回true;复杂度为O(1)

size()     :返回队列的长度;复杂度为O(n)

push(x)   :将x压入队列的末端;复杂度为O(1)

pop()      :弹出队列的第一个元素(队顶元素);没有返回值,复杂度为O(1)

 1.3 步骤讲解

         首先不用先着急,在草纸上画一下基本的步骤,缕清思路,再一蹴而就。根据二叉树示例,我们有如下步骤:

                        

                                                                     图2:单向队列算法步骤图

        根据第一层、第二层的处理,是不是已经了然于心了?第一层的压入操作完成后,队列中存的是第一层的所有节点;第一层节点的弹出操作完成后,队列中存的是第二层的全部节点;第二层节点的弹出操作完成后。队列中存的是第三层的全部节点。以此类推,直至某层的没有左右子节点,队列为空,算法结束。

        其中的关键点是:1:弹出时,弹出的次数是当前列队的长度,也即是当前层的所有节点。2:队列中是节点,而不是val,因为节点保存着其左右子节点信息,能够由节点找到其下一层节点。

1.4 算法代码

class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        vector<int> res;
        if (root == NULL)
            return res;
        
        queue<TreeNode*> que;
        que.push(root);
        TreeNode* node;
        while (!que.empty()) {
            node = que.front();
            que.pop();
            res.push_back(node->val);

            if (node->left)
                que.push(node->left);
            if (node->right)
                que.push(node->right);
        }
 
        return res;
    }
};

题目2:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

2.1 思路解析

       根据题目1的基础,只需要做一步,就是每层的数据单独存放到一个容器(vector)中。而哪里又是每一层的分界点呢。对了,就是每一层开始要处理的时候,队列的长度就是该层节点的总个数,一个计数问题即可完成分层的工作。

2.2 算法代码

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root == NULL)
            return res;

        queue<TreeNode*> que;
        que.push(root);
        TreeNode* node;
        while (!que.empty()) {   //比que.size()快很多
            vector<int> tmp;
            int num = que.size();
            while (num--) {
                node = que.front();
                que.pop();
                tmp.push_back(node->val);

                if (node->left)
                    que.push(node->left);
                if (node->right)
                    que.push(node->right);
            }
            res.push_back(tmp);
        }

        return res;
    }
};

题目3:按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

3.1 思路解析

       基于题目2的思路,做一点改动即可,就是对行做奇偶判断,若是偶数层,则翻转这一行的结果即可。这种方法简单易于理解,缺点就是翻转数组的复杂度是O(n)。还有一种思路是使用双向队列,因为单向队列明显不够用了,可以用单向队列模拟一下,尽管改变压入队列的左右子树顺序,到第三层就会出现问题。

3.2 基本知识温习

        首先复习一下STL中双向队列的基本知识,主要有以下:

1:头文件

#include <deque> 

2:声明

deque<数据类型> 双向队列名称;  //队列名称如dq

3:基本方法

front()     :返回第一个元素(队顶元素);复杂度为O(1)

back()     :返回最后被压入的元素(队尾元素);复杂度为O(1)

empty()  :当队列为空时,返回true;复杂度为O(1)

size()     :返回队列的长度;复杂度为O(n)

push_front(x)   :将x压入队列的头部;复杂度为O(1)

push_back(x)   :将x压入队列的尾部;复杂度为O(1)

pop_front()      :弹出队列的第一个元素(队顶元素);没有返回值,复杂度为O(1)

pop_back()      :弹出队列的最后一个元素(队尾元素);没有返回值,复杂度为O(1)

3.3  步骤讲解

        为了更好地理解算法,本文约定:若是从左向右打印,就从队列右边弹出,若从右向左打印,就从左边弹出。有一个细节容易忽略:若从某个方向在弹出操作,那这个方向的节点未弹出完成之前,这个方向不能有压入操作,只能从另一个端压入。对于列队,边为front,右边为back。前三步如图所示:

                          

                                                                  图3:双向队列算法步骤图

         其中有一个难点,就是压入左右子节点的顺序。例如第二步从右向左打印,在双向队列右边弹出3时,在左边以什么顺序压入3的左右子节点呢?顺着走思路好像有点混乱了,那逆推一下,若想下一步从左边先弹出8(3的右子节点),再弹出5(3的左子节点),是不是得先从左边压入5再压入8,即从左边先压入左子节点再压入右子节点。第三步从左向右打印,从左边弹出8后,以什么顺序压入8的左右子节点呢?还是逆推,对于8的子节点,若想下一步从右边边弹出先弹出9再弹出2,则2(8的右子节点)应该先从右边压入,再压入9(8的左子节点),即从右边先压入右子节点再压入左子节点。对于5亦是如此。至此一个“之”字分析解析完,规律也总结完了。

3.4 算法代码

    双端队列,右端出则左边进,左端出则右端进。同时考虑下一次哪段出,由于每次都换端弹出,则元素先进后出。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        if (NULL == root)
            return vector<vector<int>>();
        
        vector<vector<int>> res;
        bool isRight = true;
        deque<TreeNode*> que;
        que.push_back(root);
        while (!que.empty()) {
            int num = que.size();
            vector<int> tmp;
            while (num--) {
                if (isRight) {
                    TreeNode *node = que.back();
                    que.pop_back();
                    tmp.push_back(node->val);
                
                    if (node->left)
                        que.push_front(node->left);
                    if (node->right)
                        que.push_front(node->right);
                } else {
                    TreeNode *node = que.front();
                    que.pop_front();
                    tmp.push_back(node->val);

                    if (node->right)
                        que.push_back(node->right);
                    if (node->left)
                        que.push_back(node->left);
                }
            }
            isRight = !isRight;
            res.push_back(tmp);
        }

        return res;
    }
};

 107. 二叉树的层次遍历 II

题目:

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

解法1:借助从上向下打印二叉树的解法,反转结果数组

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
        if (NULL == root)
            return res;

        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            vector<int> tmp;
            int len = que.size();
            while (len--) {
                TreeNode* node = que.front();
                que.pop();
                tmp.push_back(node->val);
                if (node->left)
                    que.push(node->left);
                if (node->right)
                    que.push(node->right);    
            }
            res.push_back(tmp);
        }
        reverse(res.begin(), res.end());

        return res;
    }
};

解法2:用栈压入每层节点数组

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
        if (NULL == root)
            return res;

        queue<TreeNode*> que;
        stack<vector<int>> sta;
        que.push(root);
        while (!que.empty()) {
            vector<int> tmp;
            int len = que.size();
            while (len--) {
                TreeNode* node = que.front();
                que.pop();
                tmp.push_back(node->val);
                if (node->left)
                    que.push(node->left);
                if (node->right)
                    que.push(node->right);    
            }
            sta.push(tmp);
        }
        while (!sta.empty()) {
            res.push_back(sta.top());
            sta.pop();
        }

        return res;
    }
};

附录:补充下vector的基本知识

1:头文件

#include<vector>

2:声明

vector<数据类型> 动态数组名称;  //动态数组名称如vec

3:基本方法

capacity() :返回动态数组的容量

size()       :返回动态数组实际长度

empty()   :是否为空时,返回true

front()     :返回动态数组第一个元素;复杂度为O(1)

back()    :返回动态数组最后一个元素;复杂度为O(1)

push_back(x)   :将x压入动态数组的尾部;复杂度为O(1)

pop_back()      :弹出动态数组最后一个元素;没有返回值,复杂度为O(1)

4:迭代器

begin()  :指向第一个元素位置

end()    :指向最后一个元素的下一个位置

cbegin():指向第一个元素位置,通过指针只读元素,不能对元素做修改

cend()  :指向最后一个元素的下一个位置

5:方法

#include <algorithm>

reverse(vec.begin(), vec.end()) :翻转动态链表

sort(vec.begin(), vec.end())  :默认是从小到大的排序

//如果想从大到小排序,有两种方法:

5.1:自写比较函数:

bool comp(const int& x, const int& y) {

return x > y;

}

sort(vec.begin(), vec.end(), comp);

5.2:用lamda

sort(vec.begin(),vec.end(),[](const int& x, const int& y){return x>y;});

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值