Android 车机面试题分享

350 阅读25分钟

一 背景

今日在工作当中,受公司委托成面试官。本人处于车机智能座舱行业,记录下常用的面试题。总的篇章分为JAVA基础得分,Android 基础得分,简历问题得分,逻辑问题得分来展开。

二 面试题 JAVA基础

问题1:基础问java 三大特性

java的三大特性是封装,继承,和多态。

问题2:接口和抽象类的区别?

  • 定义的关键字不同。
  • 子类继承或实现关键字不同。
  • 类型扩展不同:抽象类是单继承,而接口是多继承。
  • 方法访问控制符:抽象类无限制,只是抽象类中的抽象方法不能被 private 修饰;而接口有限制,接口默认的是 public 控制符。
  • 属性方法控制符:抽象类无限制,而接口有限制,接口默认的是 public 控制符。
  • 方法实现不同:抽象类中的普通方法必须有实现,抽象方法必须没有实现;而接口中普通方法不能有实现,但在 JDK 8 中的 static 和 defualt 方法必须有实现。
  • 静态代码块的使用不同:抽象类可以有静态代码块,而接口不能有

问题3:什么是多态

在java中,多态是面向对象编程的一种重要特性,它允许不同的对象对同一个消息做出不同的响应。简单来说,多态就是同一种行为在不同对象上的表现方式不同。Java中的多态实现主要依靠两个机制:继承和接口。在继承中,子类可以继承父类的属性和方法,也可以重写父类的方法来实现自己的特定行为。当父类的引用指向子类的对象时,可以通过父类的引用调用子类重写的方法,实现多态。在接口中,类可以实现接口中定义的方法,这样可以实现不同的类拥有相同的行为,也可以通过接口的引用调用这些方法,实现多态。

问题4:多态动态绑定和静态绑定有什么区别?

静态绑定是在编译期间发生的绑定。它也被称为早期绑定,因为它发生在编译时本身。在静态绑定的情况下,编译器确切地知道要使用该方法的哪个实现。它使用类型或类信息进行绑定;方法重载是静态绑定的一个例子。请注意,所有静态、最终和私有方法都使用静态绑定,因为子类不能覆盖静态、最终和私有方法。编译器在编译时就知道要使用哪个方法实现。由于编译器知道父类的方法不能被覆盖,所以它总是使用该方法的父类实现。此外,静态绑定的性能也更好,因为涉及的开销更少。动态绑定在运行时解析绑定,因为编译器不知道在编译时使用哪个实现。它也被称为后期绑定,因为绑定是在运行时的后期而不是在编译时解析的。动态绑定也使用对象而不是类型或类进行绑定;方法覆盖是动态绑定的一个例子

问题5:java默认提供哪些数据结构

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties)

问题6:JVM内存结构

方法区method+栈内存+堆内存+本地方法栈jni+运行时data区

问题7: java虚拟机和dalvik区别

java虚拟机运行的是Java字节码,Dalvik虚拟机运行的是Dalvik字节码;传统的Java程序经过编译,生成Java字节码保存在class文件中,java虚拟机通过解码class文件中的内容来运行程序。而Dalvik虚拟机运行的是Dalvik字节码,所有的Dalvik字节码由Java字节码转换而来,并被打包到一个DEX(Dalvik Executable)可执行文件中Dalvik虚拟机通过解释Dex文件来执行这些字节码。Dalvik可执行文件体积更小。SDK中有一个叫dx的工具负责将java字节码转换为Dalvik字节码。java虚拟机与Dalvik虚拟机架构不同。java虚拟机基于栈架构。程序在运行时虚拟机需要频繁的从栈上读取或写入数据。这过程需要更多的指令分派与内存访问次数,会耗费不少CPU时间,对于像手机设备资源有限的设备来说,这是相当大的一笔开销。Dalvik虚拟机基于寄存器架构,数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式快的多。

问题8: int和integer区别?

Integer是Int的包装类,Int是八种基本数据类型之一。Integer变量必须实例化以后才可以使用,而Int变量不需要实例化。Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象,而Int是直接存储数据值。Integer的默认值是null,Int的默认值是0。

问题9:什么是反射机制?

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键

问题10:反射机制的应用场景有哪些?

类加载器,反射调用方法,动态代理,注解处理器。安卓当中的热修复也用到大量的反射。

问题11:JAVA static 关键字

  • 1.static修饰的变量和方法是属于类的;
  • 2.static修饰的变量和对象,方法存储在方法区的静态常量池中;是共享的;
  • 3.可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法
  • 4.static修饰的代码块在main方法前执行,目的是修饰main方法
  • 5.static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问6.static方法是没有this的方法,在static方法内部不能调用非静态方法,反过来是可以的static:方便在没有创建对象的情况下来进行调用方法和变量

问题12:String、StringBuffer、StringBuilder的区别?

编辑切换为居中

添加图片注释,不超过 140 字(可选)

问题13:java迭代器iterator,讲出用法

  迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

  Java中的Iterator功能比较简单,并且只能单向移动:

  • 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
  • 使用next()获得序列中的下一个元素。
  • 使用hasNext()检查序列中是否还有元素。
  • 使用remove()将迭代器新返回的元素删除。

  Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

问题14:JAVA中深拷贝和浅拷贝的区别?

是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

问题15:谈谈Error和Exception的区别?

首先Exception和Error都是继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。Exception和Error体现了JAVA这门语言对于异常处理的两种方式。Exception是java程序运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。非检查性异常 在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。 切记,Error是Throw不是Exception 。

问题16:谈一谈Java成员变量,局部变量和静态变量的创建和回收时机?

静态变量在类装载的时候就进行了创建,在整个程序结束时按序销毁。成员变量在类实例化对象时候创建,在对象销毁的时候销毁。局部变量在局部范围使用时创建,跳出局部范围销毁。

问题17:谈谈你对Java泛型中类型擦除的理解,并说说其局限性?

  • 不能使用instanceof
  • 不能用基本类型实例化类型参数
  • 不能创建参数化类型的数组
  • 泛型类的静态上下文中类型变量无效
  • 不能抛出或捕获泛型类的实例

三 面试题 安卓基础

问题1:启动activityA再启动B返回,此时A的生命周期

一个APP应用有2个完全不透明的Activity:Activity A和Activity B,其中Activity A是主活动。首先从手机桌面启动该APP应用,即创建加载运行Activity A,该应用各活动生命周期是:A.onCreate() A.onStart() A.onResume()接着点击Activity A中的一个跳转到Activity B的按钮,该应用各活动生命周期是:A.onPause() B.onCreate() B.onStart() B.onResume() A.onStop()

注意:如果Activity B是透明的或者Activity B并未完全遮住Activity A,那么上述操作点击Activity A中的一个跳转到Activity B的按钮的生命周期中A.onStop()是不会被调用的,因为Activity A还可见,所以Activity A不能被停止。

再接着点击返回按钮销毁Activity B并返回到Activity A,该应用各活动生命周期是:B.onPause() A.onRestart() A.onStart() A.onResume() B.onStop() B.onDestroy()最后再次点击返回按钮销毁Activity A并返回到手机桌面,该应用各活动生命周期是:A.onPause() A.onStop() A.onDestroy()

问题2:为什么要设计生命周期,用到了什么设计思想

Activity的生命周期采用了模板设计模式 。模板模式故名思意,他说对每个界面的一种抽象。有非常多的好处。最大的好处就是提供了标准的开发模式。为谷歌安卓系统的大力推广提供了基础支持。界面本身是多元且个性的。即便是谷歌也没有办法完全满足每个人的需求。所以创建一个模板,即满足编程需求,又满足市场需求。这种模板模式,就是快速解耦客户多元个性的软件体现。

问题3:如何在activity和service间传消息

  • 1.bindService 可以通过aidl通信
  • 2.广播也可以传递消息
  • 3.ContentProvider也可以用于数据传递
  • 4.文件
  • 5.sockt

问题4:startService和bindService的区别

生命周期上的区别。执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。

如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

问题5:service运行在要给新的进程里有多少个上下文对象

上下文对象在android中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程,Application、Activity和Service都是继承自Context。Android应用程序会在如下的几个时间点创建应用上下文Context。

  • 创建Application
  • 创建Activity
  • 创建Service

当应用程序第一次启动时,Android系统都会创建一个Application对象,同时创建Application Context,所有的组件都共同拥有这样一个Context对象,这个上下文对象贯穿整个应用进程的生命周期,为应用全局提供了功能和环境支持。而创建Activity和Service组件时,系统也会给它们提供运行的上下文环境,即创建Activity实例、Service实例的Context对象。所以我们在Activity中获取Context对象时,可以直接使用this,而在匿名内部类中,就必须指定特定XXXActivity.this才可以获得该Activity的Context对象。当然,我们也可以通过getApplicationContext()方法来获取整个App的上下文对象,但是通过 getApplicationContext()方法来获得的是整个应用的上下文对象,这与某个组件的上下文引用,在某些时候还是有区别的。

所以service运行在要给新的进程里有多少个上下文对象 = Application +Activity数+Service数

问题6:Content Provider监听数据变化

通过ContentObserver 实现监听

问题7:ContentProvider哪些方法运行在主线程

  • 当ContentProvider运行与独立进程的时候; onCreate方法运行与该进程的主线程,其余增删改查等所有方法运行在Binder线程.
  • 当ContentProvider运行在调用者同一个进程的时候; onCreate方法运行与该进程的主线程,其余增删改查等所有方法运行在调用时所在的线程.

问题8:权限广播怎么写

接受端需实现发送端定义好的 uses-permission 。仅实现了定义端定义的 uses-permission 的APP能收到关门广播

问题9:同一个广播,要给静态注册一个动态注册哪个先收到,为什么

动态的先收到。静态广播是有序的,比如短信。而动态广播是并行的。动态广播是无序的。从业务场景来看,动态广播本质的强调的并行,而静态广播是单例的一个个传递。他们本身就是为了程序的多元化而互补的。

问题10:自定义view流程

View的绘制基本由measure()、layout()、draw()这个三个函数完成

问题11:自定义view有什么要注意的?/如何适配从右到左的布局?

1、让View支持wrap_content属性

直接继承View或ViewGroup的控件,如果不在onMeasure中做处理,当控件设置wrap_content属性时无法达到预期效果。wrap_content属性会失效。

2、让View支持padding属性

直接继承View的控件,如果不处理padding属性,则padding会失效。如果继承ViewGroup的控件,还需要处理子元素的margin属性。

3、为了让控件使用更方便,尽量添加自定义属性。

4、如果View需要响应用户touch事件,需要处理好滑动冲突。

5、尽量不要在View中使用Handler,可以用post方法代替。

6、如果View中有子线程或者动画,要在onDetachedFromWindow中及时停止。

7、在onDraw方法中尽量不要创建临时对象,不要做任何耗时的操作,不要执行大数据量的循环操作。

适配从右到左的布局主要是坐标的变更。需要写好从右到左的测量方法。从左到右默认是0,0 开始,从右到左则是从宽度,0 高度,宽度,这样的标准而来。

问题12:handler如何确定它运行在哪个线程

  • 使用Looper类判断
  • 通过查看Thread类的当前线程

问题13:handler一个消息处理完成挂起后为什么不会消耗cpu

主线程的死循环一直运行是不是特别消耗CPU资源的问题 Linux 中有pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

问题14:OOM 哪些情况容易发生如何避免

  • 非法持有Activity对象导致的Activity泄漏。如非要持有弱引用
  • handler 匿名持有Activity导致的泄漏。 使用static 修饰
  • 数组集合无限制添加导致的泄漏。做好数组集合数据监控
  • aild远程注册监听未及时解除监听导致的内存泄漏。 生命周期销毁的地方及时的解除注册
  • IO流未关闭导致的内存泄漏 及时的关闭IO流

问题15:Android异常体系

Throwable,Error和Exception

问题16:ANR 哪些情况容易发生如何避免

1.只有主线程才会产生ANR,主线程就是UI线程;

2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;

3.上述事件响应超时,不同的context规定的上限时间不同

  • 主线程对输入事件5秒内没有处理完毕
  • 主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
  • 主线程在Service的各个生命周期函数时20秒内没有处理完毕。

避险在主线程执行耗时操作。

问题17:死锁如何分析

分析trace文件。

问题18:讲清楚AIDL或者service/client或者binder怎么用

常见如车机中的carservce。首先是对业务的封装,多个APP需要接入的必须功能可以抽象在servce中,采用CS架构模式。以服务端,代理端执行操作。共享数据。每个APP拿到的都是service的代理对象,对正在的执行采取集中的行为。符合面向切面编程。安卓官方的phone 等service audioservice也是如此的结构。

问题19:深刻理解binder

binder 主要就是为了解决跨进程通信。传统的sockt通信读写需要操作两次内存。binder 通过内存一些读写才懂代理模式,一次就把数据从一端传到了另外一段。基于嵌入式的开发,开销更小。对性能更加的友好。

问题20:启动模式有几种

  • 1、标准模式(standard):每启动一次Activity,就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。 应用场景:一般我们不主动设置启动模式,都是标准模式。
  • 2、栈顶模式(singleTop):如果栈顶存在该activity的实例,则复用,不存在新建放入栈顶。 应用场景:(1)点击通知跳详情 (2)新闻详情页,点击推荐新闻条目。
  • 3、栈内模式(singleTask):如果栈内存在该activity的实例,会将该实例上边的activity全部出栈,将该实例置于栈顶,如果不存在,则创建。应用场景: (1)APP的home页面,如果跳转到其他页面后又要跳回来 (2)浏览器的主页。
  • 4、单例模式(singleInstance):新开一个任务栈,该栈内只存放当前实例。应用场景:项目中语音通话功能,来电显示页面采用的就是singleinstance模式

问题21:冷启动和暖启动的区别?

  1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。   2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。

特点   1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。   2、热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,   因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

问题22:如何优化启动时间?

  • 1.减少应用启动时的初始化工作
  • 2.优化应用的布局和控件
  • 3.合理使用多线程和异步任务
  • 4.压缩图片和资源
  • 5.使用代码混淆和优化

问题23:OkHttp、Retrofit设计思想,本质?

分层架构,他的本质还是一个HTTP层面的框架,它的工作原理简单来说就是,先利用socket建立了与服务器的TCP连接,建立连接之后,在根据具体的需求,将符合HTTP协议的请求报文拼接好,进而通过刚才的连接传递到服务器,然后再读取服务器的响应。同时除了刚才基本HTTP的使用,okhttp提供了线程池,以此来执行具体的异步请求

问题24:属性动画、补间动画、帧动画

帧动画:是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。

补间动画:指的是做FLASH动画时,在两个关键帧中间需要做“补间动画”,才能实现图画的运动;

属性动画:帧动画与补间动画实现了对View进行移动、缩放、旋转和淡入淡出的效果。但对于android开发师来说是不够的,同时移动、缩放、旋转和淡入淡出的效果也不再只是一种视觉上的动画效果了。所以从Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation)。

问题25:插值器?

通俗易懂的说,Interpolator负责控制动画变化的速率,即确定了 动画效果变化的模式,使得基本的动画效果能够以匀速、加速、减速、抛物线速率等各种速率变化

四 简历问题

问题1:MVC,MVP,MVVM 的区别。基本简历上都会写这些。

MVC model view control。在安卓中缺点Actvity model 和control高度重叠,导致Activity代码巨大,耦合高,不易移植。

MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示

MVVM它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。让viewmodel 和UI通关观察者模式实现UI的数据动态绑定。当数据变化的时候UI自动变化。高度解耦了业务。可移植性强。个人比较推荐的也是MVVM的开发框

问题2:如何看待Android整个知识,自己擅长哪个方面

这个安卓还是博大精深,根据自己擅长的方向来回答。系统工程师和APP应用工程师的侧重点不一样。

问题3:车辆的驾驶模式,如运动,节能,是如何传递给车身的

一般情况 系统设置通过aidl 传递指令给carservice。carservice 通过HIDL 把数据传递给VHAL。VHAL通过串口通信传递数据给MCU。MCU再通过CAN 发送报文给BCM。

五 算法逻辑题

问题1:如何判断链表是否成环

例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

public static boolean hasCycle(ListNode head) { if (head == null || head.next == null) { // 链表为空或只有一个节点,不可能有环 return false; } ListNode slow = head; // 慢指针 ListNode fast = head.next; // 快指针 while (slow != fast) { if (fast == null || fast.next == null) { // 快指针已经到达链表末尾,不可能有环 return false; } slow = slow.next; // 慢指针移动一步 fast = fast.next.next; // 快指针移动两步 } // 当快指针追上慢指针时,链表有环 return true; } public static void main(String[] args) { ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); head.next.next.next.next = new ListNode(5); head.next.next.next.next.next = head.next; // 将链表的最后一个节点指向第二个节点,构成环 boolean result = hasCycle(head); System.out.println(result); // 输出 true } /** * 链表节点类 */ static class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } }

如上代码,通过定义快慢指针,如果快指针能够循环到最后一个则没有成环,如果快慢指针相遇,则链表必然是一个环形链表。

问题2:15瓶药,其中有一瓶是毒药,4只老鼠如何确定哪瓶有毒

考察二进制。15 的二进制刚好是1111 假设 1为老鼠喝药,0为不喝,最终看哪个唯一值下四方即可确定是哪一瓶有毒。

  1. 0001 => 1号瓶毒
  2. 0001 => 1号瓶
  3. 0010 => 2号瓶
  4. 0011 => 3号瓶
  5. 0100 => 4号瓶
  6. 0101 => 5号瓶
  7. 0111 => 7号瓶号瓶
  8. 0111 => 7号瓶
  9. 1000 => 8号瓶
  10. 1001 => 9号瓶
  11. 1010 => 10号瓶
  12. 1011 => 11号瓶
  13. 1100 => 12号瓶
  14. 1101 => 13号瓶
  15. 1110 => 14号瓶
  16. 1111 => 15号瓶

问题3:二叉树前序,中序,后序遍历是怎么样的?

先序遍历(D-L-R),从顶点按照根左右的顺序沿一定路径经过路径上所有的结点。在二叉树中,先根后左再右。巧记:根左右。

中序遍历(LDR),按照左根右的顺序沿一定路径经过路径上所有的结点,中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树,巧记:左根右。

后序遍历(LRD),按照左右根的顺序沿一定路径经过路径上所有的结点,后序遍历首先遍历左子树,然后访问右子树,最后遍历根结点,巧记:左右根。

六 总结

以前只是个人在工作当中的总结,如果误导的地方还请各位看官一起探讨修正!