算法与数据结构-2

C++软件与嵌入式软件面经解析大全(蒋豆芽的秋招打怪之旅)


本章讲解知识点

  • 1.1 算法的时间复杂度和空间复杂度
  • 1.2 数组
  • 1.3 链表
  • 1.4 递归
  • 1.5 栈与队列
  • 1.6 树与二叉树
  • 1.7 二叉堆与最小堆
  • 1.8 哈希表
  • 1.9 红黑树
  • 1.10 Trie(前缀树)
  • 1.11 排序算法
  • 1.12 查找算法
  • 1.13 算法思想——DFS和BFS
  • 1.14 算法思想——回溯
  • 1.15 算法思想——分治法
  • 1.16 算法思想——贪心法
  • 1.17 算法思想——动态规划

受众:本教程适合于C/C++已经入门的学生或人士,有一定的编程基础。

本教程适合于互联网嵌入式软件求职的学生或人士。

img

故事背景

img

蒋 豆 芽:小名豆芽,芳龄十八,蜀中人氏。卑微小硕一枚,科研领域苟延残喘,研究的是如何炒好一盘豆芽。与大多数人一样,学习道路永无止境,间歇性踌躇满志,持续性混吃等死。会点编程,对了,是面对对象的那种。不知不觉研二到找工作的时候了,同时还在忙论文,豆芽都秃了,不过豆芽也没头发啊。

隔壁老李:大名老李,蒋豆芽的好朋友,技术高手,代码女神。给了蒋豆芽不少的人生指导意见。

导 师:蒋豆芽的老板,研究的课题是每天对豆芽嘘寒问暖。

img

故事引入

img

隔壁老李:豆芽啊,你刷过一段时间的编程题了,感觉什么数据结构非常容易考啊?

蒋 豆 芽:经过我深思熟虑后,我发现,二叉树这个数据结构十分容易考。

隔壁老李:是的,你说得没错,二叉树可以说是面试官最喜欢考的了,数组链表太简单,哈希、红黑树又太难,而二叉树刚刚好,几十行代码就可以实现一个简单的二叉树,同时二叉树又能很好的考察一个考生的功底。

蒋 豆 芽:所以今天就要讲树了,对吧。

img

1.6 树与二叉树

img

隔壁老李:没错,树是很重要的数据结构,我们生活中有很多场景,比如,豆芽你家的族谱,是一棵树,企业的上下级关系,是一棵树。我们依据《漫画算法》一书,对树进行一个总结。

好了,豆芽,你来总结一下树的概念吧。

蒋 豆 芽:没问题,一棵树分为根节点、子树、叶子节点,如图:

img

节点还分为父节点和子节点,如图:

img

隔壁老李:是的,豆芽,你再来总结二叉树

蒋 豆 芽:好嘞!二叉树( binary tree) 是树的一种特殊形式。 二叉, 顾名思义, 这种树的每个节点最多有2个孩子节点。 注意, 这里是最多有2个, 也可能只有1个, 或者没有孩子节点。

二叉树还有两种特殊形式, 一个叫作满二叉树, 另一个叫作完全二叉树

一个二叉树的所有非叶子节点都存在左右孩子, 并且所有叶子节点都在同一层级上, 那么这个树就是满二叉树

img

完全二叉树

对一个有n个节点的二叉树, 按层级顺序编号, 则所有节点的编号为从1到n。 如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同, 则这个二叉树为完全二叉树。

img

完全二叉树的条件没有满二叉树那么苛刻: 满二叉树要求所有分支都是满的; 而完全二叉树只需保证最后一个节点之前的节点都齐全即可

隔壁老李:说得很好,我们来实现一棵二叉树吧。

蒋 豆 芽:简单!

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

一棵树由节点构成,节点包含左、右指针分别指向左、右子节点。是不是很简单。

隔壁老李:好了,来说说树的遍历吧。树的遍历就是为了访问树的节点元素。

蒋 豆 芽:树的遍历有四种,前序遍历、中序遍历、后序遍历、层序遍历。

前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。简单记就是根-->左-->右。如图:

img

我们来分析一下:口诀很好记:根-->左-->右F是根,那就输出F,接下来进入节点F的左边。在左子树中,B是左子树的根,那就输出B;那又进入左边,左边只有A最后一个节点了,输出A。然后返回节点B,根-->左都输出了,就轮到右了,进入节点B的右边,节点C、D、E构成一棵子树,节点D是根,按照根-->左-->右的口诀,按顺序输出D、C、E。

接下来都是按照这个思路,直到遍历完所有节点。在遍历的过程中,遇到空节点就返回上一层节点。

我们再通过动图直观感受下:

img

我们给出代码:

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) { // 将整个树拆分成一个个的子树,针对每个子树进行根-->左-->右遍历
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 根
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)

中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。简单记就是左-->根-->右。如图:

img

我们来分析一下:口诀很好记:左-->根-->右F是根,要找到左节点,接下来进入节点F的左边。在左子树中,B是左子树的根,还不够,我们要一路向左;那又进入左边,左边只有A最后一个节点了,终于找到了,输出A。然后返回节点B,B是根,输出B。左-->根都输出了,就轮到右了,进入节点B的右边,节点C、D、E构成一棵子树,节点D是根,按照左-->根-->右的口诀,按顺序输出C、D、E。

接下来都是按照这个思路,直到遍历完所有节点。在遍历的过程中,遇到空节点就返回上一层节点。

我们再通过动图直观感受下:

img

我们给出代码:

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {// 将整个树拆分成一个个的子树,针对每个子树进行左-->根-->右遍历
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 根
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)

后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。简单记就是左-->右-->根。如图:

img

我们来分析一下:口诀很好记:左-->右-->根。同样的思路,我们就不再赘述了。在遍历的过程中,遇到空节点就返回上一层节点。

我们再通过动图直观感受下:

img

我们给出代码:

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {// 将整个树拆分成一个个的子树,针对每个子树进行左-->右-->根遍历
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
        vec.push_back(cur->val);    // 根
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)

其实利用递归遍历树很简单,我们还可以利用来实现树的遍历。如下,我们用栈来实现前序遍历。

img

我们来分析一下:前序遍历,口诀很好记:根-->左-->右。所以根先入栈,从节点F开始,F为根,入栈并输出F。进入节点F的左边,在左子树中,节点B为根节点,入栈并输出B,然后向左。A节点与两个空节点构成一棵子树,A为根,入栈并输出A。就访问完了,这时A就要出栈。

根-->左访问完了,开始访问右,怎么到达右边呢?别忘了,我们栈顶保存了B节点,通过获取栈顶元素,也就可以访问节点B的右边了,所以此时获取B节点后,让B出栈。

D、C、E构成右边的子树,接下来就是重复以上的步骤了。直到栈为空,整棵树也就完成输出了。

我们给出代码实现:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) { // 前序遍历,口诀很好记:根-->左-->右。
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        stack<TreeNode*> stk;
        TreeNode* node = root;
        while (!stk.empty() || node != nullptr) {
            while (node != nullptr) {
                res.emplace_back(node->val); // 前序遍历,先访问根
                stk.emplace(node);      
                node = node->left;           // 接下来进入左子树
            }
            if (!stk.empty()){
                node = stk.top();
                stk.pop();
                node = node->right;          // 接下来进入右子树
            }
        }
        return res;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)

好了,介绍最后一种层序遍历。如图

img

我们来分析一下:层序遍历,就是一层一层按顺序访问节点。所以根先入队列,从节点F开始,F为根,入队列并输出F,输出完就要让F出队列了。然后就判断F节点是否有左右子节点,如果有,就让左右子节点入队列。

接下来就是依次取队列前面的元素了,取出B节点,让B出队并输出B,然后判断B节点是否有左右子节点,如果有,就让左右子节点入队列。

又取出G节点,让G出队并输出G,然后判断G节点是否有左右子节点,如果有,就让左右子节点入队列。

可以注意到第7和第8行,因为节点A没有子节点,所以此时队列的状态不变。

重复以上步骤,直到队列没有元素可取了,此时整棵树也输出完毕了。

我们再通过动图直观感受下:

img

我们给出代码:层序需要利用队列来实现。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == NULL) return {};
        queue<TreeNode*> que;            // 创建队列
        que.push(root);                  // 根节点先入队
        vector<vector<int>> res;
        while (!que.empty()){
            vector<int> vec;
            int len = que.size();
            for (int i=0; i<len; i++){
                TreeNode* node = que.front();
                vec.push_back(node->val);                       // 访问根节点
                que.pop();                                      // 根节点出队列
                if (node->left != NULL) que.push(node->left);   // 左节点入队
                if 

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

<p> - 本专刊适合于C/C++已经入门的学生或人士,有一定的编程基础。 - 本专刊适合于互联网C++软件开发、嵌入式软件求职的学生或人士。 - 本专刊囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构等一系列知识点的讲解,并且最后总结出了高频面试考点(附有答案)共近400道,知识点讲解全面。不仅如此,教程还讲解了简历制作、笔试面试准备、面试技巧等内容。 </p> <p> <br /> </p>

全部评论
😈
点赞 回复 分享
发布于 2024-01-15 22:47 江苏

相关推荐

陈逸轩1205:才105 哥们在养生呢
点赞 评论 收藏
分享
05-30 18:54
武汉商学院 Java
湫湫湫不会java:先投着吧,大概率找不到实习,没实习的时候再加个项目,然后把个人评价和荣誉奖项删了,赶紧成为八股战神吧,没实习没学历,秋招机会估计不多,把握机会。或者说秋招时间去冲实习,春招冲offer,但是压力会比较大
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务