24
24
* [ 堆排序] ( #堆排序 )
25
25
* [ 小结] ( #小结 )
26
26
* [ 六、查找] ( #六查找 )
27
- * [ 链表实现无序符号表] ( #链表实现无序符号表 )
28
- * [ 二分查找实现有序符号表] ( #二分查找实现有序符号表 )
27
+ * [ 初级实现] ( #初级实现 )
29
28
* [ 二叉查找树] ( #二叉查找树 )
30
29
* [ 2-3 查找树] ( #2-3-查找树 )
31
30
* [ 红黑树] ( #红黑树 )
@@ -1178,7 +1177,9 @@ public interface OrderedST<Key extends Comparable<Key>, Value> {
1178
1177
}
1179
1178
```
1180
1179
1181
- ## 链表实现无序符号表
1180
+ ## 初级实现
1181
+
1182
+ ### 1. 链表实现无序符号表
1182
1183
1183
1184
``` java
1184
1185
public class ListUnorderedST <Key, Value> implements UnorderedST<Key , Value > {
@@ -1253,7 +1254,7 @@ public class ListUnorderedST<Key, Value> implements UnorderedST<Key, Value> {
1253
1254
}
1254
1255
```
1255
1256
1256
- ## 二分查找实现有序符号表
1257
+ ### 2. 二分查找实现有序符号表
1257
1258
1258
1259
使用一对平行数组,一个存储键一个存储值。
1259
1260
@@ -1308,7 +1309,7 @@ public class BinarySearchOrderedST<Key extends Comparable<Key>, Value> implement
1308
1309
@Override
1309
1310
public void put (Key key , Value value ) {
1310
1311
int index = rank(key);
1311
- // 如果找到已经存在的节点键位 key,就更新这个节点的值为 value
1312
+ // 如果找到已经存在的节点键为 key,就更新这个节点的值为 value
1312
1313
if (index < N && keys[index]. compareTo(key) == 0 ) {
1313
1314
values[index] = value;
1314
1315
return ;
@@ -1391,14 +1392,14 @@ public class BST<Key extends Comparable<Key>, Value> implements OrderedST<Key, V
1391
1392
return 0 ;
1392
1393
return x. N ;
1393
1394
}
1394
-
1395
+
1395
1396
protected void recalculateSize (Node x ) {
1396
1397
x. N = size(x. left) + size(x. right) + 1 ;
1397
1398
}
1398
1399
}
1399
1400
```
1400
1401
1401
- (为了方便绘图,二叉树的空链接不画出来 。)
1402
+ (为了方便绘图,下文中二叉树的空链接不画出来 。)
1402
1403
1403
1404
### 1. get()
1404
1405
@@ -1427,7 +1428,7 @@ private Value get(Node x, Key key) {
1427
1428
1428
1429
### 2. put()
1429
1430
1430
- 当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中 。
1431
+ 当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接指向该节点,使得该节点正确地链接到树中 。
1431
1432
1432
1433
<div align =" center " > <img src =" ../pics//107a6a2b-f15b-4cad-bced-b7fb95258c9c.png " width =" 200 " /> </div ><br >
1433
1434
@@ -1454,7 +1455,9 @@ private Node put(Node x, Key key, Value value) {
1454
1455
1455
1456
### 3. 分析
1456
1457
1457
- 二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。
1458
+ 二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。
1459
+
1460
+ 最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。
1458
1461
1459
1462
<div align =" center " > <img src =" ../pics//4d741402-344d-4b7c-be01-e57184bcad0e.png " width =" 200 " /> </div ><br >
1460
1463
@@ -1467,8 +1470,7 @@ private Node put(Node x, Key key, Value value) {
1467
1470
floor(key):小于等于键的最大键
1468
1471
1469
1472
- 如果键小于根节点的键,那么 floor(key) 一定在左子树中;
1470
- - 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。
1471
-
1473
+ - 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就返回,否则根节点就是 floor(key)。
1472
1474
1473
1475
``` java
1474
1476
public Key floor(Key key) {
@@ -1497,7 +1499,7 @@ rank(key) 返回 key 的排名。
1497
1499
1498
1500
- 如果键和根节点的键相等,返回左子树的节点数;
1499
1501
- 如果小于,递归计算在左子树中的排名;
1500
- - 如果大于,递归计算在右子树中的排名,并加上左子树的节点数 ,再加上 1(根节点)。
1502
+ - 如果大于,递归计算在右子树中的排名,加上左子树的节点数 ,再加上 1(根节点)。
1501
1503
1502
1504
``` java
1503
1505
@Override
@@ -1793,15 +1795,15 @@ private Node put(Node x, Key key, Value value) {
1793
1795
1794
1796
- 一致性:相等的键应当有相等的 hash 值,两个键相等表示调用 equals() 返回的值相等。
1795
1797
- 高效性:计算应当简便,有必要的话可以把 hash 值缓存起来,在调用 hash 函数时直接返回。
1796
- - 均匀性:所有键的 hash 值应当均匀地分布到 [ 0, M-1] 之间,这个条件至关重要,直接影响到散列表的性能 。
1798
+ - 均匀性:所有键的 hash 值应当均匀地分布到 [ 0, M-1] 之间,如果不能满足这个条件,有可能产生很多冲突,从而导致散列表的性能下降 。
1797
1799
1798
1800
除留余数法可以将整数散列到 [ 0, M-1] 之间,例如一个正整数 k,计算 k%M 既可得到一个 [ 0, M-1] 之间的 hash 值。注意 M 必须是一个素数,否则无法利用键包含的所有信息。例如 M 为 10<sup >k</sup >,那么只能利用键的后 k 位。
1799
1801
1800
- 对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其表示成二进制形式,然后使用二进制形式的整数值进行除留余数法 。
1802
+ 对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其的二进制形式转换成整数 。
1801
1803
1802
- 对于有多部分组合的键,每部分都需要计算 hash 值,并且最后合并时需要让每部分 hash 值都具有同等重要的地位。可以将该键看成 R 进制的整数,键中每部分都具有不同的权值 。
1804
+ 对于多部分组合的类型,每个部分都需要计算 hash 值,这些 hash 值都具有同等重要的地位。为了达到这个目的,可以将该类型看成 R 进制的整数,每个部分都具有不同的权值 。
1803
1805
1804
- 例如,字符串的散列函数实现如下
1806
+ 例如,字符串的散列函数实现如下:
1805
1807
1806
1808
``` java
1807
1809
int hash = 0 ;
@@ -1823,7 +1825,7 @@ Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存
1823
1825
int hash = (x. hashCode() & 0x7fffffff ) % M ;
1824
1826
```
1825
1827
1826
- 使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode()。
1828
+ 使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode():
1827
1829
1828
1830
``` java
1829
1831
public class Transaction {
@@ -1850,15 +1852,19 @@ public class Transaction {
1850
1852
1851
1853
### 2. 基于拉链法的散列表
1852
1854
1853
- 拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
1855
+ 拉链法使用链表来存储 hash 值相同的键,从而解决冲突。
1854
1856
1855
- < div align = " center " > < img src = " ../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png " width = " 300 " /> </ div >< br >
1857
+ 查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
1856
1858
1857
- 对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~ N/M。
1859
+ 对于 N 个键,M 条链表 (N>M),如果 hash 函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~ N/M。
1860
+
1861
+ <div align =" center " > <img src =" ../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png " width =" 300 " /> </div ><br >
1858
1862
1859
1863
### 3. 基于线性探测法的散列表
1860
1864
1861
- 线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线性探测法,数组的大小 M 应当大于键的个数 N(M>N)。
1865
+ 线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。
1866
+
1867
+ 使用线性探测法,数组的大小 M 应当大于键的个数 N(M>N)。
1862
1868
1863
1869
<div align =" center " > <img src =" ../pics//dbb8516d-37ba-4e2c-b26b-eefd7de21b45.png " width =" 400 " /> </div ><br >
1864
1870
@@ -1962,9 +1968,7 @@ public void delete(Key key) {
1962
1968
1963
1969
<div align =" center " > <img src =" ../pics//386cd64f-7a9d-40e6-8c55-22b90ee2d258.png " width =" 400 " /> </div ><br >
1964
1970
1965
- α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。
1966
-
1967
- 为了保证散列表的性能,应当调整数组的大小,使得 α 在 [ 1/4, 1/2] 之间。
1971
+ α = N/M,把 α 称为使用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。为了保证散列表的性能,应当调整数组的大小,使得 α 在 [ 1/4, 1/2] 之间。
1968
1972
1969
1973
``` java
1970
1974
private void resize() {
@@ -2044,7 +2048,9 @@ public class SparseVector {
2044
2048
2 . 将 1 个圆盘从 from -> to
2045
2049
3 . 将 n-1 个圆盘从 buffer -> to
2046
2050
2047
- 如果只有一个圆盘,那么只需要进行一次移动操作,从上面的移动步骤可以知道,n 圆盘需要移动 (n-1)+1+(n-1) = 2n-1 次。
2051
+ 如果只有一个圆盘,那么只需要进行一次移动操作。
2052
+
2053
+ 从上面的讨论可以知道,n 圆盘需要移动 (n-1)+1+(n-1) = 2n-1 次。
2048
2054
2049
2055
<div align =" center " > <img src =" ../pics//54f1e052-0596-4b5e-833c-e80d75bf3f9b.png " width =" 300 " /> </div ><br >
2050
2056
@@ -2093,7 +2099,7 @@ from H1 to H3
2093
2099
- c : 40
2094
2100
- d : 80
2095
2101
2096
- 可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码能让出现频率最大的字符编码最短,从而保证最终的编码长度最短 。
2102
+ 可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码能让出现频率最高的字符的编码最短,从而保证整体的编码长度最短 。
2097
2103
2098
2104
首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点在树的最底层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。
2099
2105
0 commit comments