|
@ -67,7 +67,7 @@
|
|||
|
||||
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 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
|
||||
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200(OK)并返回数据。而使用 POST 方法,浏览器先发送 Header,服务器响应 100(Continue)之后,浏览器再发送 Data,最后服务器响应 200(OK)并返回数据。
|
||||
|
||||
```
|
||||
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
|
||||
```
|
||||
|
@ -107,10 +111,6 @@ Host: w3schools.com
|
|||
name1=value1&name2=value2
|
||||
```
|
||||
|
||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
|
||||
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200(OK)并返回数据。而使用 POST 方法,浏览器先发送 Header,服务器响应 100(Continue)之后,浏览器再发送 Data,最后服务器响应 200(OK)并返回数据。
|
||||
|
||||
## HEAD
|
||||
|
||||
> 获取报文首部
|
||||
|
@ -172,7 +172,7 @@ DELETE /file.html HTTP/1.1
|
|||
|
||||
> 要求用隧道协议连接代理
|
||||
|
||||
要求在于代理服务器通信时建立隧道,使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
要求在与代理服务器通信时建立隧道,使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
|
||||
```html
|
||||
CONNECT www.example.com:443 HTTP/1.1
|
||||
|
@ -342,7 +342,16 @@ Host: www.example.org
|
|||
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 |
|
||||
| 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 用于客户端。
|
||||
|
||||
### 4. 浏览器禁用 Cookie 的情况
|
||||
### 5. 浏览器禁用 Cookie 的情况
|
||||
|
||||
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
|
||||
|
||||
### 5. 使用 Cookie 实现用户名和密码的自动填写
|
||||
### 6. 使用 Cookie 实现用户名和密码的自动填写
|
||||
|
||||
网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
|
||||
|
||||
但是如果 Set-Cookie 指定了 HttpOnly 属性,就无法通过 Javascript 脚本获取 Cookie 信息,这是出于安全性考虑。
|
||||
|
||||
## 缓存
|
||||
|
||||
### 1. 优点
|
||||
|
@ -448,7 +459,7 @@ Content-Type: text/plain
|
|||
|
||||
如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。
|
||||
|
||||
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。
|
||||
在请求报文首部中添加 Range 字段指定请求的范围,请求成功的话服务器发送 206 Partial Content 状态。
|
||||
|
||||
```html
|
||||
GET /z4d4kWk.jpg HTTP/1.1
|
||||
|
@ -504,9 +515,11 @@ HTTP 有以下安全性问题:
|
|||
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
||||
3. 无法证明报文的完整性,报文有可能遭篡改。
|
||||
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。也就是说使用了隧道进行通信。
|
||||
|
||||
<div align="center"> <img src="../pics//ssl-offloading.jpg" width=""/> </div><br>
|
||||
通过使用 SSL,HTTPs 具有了加密、认证和完整性保护。
|
||||
|
||||
<div align="center"> <img src="../pics//ssl-offloading.jpg" width="700"/> </div><br>
|
||||
|
||||
## 加密
|
||||
|
||||
|
@ -521,7 +534,7 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信
|
|||
|
||||
### 2. 公开密钥
|
||||
|
||||
(Public-Key Encryption)而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||
(Public-Key Encryption),使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||
|
||||
- 优点:更为安全;
|
||||
- 缺点:运算速度慢;
|
||||
|
@ -530,7 +543,7 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信
|
|||
|
||||
### 3. HTTPs 采用的加密方式
|
||||
|
||||
HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。
|
||||
HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中的 Session Key 就是对称密钥)
|
||||
|
||||
<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>
|
||||
|
||||
数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
|
||||
|
||||
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
|
||||
|
||||
<div align="center"> <img src="../pics//mutualssl_small.png" width=""/> </div><br>
|
||||
|
||||
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
|
||||
|
||||
## 完整性
|
||||
|
||||
SSL 提供摘要功能来验证完整性。
|
||||
SSL 提供报文摘要功能来验证完整性。
|
||||
|
||||
# 七、Web 攻击技术
|
||||
|
||||
|
@ -733,3 +747,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)
|
||||
- [维基百科:跨站点请求伪造](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)
|
||||
- [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)
|
||||
|
|
|
@ -503,7 +503,7 @@ public static void main(java.lang.String[]);
|
|||
|
||||
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
|
||||
|
||||
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName('com.mysql.jdbc.Driver.class') 这种方式来控制类的加载,该方法会返回一个 Class 对象。
|
||||
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。
|
||||
|
||||
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
|
||||
|
||||
|
|
|
@ -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]
|
||||
```
|
||||
|
||||
<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
|
||||
public class TreeLinkNode {
|
||||
|
@ -381,7 +381,7 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) {
|
|||
|
||||
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
|
||||
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) 被重复计算了。
|
||||
|
||||
<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)。
|
||||
|
||||
<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)。
|
||||
|
||||
<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)。
|
||||
|
||||
|
@ -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
|
||||
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 将在环入口点相遇。
|
||||
|
||||
<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
|
||||
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
|
||||
|
||||
<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
|
||||
|
||||
<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 所对应的二叉搜索树。
|
||||
|
||||
<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
|
||||
|
||||
<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。
|
||||
|
||||
<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 链接进行赋值。
|
||||
|
||||
<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
|
||||
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。
|
||||
|
||||
<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
|
||||
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
|
||||
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
|
|
249
notes/算法.md
|
@ -22,12 +22,13 @@
|
|||
* [优先队列](#优先队列)
|
||||
* [应用](#应用)
|
||||
* [五、查找](#五查找)
|
||||
* [符号表](#符号表)
|
||||
* [二分查找实现有序符号表](#二分查找实现有序符号表)
|
||||
* [二叉查找树](#二叉查找树)
|
||||
* [2-3 查找树](#2-3-查找树)
|
||||
* [红黑二叉查找树](#红黑二叉查找树)
|
||||
* [散列表](#散列表)
|
||||
* [应用](#应用)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
|
@ -41,7 +42,7 @@ N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 \~ N<sup>3</sup>/6。使用 \~f(N) 来表示
|
|||
|
||||
### 2. 增长数量级
|
||||
|
||||
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 的增长数量级为 O(N<sup>3</sup>)。增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 O(N<sup>3</sup>)与它是否用 Java 实现,是否运行于特定计算机上无关。
|
||||
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 的增长数量级为 O(N<sup>3</sup>)。增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 O(N<sup>3</sup>) 与它是否用 Java 实现,是否运行于特定计算机上无关。
|
||||
|
||||
### 3. 内循环
|
||||
|
||||
|
@ -57,13 +58,13 @@ ThreeSum 用于统计一个数组中三元组的和为 0 的数量。
|
|||
|
||||
```java
|
||||
public class ThreeSum {
|
||||
public static int count(int[] a) {
|
||||
int N = a.length;
|
||||
public int count(int[] nums) {
|
||||
int N = nums.length;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
for (int j = i + 1; j < N; j++) {
|
||||
for (int k = j + 1; k < N; k++) {
|
||||
if (a[i] + a[j] + a[k] == 0) {
|
||||
if (nums[i] + nums[j] + nums[k] == 0) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ public class ThreeSum {
|
|||
}
|
||||
```
|
||||
|
||||
该算法的内循环为 if (a[i] + a[j] + a[k] == 0) 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6 - N<sup>2</sup>/2 + N/3,因此它的近似执行次数为 \~N<sup>3</sup>/6,增长数量级为 N<sup>3</sup>。
|
||||
该算法的内循环为 if(a[i]+a[j]+a[k]==0) 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6-N<sup>2</sup>/2+N/3,因此它的近似执行次数为 \~N<sup>3</sup>/6,增长数量级为 N<sup>3</sup>。
|
||||
|
||||
<font size=4> **改进** </font></br>
|
||||
|
||||
|
@ -84,21 +85,31 @@ public class ThreeSum {
|
|||
|
||||
```java
|
||||
public class ThreeSumFast {
|
||||
public static int count(int[] a) {
|
||||
Arrays.sort(a);
|
||||
int N = a.length;
|
||||
public int count(int[] nums) {
|
||||
Arrays.sort(nums);
|
||||
int N = nums.length;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
for (int j = i + 1; j < N; j++) {
|
||||
// rank() 方法返回元素在数组中的下标,如果元素不存在,这里会返回 -1。
|
||||
// 应该注意这里的下标必须大于 j,否则会重复统计。
|
||||
if (BinarySearch.rank(-a[i] - a[j], a) > j) {
|
||||
if (binarySearch(nums, -nums[i] - nums[j]) > j) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
private int binarySearch(int[] nums, int target) {
|
||||
int l = 0, h = nums.length - 1;
|
||||
while (l <= h) {
|
||||
int m = l + (h - l) / 2;
|
||||
if (nums[m] == target) return m;
|
||||
else if (nums[m] < target) h = m - 1;
|
||||
else l = m + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -109,7 +120,7 @@ public class ThreeSumFast {
|
|||
例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N<sup>3</sup>/6。进行如下实验:多次运行该算法,每次取的 N 值为前一次的两倍,统计每次执行的时间,并统计本次运行时间与前一次运行时间的比值,得到如下结果:
|
||||
|
||||
| N | Time | Ratio |
|
||||
| --- | --- | --- |
|
||||
| :---: | :---: | :---: |
|
||||
| 250 | 0.0 | 2.7 |
|
||||
| 500 | 0.0 | 4.8 |
|
||||
| 1000 | 0.1 | 6.9 |
|
||||
|
@ -301,21 +312,21 @@ public class Queue<Item> {
|
|||
|
||||
# 三、union-find
|
||||
|
||||
<font size=4> **概览** </font> <br>
|
||||
|
||||
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连通。
|
||||
|
||||
<div align="center"> <img src="../pics//7e9d0ef2-acd8-44c0-a76b-f0d2f5e76738.png" width="300"/> </div><br>
|
||||
<div align="center"> <img src="../pics//dc752c5b-bb59-4616-bf9c-21276690a24d.png" width="400"/> </div><br>
|
||||
|
||||
<font size=4> **API** </font> <br>
|
||||
|
||||
<div align="center"> <img src="../pics//867abc3c-8403-43ef-8847-c1fea32996a5.png" width="800"/> </div><br>
|
||||
|
||||
<font size=4> **基本数据结构** </font> <br>
|
||||
| 方法 | 描述 |
|
||||
| ---: | :--- |
|
||||
| UF(int N) | 构造一个大小为 N 的并查集 |
|
||||
| void union(int p, int q) | 连接 p 和 q 节点 |
|
||||
| int find(int p) | 查找 p 所在的连通分量 |
|
||||
| boolean connected(int p, int q) | 判断 p 和 q 节点是否连通 |
|
||||
|
||||
```java
|
||||
public class UF {
|
||||
// 使用 id 数组来保存点的连通信息
|
||||
private int[] id;
|
||||
|
||||
public UF(int N) {
|
||||
|
@ -333,30 +344,36 @@ public class UF {
|
|||
|
||||
## quick-find
|
||||
|
||||
保证在同一连通分量的所有节点的 id 值相等。
|
||||
可以快速进行 find 操作,即可以快速判断两个节点是否连通。
|
||||
|
||||
这种方法可以快速取得一个节点的 id 值,并且判断两个节点是否连通。但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
|
||||
同一连通分量的所有节点的 id 值相等。
|
||||
|
||||
但是 union 操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
|
||||
|
||||
<div align="center"> <img src="../pics//8f0cc500-5994-4c7a-91a9-62885d658662.png" width="350"/> </div><br>
|
||||
|
||||
```java
|
||||
public int find(int p) {
|
||||
return id[p];
|
||||
}
|
||||
public void union(int p, int q) {
|
||||
int pID = find(p);
|
||||
int qID = find(q);
|
||||
public int find(int p) {
|
||||
return id[p];
|
||||
}
|
||||
public void union(int p, int q) {
|
||||
int pID = find(p);
|
||||
int qID = find(q);
|
||||
|
||||
if (pID == qID) return;
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] == pID) id[i] = qID;
|
||||
}
|
||||
if (pID == qID) return;
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] == pID) id[i] = qID;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## quick-union
|
||||
|
||||
在 union 时只将节点的 id 值指向另一个节点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,应该注意的是根节点需要指向自己。查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
|
||||
可以快速进行 union 操作,只需要修改一个节点的 id 值即可。
|
||||
|
||||
<div align="center"> <img src="../pics//ae1f3f27-cb47-436d-b8a2-185618851b57.png" width="600"/> </div><br>
|
||||
但是 find 操作开销很大,因为同一个连通分量的节点 id 值不同,id 值只是用来指向另一个节点。因此需要一直向上查找操作,直到找到最上层的节点。
|
||||
|
||||
<div align="center"> <img src="../pics//5d4a5181-65fb-4bf2-a9c6-899cab534b44.png" width="350"/> </div><br>
|
||||
|
||||
```java
|
||||
public int find(int p) {
|
||||
|
@ -374,7 +391,7 @@ public class UF {
|
|||
|
||||
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
|
||||
|
||||
<div align="center"> <img src="../pics//dcbd1473-96c3-4ace-8b69-2c9342615e7e.png" width="300"/> </div><br>
|
||||
<div align="center"> <img src="../pics//bfbb11e2-d208-4efa-b97b-24cd40467cd8.png" width="150"/> </div><br>
|
||||
|
||||
## 加权 quick-union
|
||||
|
||||
|
@ -382,7 +399,7 @@ public class UF {
|
|||
|
||||
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
|
||||
|
||||
<div align="center"> <img src="../pics//7f9a9342-1491-436d-bdd2-0fa94eb0e4f1.png" width="500"/> </div><br>
|
||||
<div align="center"> <img src="../pics//095720ee-84b3-42ff-af71-70ceb6a2f4a3.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
public class WeightedQuickUnionUF {
|
||||
|
@ -429,7 +446,12 @@ public class WeightedQuickUnionUF {
|
|||
|
||||
## 各种 union-find 算法的比较
|
||||
|
||||
<div align="center"> <img src="../pics//cae894a9-2424-4de4-ab41-c15d7054a5e7.png" width="800"/> </div><br>
|
||||
| 算法 | union | find |
|
||||
| :---: | :---: | :---: |
|
||||
| quick-find | N | 1 |
|
||||
| quick-union | 树高 | 树高 |
|
||||
| 加权 quick-union | lgN | lgN |
|
||||
| 路径压缩的加权 quick-union | 非常接近 1 | 非常接近 1 |
|
||||
|
||||
# 四、排序
|
||||
|
||||
|
@ -457,7 +479,7 @@ private void exch(Comparable[] a, int i, int j){
|
|||
|
||||
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
|
||||
|
||||
<div align="center"> <img src="../pics//aa62b91f-3540-4a28-8ea9-045f62ab3bcc.png" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ae3fc93a-44d5-4beb-b05a-874bd9c0a657.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Selection {
|
||||
|
@ -480,7 +502,7 @@ public class Selection {
|
|||
|
||||
插入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。
|
||||
|
||||
<div align="center"> <img src="../pics//a451b523-7e24-4fae-8e35-c46b14beed68.png" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c9a1de44-b1c0-4d13-a654-827d4ef8a723.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Insertion {
|
||||
|
@ -507,9 +529,7 @@ public class Insertion {
|
|||
|
||||
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。
|
||||
|
||||
<div align="center"> <img src="../pics//bed9d745-f971-405d-ba57-bcfa7986c8bd.png" width="500"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//6e8f16d1-7dea-4331-aea0-3dd739db00a4.png" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//4bb7ed45-ec14-4d31-9da4-94024d9d3b05.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Shell {
|
||||
|
@ -537,9 +557,7 @@ public class Shell {
|
|||
|
||||
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
|
||||
|
||||
<div align="center"> <img src="../pics//8d13ee52-e881-41eb-ad70-678c411f2718.png" width="600"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//b5fed547-a989-4ead-81d5-ea72660faf99.png" width="800"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8dfb4cc9-26da-45e7-b820-4576fa1cbb0e.png" width="350"/> </div><br>
|
||||
|
||||
### 1. 归并方法
|
||||
|
||||
|
@ -568,7 +586,8 @@ public class MergeSort {
|
|||
|
||||
### 2. 自顶向下归并排序
|
||||
|
||||
<div align="center"> <img src="../pics//c95a18ae-4b97-4fa9-9806-d9ed7b345b42.png" width="800"/> </div><br>
|
||||
<div align="center"> <img src="../pics//0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png" width="450"/> </div><br>
|
||||
|
||||
|
||||
```java
|
||||
public static void sort(Comparable[] a) {
|
||||
|
@ -593,8 +612,6 @@ private static void sort(Comparable[] a, int lo, int hi) {
|
|||
|
||||
先归并那些微型数组,然后成对归并得到的子数组。
|
||||
|
||||
<div align="center"> <img src="../pics//4e3faf22-fa80-445e-a57d-594c37bb76e7.png" width="800"/> </div><br>
|
||||
|
||||
```java
|
||||
public static void busort(Comparable[] a) {
|
||||
int N = a.length;
|
||||
|
@ -613,7 +630,7 @@ public static void busort(Comparable[] a) {
|
|||
|
||||
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
|
||||
|
||||
<div align="center"> <img src="../pics//202cd47c-ef2f-4c0e-a511-53359553387d.png" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ab77240d-7338-4547-9183-00215e7220ec.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public class QuickSort {
|
||||
|
@ -633,9 +650,9 @@ public class QuickSort {
|
|||
|
||||
### 2. 切分
|
||||
|
||||
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。
|
||||
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置。
|
||||
|
||||
<div align="center"> <img src="../pics//037f84c6-b470-42e0-a2d9-fd8cd7e96fa8.png" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8af348d0-4d72-4f76-b56c-0a440ed4673d.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
private static int partition(Comparable[] a, int lo, int hi) {
|
||||
|
@ -676,8 +693,6 @@ private static int partition(Comparable[] a, int lo, int hi) {
|
|||
|
||||
三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。
|
||||
|
||||
<div align="center"> <img src="../pics//b43437dd-16b6-4023-bdda-d6f7d6db762e.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Quick3Way {
|
||||
public static void sort(Comparable[] a, int lo, int hi) {
|
||||
|
@ -702,11 +717,12 @@ public class Quick3Way {
|
|||
|
||||
### 1. 堆
|
||||
|
||||
定义:一颗二叉树的每个节点都大于等于它的两个子节点。
|
||||
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
|
||||
|
||||
|
||||
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 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
|
||||
public class MaxPQ<Key extends Comparable<Key> {
|
||||
|
@ -741,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
|
||||
private void swim(int k) {
|
||||
|
@ -754,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
|
||||
private void sink(int k) {
|
||||
|
@ -797,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
|
||||
public static void sort(Comparable[] a){
|
||||
|
@ -830,7 +854,15 @@ public static void sort(Comparable[] a){
|
|||
|
||||
### 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 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
|
||||
|
||||
|
@ -861,23 +893,13 @@ public static Comparable select(Comparable[] a, int k) {
|
|||
|
||||
# 五、查找
|
||||
|
||||
符号表是一种存储键值对的数据结构,支持两种操作:插入一个新的键值对;根据给定键得到值。
|
||||
符号表是一种存储键值对的数据结构,主要支持两种操作:插入一个新的键值对、根据给定键得到值。
|
||||
|
||||
## 符号表
|
||||
|
||||
### 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() 等根据键的大小关系来实现的操作。
|
||||
符号表分为有序和无序两种,有序符号表主要指支持 min()、max() 等根据键的大小关系来实现的操作。
|
||||
|
||||
有序符号表的键需要实现 Comparable 接口。
|
||||
|
||||
### 3. 二分查找实现有序符号表
|
||||
## 二分查找实现有序符号表
|
||||
|
||||
使用一对平行数组,一个存储键一个存储值。其中键的数组为 Comparable 数组,值的数组为 Object 数组。
|
||||
|
||||
|
@ -944,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)是一颗二叉树,并且每个节点的值都大于其左子树中的所有节点的值而小于右子树的所有节点的值。
|
||||
|
||||
<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 有一个重要性质,就是它的中序遍历结果递增排序。
|
||||
|
||||
<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>
|
||||
|
||||
基本数据结构:
|
||||
|
||||
|
@ -987,6 +1009,8 @@ public class BST<Key extends Comparable<Key>, Value> {
|
|||
}
|
||||
```
|
||||
|
||||
(为了方便绘图,二叉树的空链接不画出来。)
|
||||
|
||||
### 1. get()
|
||||
|
||||
- 如果树是空的,则查找未命中;
|
||||
|
@ -1012,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
|
||||
public void put(Key key, Value val) {
|
||||
|
@ -1031,9 +1055,13 @@ private Node put(Node x, Key key, Value val) {
|
|||
|
||||
### 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()
|
||||
|
||||
|
@ -1042,7 +1070,6 @@ 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
|
||||
public Key floor(Key key) {
|
||||
|
@ -1098,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
|
||||
public void deleteMin() {
|
||||
|
@ -1117,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
|
||||
public void delete(Key key) {
|
||||
|
@ -1167,7 +1194,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
|
|||
|
||||
## 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 查找树的所有空链接到根节点的距离应该是相同的。
|
||||
|
||||
|
@ -1179,11 +1206,11 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
|
|||
|
||||
插入到 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- 节点。
|
||||
|
||||
<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. 性质
|
||||
|
||||
|
@ -1197,7 +1224,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
|
|||
|
||||
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>
|
||||
|
||||
红黑树具有以下性质:
|
||||
|
||||
|
@ -1240,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//ed23a7cd-d55f-453d-b5bc-076d7b1c002e.jpg" width="300"/> </div><br>
|
||||
<div align="center"> <img src="../pics//9110c1a0-8a54-4145-a814-2477d0128114.png" width="450"/> </div><br>
|
||||
|
||||
```java
|
||||
public Node rotateLeft(Node h) {
|
||||
|
@ -1261,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//7ca30d08-f7fa-4e39-b386-93c70631a900.png" width="300"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e2f0d889-2330-424c-8193-198edebecff7.png" width="450"/> </div><br>
|
||||
|
||||
```java
|
||||
public Node rotateRight(Node h) {
|
||||
|
@ -1281,9 +1304,7 @@ public Node rotateRight(Node h) {
|
|||
|
||||
一个 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//04662fa2-d19b-4de3-a829-50acb7af75d7.png" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//af4639f9-af54-4400-aaf5-4e261d96ace7.png" width="300"/> </div><br>
|
||||
|
||||
```java
|
||||
void flipColors(Node h){
|
||||
|
@ -1301,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
|
||||
public void put(Key key, Value val) {
|
||||
|
@ -1329,23 +1350,7 @@ private Node put(Node x, Key key, Value val) {
|
|||
|
||||
根节点一定为黑色,因为根节点没有上层节点,也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1.
|
||||
|
||||
### 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. 分析
|
||||
### 5. 分析
|
||||
|
||||
一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树,构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。
|
||||
|
||||
|
@ -1419,7 +1424,7 @@ public class Transaction{
|
|||
|
||||
拉链法使用链表来存储 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。
|
||||
|
||||
|
@ -1427,7 +1432,7 @@ public class Transaction{
|
|||
|
||||
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>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
|
||||
public class LinearProbingHashST<Key, Value> {
|
||||
|
@ -1552,7 +1557,13 @@ private void resize(int cap) {
|
|||
|
||||
### 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 |
|
||||
|
||||
应当优先考虑散列表,当需要有序性操作时使用红黑树。
|
||||
|
||||
|
@ -1596,3 +1607,7 @@ public class SparseVector {
|
|||
}
|
||||
```
|
||||
|
||||
# 参考资料
|
||||
|
||||
- Sedgewick, Robert, and Kevin Wayne. _Algorithms_. Addison-Wesley Professional, 2011.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 225 KiB |
BIN
pics/05e41947-3cbc-4f02-a428-96765ec916ff.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 8.0 KiB |
BIN
pics/08427d38-8df1-49a1-8990-e0ce5ee36ca2.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
pics/095720ee-84b3-42ff-af71-70ceb6a2f4a3.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 9.0 KiB |
BIN
pics/0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 67 KiB |
BIN
pics/107a6a2b-f15b-4cad-bced-b7fb95258c9c.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 80 KiB |
BIN
pics/1f039a45-6b91-4f31-a2c2-6c63eb8bdb56.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 3.0 KiB |
BIN
pics/26020e1a-06ab-4114-a6b3-e428de690c7e.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 309 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 6.0 KiB |
BIN
pics/3290673d-edab-4678-8b2e-f18e0f6b7fc1.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 7.0 KiB |
BIN
pics/33ac2b23-cb85-4e99-bc41-b7b7199fad1c.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.0 KiB |
BIN
pics/4bb7ed45-ec14-4d31-9da4-94024d9d3b05.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 4.0 KiB |
BIN
pics/4d741402-344d-4b7c-be01-e57184bcad0e.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 252 KiB |
BIN
pics/4f48e806-f90b-4c09-a55f-ac0cd641c047.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 74 KiB |
BIN
pics/51fb761d-8ce0-4472-92ff-2f227ac7888a.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.0 KiB |
BIN
pics/5acf7550-86c5-4c5b-b912-8ce70ef9c34e.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
pics/5d4a5181-65fb-4bf2-a9c6-899cab534b44.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 228 KiB |
BIN
pics/72f0ff69-138d-4e54-b7ac-ebe025d978dc.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 463 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 77 KiB |
BIN
pics/7f38a583-2f2e-4738-97af-510e6fb403a7.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 197 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 162 KiB |
BIN
pics/8ae4550b-f0cb-4e4d-9e2b-c550538bf230.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
pics/8af348d0-4d72-4f76-b56c-0a440ed4673d.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
pics/8dfb4cc9-26da-45e7-b820-4576fa1cbb0e.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
pics/8f0cc500-5994-4c7a-91a9-62885d658662.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pics/9110c1a0-8a54-4145-a814-2477d0128114.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 55 KiB |