【C++】经典二叉树面试题

news2024/11/19 22:34:36

二叉树前中后序遍历的实现

1.非递归实现 

1

 我们先回顾一下三种遍历:

 

  • 前序遍历:根->左->右:  F-C-A -D-B-E-H-G-M
  • 中序遍历:左->根->右:  A-C-B-D-F-H-E-M-G
  • 后序遍历:左->右->根:  A-B-D-C-H-M-G-E-F

既然不用递归实现那其实我们可以用栈来实现。

对于前序:

  • 1.判空:

如果根是空的话,return {},注意他的函数返回类型是vector,所以加上{}。

  • 2.建栈并压根

因为前序是根先出,所以先压入根。

  • 3.出栈,依次压入左右子树
  1. 只要栈不为空就继续循环。
  2. 返回栈顶元素并删除栈顶元素。
  3. 如果有右子树先入右子树,因为栈是先进后出。所以右子树比左子树后出。
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
     if(root==nullptr)
     {
         return{} ;
     }
     //用栈来实现二叉树
         vector<int> v;
         stack<TreeNode*> s;
         s.push(root);    //先压入root
         while(!s.empty())    //只要栈不为空就继续
         {
             TreeNode* top=s.top();
             v.push_back(top->val);
             s.pop();
           //先入根的右子树,才能让左子树先遍历
             if(top->right)
             {
                 s.push(top->right);
             }
             if(top->left)
             {
                 s.push(top->left);
             }
         }
     
     return v;
    }
};

中序遍历和前序遍历有点不一样。

  • 1.判空
  • 2.建栈,进入循环
  1. 栈不为空或者根节点不为空进入循环。
  2. 判断根节点是否为空,如果不为空,压入根,往左子树走。直到走到左子树的尽头,并且根为空。
  3. 如果根为空,说明最大的左子树已经遍历完了,所以返回栈顶元素,并且压入右子树但是是top->right而不是root->right,这个找的是离栈顶最近的右子树。
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
    if(root==nullptr)
       {
         return{} ;
       }
     //用栈来实现二叉树
         vector<int> v;
         stack<TreeNode*> s;
         
         while(!s.empty()||root)    //只要栈不为空就继续
         {
             if(root)
             {
                 s.push(root);
                 root=root->left;
             }
             else
             {
                 TreeNode* top=s.top();
                 v.push_back(top->val);
                 s.pop();
                root=top->right;   //这里不再是root->right而是top->right
             }
         }
     
     return v;
    }
};

后序遍历:

后序遍历根前序遍历如出一辙,前面都一样,就是压入子树的顺序有点区别,这里是先压入左子树,此时栈里数据的顺序是根,右,左,然后我们用一个逆序函数,就变成左,右,根的顺序了。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
    if(root==nullptr)
     {
         return{} ;
     }
     //用栈来实现二叉树
         vector<int> v;
         stack<TreeNode*> s;
         s.push(root);    //先压入root
         while(!s.empty())    //只要栈不为空就继续
         {
             TreeNode* top=s.top();
             v.push_back(top->val);
             s.pop();
           //先入根的左子树,因为后面还有一个逆序呢
            
             if(top->left)
             {
                 s.push(top->left);
             }
             if(top->right)
             {
                 s.push(top->right);
             }
         }
     reverse(v.begin(),v.end());
     return v;
    }
};

现在还有兄弟问我while(!s.empty())啥意思,意思就是如果条件不为空,就继续。而不带!就是条件为真就继续,别搞混了。

2.递归实现 

递归的思路相对更简单,我们写一个前序功能的函数完成根,左,右的遍历即可。

这里要用传引用,因为当不断插入数据时vector这个容器的数据会被修改。

class Solution {
public:
    void preorder(TreeNode* root,vector<int>& v)
    {
        if(root==nullptr)
        {
            return ;
        }
         v.push_back(root->val);
         preorder(root->left,v);
         preorder(root->right,v);
    }
    vector<int> preorderTraversal(TreeNode* root) {
      vector<int> v;
      preorder(root,v);
      return v;
    }
};

中后序就一笔带过了:

//中序遍历
class Solution {
public:
void inorder(TreeNode* root,vector<int>& v)
    {
        if(root==nullptr)
        {
            return ;
        }
       inorder(root->left,v);
       v.push_back(root->val);
       inorder(root->right,v);
       
    }
    vector<int> inorderTraversal(TreeNode* root)
   {
      vector<int> v;
     inorder(root,v);
     return v;
    }
};
//后序遍历
class Solution {
public:
  void postorder(TreeNode* root,vector<int>& v)
    {
        if(root==nullptr)
        {
            return ;
        }
         postorder(root->left,v);
         postorder(root->right,v);
          v.push_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
  
         vector<int> v;
         postorder(root,v);
         return v;
    }
};

3.Morris实现前中后序遍历 

当我们用栈实现前序遍历时就会遇到一个无奈的问题,要想返回右子树的值,只能先让左子树的数据出栈后才能根据联系找到对应的右子树,但是Morris方法可以直接返回右子树。

  • 它的核心思想就是:

Morris其实解决了一个常规循环中循环到叶子节点后难以回到根节点的问题。 我们都知道前序遍历是先左后右,那么对任一节点p1来说,其右子树p1right所有节点必然在左子树p1left之后。代码中第二个while做的是,在p1left里一直往右,直到找不到更右的点,记这一点为p2。然后把p1接到p2的右边。 这样既保证了p1right在p1left所有点之后,又不需要再回到p1节点。 即在正常的往下循环的过程中,不断把右半部分剪下来,接到左半部分的最右下。

  • 遍历原则:(curr初始化为根节点)
  • 如果curr没有左孩子,curr就向右移动(curr = curr->right
  • 如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
  1. 如果mostRight的right指针指向空,让其指向curr,cur向左移动(cur=cur->left
  2. 如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur->right

前序遍历:

class Solution {
public: 
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        if (root == NULL)
            return ans;
        TreeNode* curr = root;  // 当前的结点
        TreeNode* currLeft = NULL;  // 当前结点的左子树
        while (curr != NULL) 
        {
            currLeft = curr->left;
            // 当前结点的左子树存在即可建立连接
            if (currLeft != NULL) 
            {
                // 找到当前左子树的最右侧节点,并且不能沿着连接返回上层
                while (currLeft->right != NULL && currLeft->right != curr)
                    currLeft = currLeft->right;
                //最右侧节点的右指针没有指向根结点,创建连接并往下一个左子树的根结点进行连接操作
                if (currLeft->right == NULL) 
                {
                    currLeft->right = curr;
                    ans.push_back(curr->val);
                    curr = curr->left;
                    continue;  // 这个continue很关键
                } 
                else 
                // 当左子树的最右侧节点有指向根结点,此时说明我们已经进入到了返回上层的阶段,不再是一开始的建立连接阶段,同时在回到根结点时我们应已输出过下层节点,直接断开连接即可
                    currLeft->right = NULL;
            }
            else
                // 当前节点的左子树为空,说明左侧到头,直接输出
                ans.push_back(curr->val);
            // 返回上层的阶段不断向右走
            curr = curr->right;
        }
        return ans;
    }
};

中序遍历:

思路:
类似迭代,整个二叉树中输出的第一个节点是最左侧结点,因此在建立连接的时候是不能够直接输出的,必须在建立连接阶段完成,到达最左侧结点之后返回上层的阶段,才能开始输出,此时正好符合“左中右”的遍历方式。
特殊处理:

  • 在建立连接阶段并不输出结点。
  • 在找到最左侧结点(即根结点的左子树为空)时,开始向右走返回上层并同时输出当前结点。
  • 对右子树也进行同样的处理。
class Solution {
public: 
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        if (root == NULL)
            return ans;
        TreeNode* curr = root;  // 当前的结点
        TreeNode* currLeft = NULL;  // 当前结点的左子树
        while (curr != NULL) 
         {
            currLeft = curr->left;
            // 当前结点的左子树存在即可建立连接
            if (currLeft != NULL) 
            {
                // 找到当前左子树的最右侧节点,并且不能沿着连接返回上层
                while (currLeft->right != NULL && currLeft->right != curr)
                    currLeft = currLeft->right;
                //最右侧节点的右指针没有指向根结点,创建连接并往下一个左子树的根结点进行连接操作
                if (currLeft->right == NULL) 
                {
                    currLeft->right = curr;
                    curr = curr->left;
                    continue;  // 这个continue很关键
                } 
                else 
                // 当左子树的最右侧节点有指向根结点,此时说明我们已经进入到了返回上层的阶段,不再是一开始的建立连接阶段,同时在回到根结点时我们应已输出过下层节点,直接断开连接即可
                    currLeft->right = NULL;
            }
            // 当前节点的左子树为空,说明左侧到头,直接输出并返回上层
            ans.push_back(curr->val);
            // 返回上层的阶段不断向右走
            curr = curr->right;
        }
        return ans;
    }
};

这是我复制力扣上一位大佬的代码,写的很不错。后序各位有兴趣去看看大佬写的,我不在列出来了。

作者:bei-zhi-hu
链接:https://leetcode.cn/problems/binary-tree-preorder-traversal/solution/cer-cha-shu-san-

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

与“改善”形成两个轮子。落实“改善”的东西

与“改善”形成两个轮子。落实“改善”的东西 简单地说&#xff0c;“用心”不只是“用心”&#xff0c;科学地推进的思考就是“自工程完结”。这是一种彻底、科学地思考“只能做好工作”“只能做出好东西”的条件是什么。 “改善”、“QC循环”、“丰田生产方式”等&#xff…

Java常量:Java常量的定义和分类

常量是指在程序的整个运行过程中值保持不变的量。在这里要注意常量和常量值是不同的概念&#xff0c;常量值是常量的具体和直观的表现形式&#xff0c;常量是形式化的表现。通常在程序中既可以直接使用常量值&#xff0c;也可以使用常量。 Java入门基础视频教程&#xff0c;ja…

物联网智能家居总体设计与实现

物联网智能家居突出特点就在于家居都连接入网&#xff0c;且都可以通过一定手段进行智能控制。 图3-1 物联网智能家居框架 如图3-1所示&#xff0c;构建了一个现代家庭中所涉及到的所有物联网智能家居相关的框架。包括了安防、照明、互联网、影音、饮水、停车等方方面面。下面本…

深入理解MySQL索引的数据结构和事务的四大特性、隔离性的四种级别

1.索引1.2 使用索引2.索引的数据结构【重点】3. 事务3.1 使用3.2 事务的四大特性1.索引 概念 索引是一种特殊的文件,饱含着对数据表里所有记录的引用指针。可以对表中的一列或者多列创建索引&#xff0c;并指定索引的类型&#xff0c;各类索引有各自的数据结构实现。 作用 …

Linux学习-46-LVM逻辑卷管理机制

11.9 LVM逻辑卷管理机制&#xff08;硬盘分区管理&#xff09; 我们在实际使用 Linux 服务器的时候&#xff0c;随着业务的增加&#xff0c;文件系统负载会越来越大&#xff0c;当到了空间不足的情况时&#xff0c;如果我们还在使用传统的分区方式管理硬盘&#xff0c;就不得不…

项目管理的核心是什么?

管理的核心是目标管理 目标管理通俗来讲是对做什么的思考&#xff0c;作为管理者在面向业务的开展过程中&#xff0c;根据业务的动态调整目标&#xff0c;大目标不变的情况下&#xff0c;去不断思考做什么更好地完成目标。 目标发展的几个阶段 1、相关的概念 项目启动&…

做测试8年,刚升主管,还是没逃过裁员....

我做测试8年&#xff0c;半年前被升为测试部门主管&#xff0c;本以为马上到达人生巅峰&#xff0c;没想到公司今天通知跟我解约。 回想晋升的这半年&#xff0c;我也曾激情满满&#xff0c;想着一定要好好干出一番成绩。可做了之后才发现这是个坑&#xff1a; 每天不是在开会…

一个简单的HTML网页 故宫学生网页设计作品 dreamweaver作业静态HTML网页设计模板 旅游景点网页作业制作

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

计算机网络第七章知识点回顾(自顶向下)

第七章知识点回顾1.网络层1.1网络层服务1.1.1网络层的两个主要功能1.1.2选路和转发的关系1.2网络层: 数据面和控制面1.网络层 Chapter goals: 理解网络层服务原理&#xff0c;主要关注数据面 网络层服务模型网络层上的重要功能&#xff1a;转发和选路路由器工作原理编址因特网…

【linux】cpu过高解决方法

CPU过高情况&#xff1a; 1、使用top命令查看cpu的进程占用情况&#xff1a; 2、发现11443的进程占比过高&#xff0c;通过top -Hp 11443 查看线程的占用情况&#xff0c;发现11459、11460、11461线程的占比过高&#xff1a; 解决swap占用CPU&#xff1a; 设置vm.swappiness0…

win10录屏快捷键是什么?电脑录屏快捷键ctrl+alt+

​在我们日常生活中&#xff0c;经常会使用电脑。我们在使用win10电脑的时候&#xff0c;有时候经常会遇到一些好看的视频或者是一些十分有用的知识教程&#xff0c;想要对其进行保存与分享&#xff0c;这个时候就需要用到电脑自带的录屏功能了。那么win10录屏快捷键是什么&…

Ubuntu22 Docker运行SRS流媒体服务,推拉流,yolov5训练自定义模型进行视频流识别

首先安装docker&#xff0c;设置系统启动 sudo apt-get install -y docker.io sudo systemctl start docker 查看docker进程 ps -ef|grep docker 拉去srs镜像 sudo docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:v4.0.198 启动镜像 sudo docker run -p 193…

PPT设置“只读模式”的两种方法

想要防止PPT文件被意外更改&#xff0c;或者禁止他人随意更改&#xff0c;我们可以给PPT设置保护模式&#xff0c;而PPT的“只读模式”就起到了这样的作用。 ​具体的设置方法有两种&#xff0c;我们可以根据不同需求选择合适的方法。 方法一&#xff1a; 防止意外更改&…

Linux安装Nexus3搭建maven私服超详细搭建上传步骤

下载nexus3.x 上传nexus压缩包并解压 启动 开启端口号 浏览器访问 ​编辑 设置开机自启动 运行用户为root (编辑nexus bin下的nexus.rc) 修改nexus3启动时要使用的jdk版本 修改nexus3默认端口 私服新建自定义的仓库 添加角色和用户 添加角色 添加用户 使用 Mave…

[附源码]java毕业设计学生实习管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

12.5 Hierarchical names (层次化名称)

Verilog HDL描述中的每个标识符应具有唯一的分层路径名。模块的层次结构和项目的定义&#xff08;如模块内的任务和命名块&#xff09;应定义这些名称。名称的层次结构可以被视为树结构&#xff0c;其中每个模块实例、生成块实例、任务、函数或命名的begin-end 或者 fork-join块…

【Git】一文带你入门Git分布式版本控制系统(简介,安装,Linux命令)

Git 系列文章目录 文章目录Git 系列文章目录一、前言二、安装 Git三、基本 Linux 命令一、前言 [ 什么是Git&#xff1f;] Git 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。【百度百科】 Git 是分布式版本控制系统&#…

Scratch、Python、C++,谁才是少儿编程的第一选择?

前言 面对市面上形形色色的编程语言类型&#xff0c;经常有家长犯难问我们该如何为孩子选择合适的课程&#xff1a; “那种拖块看起来像玩游戏&#xff0c;不如 Python、C 这样的代码编程语⾔⾼级。” “现在是人工智能时代&#xff0c;直接让孩子从Python学习&#xff0c;以…

【0147】当参数shared_memory_type分别为sysv和mmap时,差异为何如此大?

文章目录 1. sysv和mmap差异如此大2. 底层原理2.1 创建匿名mmap()共享内存段2.2 确定huge page大小2.3 创建ipcs看见的64字节shared memory1. sysv和mmap差异如此大 在【0145】postmaster创建System V shared memory默认值大小(2)一文中的第1节里,我有给出过当postgresql.c…

Hystrix 请求合并、请求隔离、优化

文章目录请求合并引入依赖启动类 加注解EnableHystrixservice服务测试请求隔离线程池隔离&#xff08;大部分情况下&#xff09;信号量隔离线程池隔离演示引入依赖启动类 加注解EnableHystrixservice服务测试信号量隔离演示Hystrix的其他用法请求合并 引入依赖 <dependenc…