diff --git a/数据库/Redis/ch0补充.md b/数据库/Redis/ch0补充.md new file mode 100644 index 00000000..4c3327cb --- /dev/null +++ b/数据库/Redis/ch0补充.md @@ -0,0 +1,3 @@ +> https://blog.csdn.net/zh15732621679/article/details/80614091 +### Redis使用场景 +![](image/2021-04-07-23-46-25.png) \ No newline at end of file diff --git a/数据库/Redis/0redis.md b/数据库/Redis/ch1概述.md similarity index 100% rename from 数据库/Redis/0redis.md rename to 数据库/Redis/ch1概述.md diff --git a/数据库/Redis/image/2021-04-07-23-46-25.png b/数据库/Redis/image/2021-04-07-23-46-25.png new file mode 100644 index 00000000..4947c52e Binary files /dev/null and b/数据库/Redis/image/2021-04-07-23-46-25.png differ diff --git a/数据库/设计篇——1.规范化.md b/数据库/设计篇——1.规范化.md index 5e76657c..9d3666a4 100644 --- a/数据库/设计篇——1.规范化.md +++ b/数据库/设计篇——1.规范化.md @@ -6,9 +6,20 @@ 如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为**键码**。 -对于 A-\>B,如果能找到 A 的真子集 A',使得 A'-\> B,那么 A-\>B 就是**部分函数依赖**,否则就是**完全函数依赖**。 +### 部分函数依赖 +* 设X,Y是关系R的两个属性集合,存在X→Y,若X’是X的真子集,存在X’→Y,则称Y部分函数依赖于X。 -对于 A-\>B,B-\>C,则 A-\>C 是一个**传递函数依赖**。 +* 举个例子:学生基本信息表R中(学号,身份证号,姓名)当然学号属性取值是唯一的,在R关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号); + +### 完全函数依赖 + +* 设X,Y是关系R的两个属性集合,X’是X的真子集,存在X→Y,但对每一个X’都有X’!→Y,则称Y完全函数依赖于X。 +* 例子:学生基本信息表R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在R关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级); + +### 传递函数依赖 + +* 设X,Y,Z是关系R中互不相同的属性集合,存在X→Y(Y !→X),Y→Z,则称Z传递函数依赖于X。 +* 例子:在关系R(学号 ,宿舍, 费用)中,(学号)->(宿舍),宿舍!=学号,(宿舍)->(费用),费用!=宿舍,所以符合传递函数的要求; ## 2 异常 @@ -49,9 +60,7 @@ 每个非主属性完全函数依赖于键码。 -可以通过分解来满足。 - - **分解前**
+> **分解前** | Sno | Sname | Sdept | Mname | Cname | Grade | | :---: | :---: | :---: | :---: | :---: |:---:| @@ -61,16 +70,15 @@ | 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | 以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖: +``` +Sno -> Sname, Sdept +Sdept -> Mname +Sno, Cname-> Grade +``` +* Grade 完全函数依赖于键码{Sno,Cname},它没有任何冗余数据,每个学生的每门课都有特定的成绩。 +* Sname, Sdept 和 Mname 都部分依赖于键码{Sno,Cname},当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 -- Sno -\> Sname, Sdept -- Sdept -\> Mname -- Sno, Cname-\> Grade - -Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。 - -Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 - - **分解后**
+> **分解后** 关系-1 @@ -103,10 +111,11 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 非主属性不传递函数依赖于键码。 上面的 关系-1 中存在以下传递函数依赖: +``` +Sno -> Sdept -> Mname +``` -- Sno -\> Sdept -\> Mname - -可以进行以下分解: +> 可以进行以下分解: 关系-11 diff --git a/数据结构/5 散列表.md b/数据结构/5 散列表.md index d154f553..0708dca4 100644 --- a/数据结构/5 散列表.md +++ b/数据结构/5 散列表.md @@ -2,6 +2,9 @@ > 包含集合和映射。即unordered_set和unordreed_map两种数据结构的实现方式。 +> 参考文献 +> * [https://www.cnblogs.com/gongcheng-/p/10894205.html#_label1_0](https://www.cnblogs.com/gongcheng-/p/10894205.html#_label1_0) + ## 1 哈希表简介 ### 哈希表的概念 @@ -22,7 +25,7 @@ 1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; 2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 -### 哈希函数示例 +### 哈希函数 ![](image/2021-03-13-12-51-08.png) @@ -33,7 +36,10 @@ - 如果我们搜索 1987,我们将使用相同的哈希函数将 1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。 - 例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。 -### 哈希表的关键——哈希函数 +### 常用hash函数 +* + +## 2.1 哈希表的关键——哈希函数 * 哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。在上一节的示例中,我们使用 `y = x % 5` 作为散列函数,其中 `x` 是键值,`y` 是分配的桶的索引。 @@ -44,21 +50,50 @@ * 哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。 -### 哈希表的关键——冲突解决 +## 2.2 哈希表的关键——冲突解决 +### 问题分析 * 理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(_y = x % 5_)中,1987 和 2 都分配给了桶 2,这是一个`冲突`。 -* 冲突解决算法应该解决以下几个问题: - 1. 如何组织在同一个桶中的值? - 2. 如果为同一个桶分配了太多的值,该怎么办? - 3. 如何在特定的桶中搜索目标值? * 根据我们的哈希函数,这些问题与`桶的容量`和可能映射到`同一个桶`的`键的数目`有关。 * 让我们假设存储最大键数的桶有 `N` 个键。如果 N是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 N 是可变的或很大,我们可能需要使用`高度平衡的二叉树`来代替。 +### 开放寻址法(open addressing) -## 哈希表的实现 +* 这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式: + +``` +Hi=(H(key)+di)% m i=1,2,…,n +``` + +* 其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。 + +### 链接法 +![](image/2021-04-07-23-38-56.png) +* 把散列到同一槽中的所有元素都存放在一个链表中。每个槽中有一个指针,指向所有散列到该槽的元素构成的链表的头。如果不存在这样的元素,则指针为空。如果链接法使用的是双向链表,那么删除操作的最坏情况运行时间与插入操作相同,都为O(1),而平均情况下一次成功的查找需要Θ(1+α)时间。α是装填因子。 + +* 将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。 + +* 优点:处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;删除结点的操作易于实现。只要简单地删去链表上相应的结点即可 + +* 缺点:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指 + +### 优缺点 + +* 开放散列表(open hashing)/拉链法(针对桶链结构) + 1. 优点:①对于记录总数频繁可变的情况,处理的比较好(也就是避免了动态调整的开销) ②由于记录存储在结点中,而结点是动态分配,不会造成内存的浪费,所以尤其适合那种记录本身尺寸(size)很大的情况,因为此时指针的开销可以忽略不计了 ③删除记录时,比较方便,直接通过指针操作即可 + 2. 缺点: ①存储的记录是随机分布在内存中的,这样在查询记录时,相比结构紧凑的数据类型(比如数组),哈希表的跳转访问会带来额外的时间开销 ②如果所有的 key-value 对是可以提前预知,并之后不会发生变化时(即不允许插入和删除),可以人为创建一个不会产生冲突的完美哈希函数(perfect hash function),此时封闭散列的性能将远高于开放散列 ③由于使用指针,记录不容易进行序列化(serialize)操作 +* 封闭散列(closed hashing)/ 开放定址法 + 1. 优点: ①记录更容易进行序列化(serialize)操作 ②如果记录总数可以预知,可以创建完美哈希函数,此时处理数据的效率是非常高的 + 2. 缺点: + 1. 存储记录的数目不能超过桶数组的长度,如果超过就需要扩容,而扩容会导致某次操作的时间成本飙升,这在实时或者交互式应用中可能会是一个严重的缺陷 + 2. 使用探测序列,有可能其计算的时间成本过高,导致哈希表的处理性能降低 ③由于记录是存放在桶数组中的,而桶数组必然存在空槽,所以当记录本身尺寸(size)很大并且记录总数规模很大时,空槽占用的空间会导致明显的内存浪费 + 3. 删除记录时,比较麻烦。比如需要删除记录a,记录b是在a之后插入桶数组的,但是和记录a有冲突,是通过探测序列再次跳转找到的地址,所以如果直接删除a,a的位置变为空槽,而空槽是查询记录失败的终止条件,这样会导致记录b在a的位置重新插入数据前不可见,所以不能直接删除a,而是设置删除标记。这就需要额外的空间和操作。 + + +## 3 哈希表的实现 ### 自己设计哈希函数 diff --git a/数据结构/image/2021-04-07-23-38-56.png b/数据结构/image/2021-04-07-23-38-56.png new file mode 100644 index 00000000..a592d261 Binary files /dev/null and b/数据结构/image/2021-04-07-23-38-56.png differ