面试专题资料(数据结构与算法、通讯协议)

一、通讯协议

一、三次握手简单例子

第一次握手: A给B打电话说,你可以听到我说话吗?

第二次握手: B收到了A的信息,然后对A说: 我可以听得到你说话啊,你能听得到我说话吗?  

第三次握手: A收到了B的信息,然后说可以的,我要给你发信息啦!

对图说明:

第一次握手:客户端通过调用connect发起主动打开。客户端向服务器发出连接请求的TCP报文段,其TCP首部中的同步比特SYN置为1,并TCP首部中序号seq设置为x(TCP规定SYN报文段不能携带数据,但是要消耗一个序号),表明要转送数据时初始序列号是x。

第二次握手:服务器收到数据报后,从TCP数据报首部的同步比特SYN位为1就知道这是一个建立连接的请求。服务器如果同意,会发回确认。在确认报文段中把同步比特位SYN设置为1,确认比特位ACK设置为1,由于TCP请求报文段中的序号是x,所以服务器在发送确认报文段中的确认号ack是x+1,同时把确认报文段中的序号seq设置为y,表明服务器发送数据的初始序列号为y。该报文段也不能携带数据(因为SYN=1,所以不携带任何数据)。

第三次握手:客户端收到服务器端的报文段后,要对服务器端中的SYN进行确认。在确认报文段中把确认比特位ACK设置为1,然后把确认号ack设置为y+1,自身的序号seq设置x+1。

注:客户的初始序列号为x,服务器的初始序列号为y,那么确认报文段中的确认号ack就是所期待的对方要发送的下一个序列号

二、四次挥手

例子:

A:“喂,我不说了 (FIN)。”A->FIN_WAIT

B:“我知道了(ACK)。等下,上一句还没说完。Balabala…..(传输数据)”B->CLOSE_WAIT | A->FIN_WAIT2

B:”好了,说完了,我也不说了(FIN)。”B->LAST_ACK

A:”我知道了(ACK)。”A->TIME_WAIT | B->CLOSED

图文说明:

第一次挥手:假设客户端执行主动关闭,那么它会向服务器端发出释放连接的报文段,这个TCP报文段中终止比特FIN置为1,序号seq设置为u(假设上一个发的数据序号是u-1)。并停止发送数据。主动关闭TCP连接。等待服务器的确认,这里需要注意,因为TCP是全双工的,所以TCP连接上有两条数据通路,发送FIN的一端就不能发送数据,也就是关闭了其中一条数据通路,对方还是可以继续发送数据。

第二次挥手:服务器端收到客户端的释放连接的报文段后会执行被动关闭,它要对客户端的数据报进行确认,服务器端会发送一个确认的数据报,确认比特ACK设置为1,确认号为u+1,自身的序号seq为v(假设上一个发的数据序号是v-1)。这个时候TCP处于半关闭状态,服务器依然可以向客户端发送数据(数据的序号为v+1 ~ w-1),客户端任要接受。

第三次挥手:服务器端已经没有要发送给客户端的数据,那么服务器端也会调用close关闭套接字,这样服务器端也会发送一个FIN的TCP报文段,序号是w(假设上一个发的数据序号是w-1)。这个时候服务器端不会再向客户端发送数据了。

第四次挥手:客户端接受到这个最终的FIN的释放连接报文段后必须对报文段进行确认。在确认的报文段中,ACK=1,确认序号ack=w+1,自己的序号seq=u+1(他的上一个序号的数据报就是申请释放连接的数据报,序号是seq=u)。

三、TCP连接11种状态

客户端独有的状态:SYN_SENT 、FIN_WAIT1 、FIN_WAIT2 、CLOSING 、TIME_WAIT 。

服务器独有的状态:LISTEN 、SYN_RCVD 、CLOSE_WAIT 、LAST_ACK 。

共有的状态:CLOSED 、ESTABLISHED 。

l  SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。

2  FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。

3  FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。

4  CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。

5  TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)

6  LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。

7  SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。

8  CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。

9  LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了

10  CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。

11  ESTABLISHED :表示TCP连接已经成功建立。

四、长连接:

client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。

这种方式下由于通讯连接一直存在。

建立连接——数据传输…(保持连接)…数据传输——关闭连接

长连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况

五、短连接:

Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。

此方式常用于一点对多点通讯

建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接

在HTTP/1.0中默认使用短连接。

 

HTTP/1.1起,默认使用长连接,用以保持连接特性

六、TCP与UDP区别

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。

 

流量控制:

    1,TCP:客户端A与服务端B连接TCP连接后,A向B发送数据,如果B服务端资源很忙,那么A会暂停发送数据,实现流量控制,TCP传输的速度稍慢;

    2,UDP:简单例子就是迅雷下载,迅雷服务器使用UDP向客户端发送资源,但记迅雷不管客户端是否资源繁忙,不停的发送资源,流量不能控制,传输速度快TCP

 

总结:实现了三次握手四次挥手,而UDP没有这种机制,可靠性弱,但传输速度快

七、网页请求技术点分析
1、根据域名查询域名的IP。浏览器缓存->操作系统缓存->本地域名服务器缓存->域名服务器。
2、得到IP后发起基于TCP的HTTP请求。如果浏览器存储了该域名下的cookie,那么会把cookie放入HTTP请求头里。
3、TCP被包装为IP包,通过网络(可能经过很多路由器、交换机)发送到IP地址对应的服务器。这个服务器可能只是一个反向代理服务器,如果是,则HTTP请求被转交给内网中真实的某一个服务器(可能有多个服务器)。
4、服务器分析HTTP请求,生成HTTP响应(可能是HTML、图片等)后,将响应发送给客户端浏览器。

5、浏览器得到响应后,根据响应内容显示结果。如果响应的是图片,则将图片”画“在浏览器页面上;如果是HTML,则渲染HTML并”画“在浏览器页面上,在分析HTML时,若发现引用了其他资源,例如css、图片等,则发起HTTP请求,得到响应资源。

八、解释与说明 HTTP 协议的几个重要概念

1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。

2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。

3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号

4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。

5.资源(Resource):由URI标识的网络数据对象或服务。

6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。

7.客户端(Client):一个为发送请求目的而建立连接的应用程序。

8.用户代理(User agent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。

9.服务器(Server):一个接受连接并对请求返回信息的应用程序。

10.源服务器(Origin server):是一个给定资源可以在其上驻留或被创建的服务器。

11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。

代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。

12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。

网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。

13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。

14.缓存(Cache):反应信息的局域存储。

九、HTTP报文结构-请求报文

1,请求方法、URI、HTTP协议版本

   该部分位于数据首行,基本格式为:GET /index.html HTTP/1.1

   该部分给出了请求类型和请求的资源位置(/index.html),其中HTTP中的请求类型包括:GET、POST、HEAD、PUT、DELETE.一般常用的为POST和GET方式。

2,请求头部:

   该部分紧跟着上一部分(方法、协议版本行),该部分主要是用于描述请求正文,其基本格式如下:

Host: www.qq.com     

Connection: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (X11; Linux x86_64)

主要是用于说明请求源、连接类型、以及一些Cookie信息等。

3,请求正文:

   一般用于存放POST请求类型的请求正文,如请求体为: name=xxxxx

十、HTTP报文结构-响应报文

1,状态码部分

该部分主要给出响应HTTP协议版本号、响应返回状态码、响应描述,同样是单行显示.格式为:HTTP/1.1 200 OK

常见的响应有:200(响应成功),400(请求异常,一般为参数异常),404(请求资源不存在),405(请求方式不支持),500(服务器内部异常)

2,响应头部:响应头部主要是返回一些服务器的基本信息,以及一些Cookie值等

3,响应体:该部分为请求需要得到的具体数据,可以为任何类型数据,一般网页浏览返回的为html文件内容.如上,内容不再列出。

十一、HTTP冥等性

HTTP幂等方法,是指无论调用这个url多少次,都不会有不同的结果的HTTP方法。也就是不管你调用1次还是调用100次,1000次,结果都是一样的。

HTTP GET方法,用于获取资源,不管调用多少次接口,结果都不会改变,所以是幂等的,只是查询数据,不会影响到资源的变化,因此我们认为它幂等。幂等性指的是作用于结果而非资源本身。怎么理解呢?例如,这个HTTP GET方法可能会每次得到不同的返回内容,但并不影响资源。

HTTP POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。POST一般是向后台添加数据时,才用POST?为什么?

也有例外,我们有的时候可能需要把查询方法改造成HTTP POST方法。比如,超长(1k)的GET URL使用POST方法来替代,因为GET受到URL长度的限制。虽然,它不符合幂等性,但是它是一种折中的方案

十二、HTTP如何处理长连接

HTTP的长连接本质上是TCP长连接

从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:

Connection:keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,当然这个需要服务器的支持

十三、HTTP版本的区别

HTTP1.0:无状态、无连接

HTTP1.0规定浏览器和服务器保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接),服务器不跟踪每个客户端也不记录过去的请求(无状态)。

这种无状态性可以借助cookie/session机制来做身份认证和状态记录。

每次发送请求的时候,都需要进行一次TCP的连接,而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率非常低

其次就是就是队头阻塞(head of line blocking)。由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送。假设前一个请求响应一直不到达,那么下一个请求就不发送,同样的后面的请求也给阻塞了。

 

HTTP1.1:持久连接  请求管道化  增加缓存处理  增加Host字段、支持断点传输等

对于HTTP1.1,不仅继承了HTTP1.0简单的特点,还克服了诸多HTTP1.0性能上的问题。

首先是长连接,HTTP1.1增加了一个Connection字段,通过设置Keep-Alive可以保持HTTP连接不断开,避免了每次客户端与服务器请求都要重复建立释放建立TCP连接,提高了网络的利用率。如果客户端想关闭HTTP连接,可以在请求头中携带Connection: false来告知服务器关闭请求。

其次,是HTTP1.1支持请求管道化(pipelining)。基于HTTP1.1的长连接,使得请求管线化成为可能。管线化使得请求能够并行传输。举个例子来说,假如响应的主体是一个html页面,页面中包含了很多img,这个时候keep-alive就起了很大的作用,能够进行并行发送多个请求。(客户端依据域名来向服务器建立连接,一般PC浏览器会针对单个域名的服务器同时建立6~8个连接,手机端一般控制在4~6个。这也是为什么很多大型网站设置不同的静态资源CDN域名来加载资源。)

 

 

HTTP2.0:二进制分帧  多路复用(或连接共享) 首部压缩  服务器推送

HTTP2.0通过在应用层和传输层之间增加一个二进制分帧层,突破了HTTP1.1的性能限制、改进传输性能

HTTP/2.0提供了在单个连接上复用HTTP请求和响应的能力。 多个请求或响应可以同时在一个连接上使用流

虽然HTTP2.0的协议和HTTP1.x协议之间的规范完全不同了,但是实际上HTTP2.0并没有改变HTTP1.x的语义。

简单来说,HTTP2.0只是把原来HTTP1.x的header和body部分用frame重新封装了一层而已

HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。

HTTP 2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。

 

算法与数据结构

一、时间复杂度:

 常数阶   

int sum = 0,n = 100;      //执行一次

sum = (1+n)*n/2;          //执行一次

System.out.println (sum); //执行一次

这个算法的时间复杂度为O(1)。如果sum = (1+n)*n/2这条语句再执行10遍,因为这与问题大小n的值并没有关系,所以这个算法的时间复杂度仍旧是O(1),我们可以称之为常数阶。

线性阶

线性阶主要要分析循环结构的运行情况,如下所示。

for(int i=0;i<n;i++){

    运算处理

}

上面算法循环体中的代码执行了n次,因此时间复杂度为O(n)

平方阶

下面的代码是循环嵌套:

  for(int i=0;i<n;i++){   

      for(int j=0;j<n;i++){

         //复杂度为O(1)的算法

         ...

      }

  }

内层循环的时间复杂度在讲到线性阶时就已经得知是O(n),现在经过外层循环n次,那么这段算法的时间复杂度则为O(n²)。

二、JAVA中常见的数据结构类型

1. List

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下 >标)来访问List中的元素,这类似于Java的数组。

1.ArrayList: 元素单个,效率高,多用于查询

基于数组上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector好一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。

2.Vector: 元素单个,线程安全,多用于查询

基于数组(Array)的List,其实就是封装了数组所不具备的一些功能方便我们使用,所以它难易避免数组的限制,同时性能也不可能超越数组。所以,在可能的情况下,我们要多运用数组。另外很重要的一点就是Vector是线程同步的(sychronized)的,这也是Vector和ArrayList 的一个的重要区别。

3.LinkedList:元素单个,多用于插入和删除

它每一个节点(Node)都包含两方面的内容:

    1.节点本身的数据(data);

    2.下一个节点的信息(nextNode)。

所以当对LinkedList做添加,删除动作的时候就不用像基于数组的ArrayList一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了,这是LinkedList的优势。

4.HashMap: 元素成对,元素可为空

5.HashTable: 元素成对,线程安全,元素不可为空

大多数情况下,从性能上来说ArrayList最好,但是当集合内的元素需要频繁插入、删除时LinkedList会有比较好的表现,但是它们三个性能都比不上数组,另外Vector是线程同步的。所以: 
如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List; 
如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList; 
如果在多线程条件下使用,可以考虑Vector; 
如果需要频繁地删除插入,LinkedList就有了用武之地; 
如果你什么都不知道,用ArrayList没错。 

所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ]

所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]

所有的List中可以有null元素,例如[ tom,null,1 ]

 基于Array的List(Vector,ArrayList)适合查询,而LinkedList 适合添加,删除操作

三、队列:

有了以上的基础,我们重点看一下队列,队列(queue)简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许 在表的一端进行插入,而在表的另一端进行删除。

在队列中把插入数据元素的一端称为队尾(rear),删除数据元素的一端称为队首(front)。向队尾插入元素称为进队或入队,新元素 入队后成为新的队尾元素;从队列中删除元素称为离队或出队,元素出队后,其后续元素成 为新的队首元素。 由于队列的插入和删除操作分别在队尾和队首进行,每个元素必然按照进入的次序离队,也就是说先进队的元素必然先离队,所以称队列为先进先出表(First In First Out,简称 FIFO)。

队列结构与日常生活中排队等候服务的模型是一致的,像火车站买票,早排队进入队列的人,早买到票并最先离开(出队);后到来的人只能排在队列的后,后得到服务并后离开

四、队列-顺序存储方式:

在队列的顺序存储实现中,我们可以将队列当作一般的表用数组加以实现,但这样做的 效果并不好。尽管我们可以用一个指针 last 来指示队尾,使得 enqueue 运算可在Ο(1)时间内 完成,但是在执行 dequeue 时,为了删除队首元素,必须将数组中其他所有元素都向前移动 一个位置。这样,当队列中有 n 个元素时,执行 dequeue 就需要Ο(n)时间。

为了提高运算的效率,我们用另一种方法来表达数组中各单元的位置关系。设想数组 A[0.. capacity-1]中的单元不是排成一行,而是围成一个圆环

五、队列-链式存储方式:

队列的链式存储可以使用单链表来实现。为了操作实现方便,这里采用带头结点的单链 表结构。根据单链表的特点,选择链表的头部作为队首,链表的尾部作为队尾。除了链表头 结点需要通过一个引用来指向之外,还需要一个对链表尾结点的引用,以方便队列的入队操 作的实现。为此一共设置两个指针,一个队首指针和一个队尾指针,如图所示,队首针指(front)向队首元素的前一个结点即始终指向链表空的头结点; 队尾指针(rear)指向队列当前队尾元素所在的结点。当队列为空时,队首指针与队尾指针均指向空的头结点

在Java中没有显式的指针类型,然而实际上对象的访问就是使用指针来实现的,即在Java中是使用对象的引用来替代指针的。

在节点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的节点

SLNode类:由data和next指针组成。

QueueSLinked类:出队 入队实现

在Java中没有显式的指针类型,然而实际上对象的访问就是使用指针来实现的,即在Java中是使用对象的引用来替代指针的。因此在使用Java实现该节点结构时,一个节点本身就是一个对象。节点的数据域data可以使用一个Object类型的对象来实现,用于存储任何类型的数据元素,并通过对象的引用指向该元素;而指针域next可以通过节点对象的引用来实现

六、树的基本概念

树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点, 所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结 构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或简称为树根

七、二叉树定义

每个结点的度均不超过 2 的有序树,称为二叉树(binary tree)。

与树的递归定义类似, 二叉树的递归定义如下:二叉树或者是一棵空树,或者是一棵由一个根结点和两棵互不相交 的分别称为根的左子树和右子树的子树所组成的非空树。二叉树中每个结点的孩子数只能是 0、1 或 2 个,并且每个孩子 都有左右之分。位于左边的孩子称为左孩子,位于右边的孩子称为右孩子;以左孩子为根的 子树称为左子树,以右孩子为根的子树称为右子树

八、二叉树数据结构

链式存储结构

设计不同的结点结构可构成不同的链式存储结构。

在二叉树中每个结点都有两个孩子, 则可以设计每个结点至少包括 3 个域:数据域、左孩子域和右孩子域。数据域存放数据元素, 左孩子域存放指向左孩子结点的指针,右孩子域存放指向右孩子结点的指针

九、如何初始化二叉树及遍历?

每个结点在遍历过程中都被途经 3 次,三种不同的遍历只是在该执行过程中的不同时机返回根结点而已,详见TreeIterator.java

深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历。

广度优先遍历:又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止

先序遍历(递归):35 20 15 16 29 28 30 40 50 45 55

中序遍历(递归):15 16 20 28 29 30 35 40 45 50 55

后序遍历(递归):16 15 28 30 29 20 45 55 50 40 35

先序遍历(非递归):35 20 15 16 29 28 30 40 50 45 55

中序遍历(非递归):15 16 20 28 29 30 35 40 45 50 55

后序遍历(非递归):16 15 28 30 29 20 45 55 50 40 35

广度优先遍历:35 20 40 15 29 50 16 28 30 45 55

十、常见算法-冒泡算法基本实现

十一、常见算法-选择算法实现?

例:将数组   int[] arr={ 5  ,2  ,8  ,4  ,9 ,1 }  元素进行排序

第一次排序: 最小数据1,把1放在首位,也就是1和5互换位置,

                           排序结果:1  2  8  4  9  5

第二次排序:第1以外的数据{2  8  4  9  5}进行比较,2最小,

                           排序结果:1  2  8  4  9  5

第三次排序:除1、2以外的数据{8  4  9  5}进行比较,4最小,8和4交换

                           排序结果:1  2  4  8  9  5

第四次排序:除第1、2、4以外的其他数据{8  9  5}进行比较,5最小,8和5交换

                           排序结果:1  2  4  5  9  8

第五次排序:除第1、2、4、5以外的其他数据{9  8}进行比较,8最小,8和9交换

                           排序结果:1  2  4  5  8  9

十二、常见算法-直接插入排序算法实现

例:将数组   int[] arr={ 11, 25, 45, 26, 12, 78}  元素进行直接插入排序。

1、首先比较25和11的大小,11小,位置互换,

       第一轮排序后,顺序为:[11, 25, 45, 26, 12, 78]。

2、对于第三个数据45,其大于11、25,所以位置不变,

       顺序依旧为:[11, 25, 45, 26, 12, 78]。

3、对于第四个数据26,其大于11、25,小于45,所以将其插入25和45之间,

        顺序为:[11, 25, 26, 45, 12, 78]。

....

4、最终顺序为:[11, 12, 25, 26, 45, 78]。

十三、常见算法-希尔排序算法实现

例:将数组   int[] array={82 ,31 ,29 ,71, 72, 42, 64, 5,110}元素进行希尔排序。

1,第一次取增量设置为array.length/2 = 4    先从82开始以4为增量遍历直到末尾,得到(82,42)

       排序得到{42 ,31 ,29 ,71, 72, 82, 64, 5,110}。

2, 然后从第二个数31开始重复上一个步骤,得到(31,64)

        排序得到{42 ,31 ,29 ,71, 72, 82, 64, 5,110}

       .......

3,以4为增量的遍历完数组之后,

      排序得到{42 ,31,5,71,72,82,64,29,110}

 

 

猜你喜欢

转载自my.oschina.net/u/3728166/blog/2876799