可扩展散列是一种动态散列方法,其中目录和桶用于散列数据。这是一种非常灵活的方法,其中散列函数也经历动态变化。
可扩展散列的主要特点:这种散列技术的主要特点是:
- 目录:目录将存储桶的地址存储在指针中。一个 id 被分配给每个目录,当目录扩展发生时,它可能会改变。
- 桶:桶用于散列实际数据。
可扩展散列的基本结构:
可扩展散列中的常用术语:
- 目录:这些容器存储指向桶的指针。每个目录都有一个唯一的 id,每次扩展时都可能会改变。哈希函数返回此目录 ID,用于导航到适当的存储桶。目录数 = 2^全局深度。
- 存储桶:它们存储散列的密钥。目录指向桶。如果一个桶的局部深度小于全局深度,它可能包含多个指向它的指针。
- 全局深度:它与目录相关联。它们表示散列函数用于对键进行分类的位数。全局深度 = 目录 id 中的位数。
- Local Depth:与 Global Depth 相同,只是 Local Depth 与存储桶相关联,而不是与目录相关联。根据全局深度的局部深度用于决定在发生溢出时要执行的动作。局部深度始终小于或等于全局深度。
- 桶拆分:当桶中的元素数量超过特定大小时,则桶被拆分为两部分。
- 目录扩展:目录扩展发生在桶溢出时。当溢出桶的局部深度等于全局深度时执行目录扩展。
可扩展哈希的基本工作:
- 步骤 1 – 分析数据元素:数据元素可能以各种形式存在,例如。整数、字符串、浮点数等。目前,让我们考虑整数类型的数据元素。例如:49。
- 步骤 2 – 转换为二进制格式:将数据元素转换为二进制格式。对于字符串元素,考虑起始字符的 ASCII 等效整数,然后将整数转换为二进制形式。因为我们有 49 作为我们的数据元素,它的二进制形式是 110001。
- 步骤 3 – 检查目录的全局深度。假设 Hash 目录的全局深度为 3。
- 步骤 4 – 识别目录:考虑二进制数中 LSB 的“全局深度”数并将其与目录 ID 匹配。
例如。得到的二进制文件:110001和全球深度为3。因此,哈希函数会返回110 001即3个LSB。 001. - 步骤 5 – 导航:现在,导航到目录 ID 为 001 的目录所指向的存储桶。
- 步骤 6 – 插入和溢出检查:插入元素并检查桶是否溢出。如果遇到溢出,则转至步骤7,然后转至步骤8 ,否则转至步骤9 。
- 步骤 7 – 处理数据插入期间的溢出情况:很多时候,在将数据插入桶中时,可能会发生桶溢出的情况。在这种情况下,我们需要遵循适当的程序以避免数据处理不当。
首先,检查局部深度是否小于或等于全局深度。然后选择以下情况之一。- Case1:如果溢出Bucket的局部深度等于全局深度,则需要进行Directory Expansion,以及Bucket Split。然后将全局深度和局部深度值增加 1。并且,分配适当的指针。
目录扩展将使散列结构中存在的目录数量增加一倍。 - Case2:如果局部深度小于全局深度,则只进行Bucket Split。然后仅将局部深度值增加 1。并且,分配适当的指针。
- Case1:如果溢出Bucket的局部深度等于全局深度,则需要进行Directory Expansion,以及Bucket Split。然后将全局深度和局部深度值增加 1。并且,分配适当的指针。
- 第 8 步 – 重新散列拆分的存储桶元素:在已拆分的溢出存储桶中存在的元素将根据目录的新全局深度进行重新哈希处理。
- 步骤 9 –元素被成功散列。
基于可扩展散列的示例:现在,让我们考虑散列以下元素的突出示例: 16、4、6、22、24、10、31、7、9、20、26。
桶大小: 3(假设)
哈希函数:假设全局深度为 X。那么哈希函数返回 X 个 LSB。
- 解决方案:首先,计算每个给定数字的二进制形式。
16- 10000
4- 00100
6- 00110
22- 10110
24- 11000
10- 01010
31- 11111
7- 00111
9- 01001
20- 10100
26- 11010 - 最初,全局深度和局部深度始终为 1。因此,散列框架如下所示:
- 插入16:
16 的二进制格式为 10000,global-depth 为 1。哈希函数返回 1000 0 的1 LSB,即 0。因此,16 映射到 id=0 的目录。
- 插入 4 和 6:
4(10 0 ) 和 6(11 0 ) 的 LSB 都是 0。因此,它们被散列如下:
- 插入 22: 22的二进制形式是 1011 0 。它的 LSB 是 0。目录 0 指向的桶已经满了。因此,发生溢出。
- 按照步骤 7-案例 1 的指示,由于局部深度 = 全局深度,因此发生存储桶拆分和目录扩展。此外,溢出桶中存在的数字的重新散列发生在拆分之后。并且,由于全局深度增加了 1,现在全局深度为 2。因此,现在 16,4,6,22 现在重新散列了 2 个 LSB。[ 16(100 00 ),4(1 00 ),6( 1 10 ),22(101 10 ) ]
*Notice that the bucket which was underflow has remained untouched. But, since the number of directories has doubled, we now have 2 directories 01 and 11 pointing to the same bucket. This is because the local-depth of the bucket has remained 1. And, any bucket having a local depth less than the global depth is pointed-to by more than one directories.
- 插入 24 和 10: 24(110 00 ) 和 10 (10 10 ) 可以根据 id 为 00 和 10 的目录进行散列。这里,我们没有遇到溢出情况。
- 插入 31,7,9:所有这些元素 [ 31(111 11 ), 7(1 11 ), 9(10 01 ) ] 在它们的 LSB 中都有 01 或 11。因此,它们被映射到 01 和 11 指出的桶上。我们在这里没有遇到任何溢出情况。
- 插入 20:插入数据元素 20 (101 00 ) 将再次导致溢出问题。
- 20 被插入到由 00 指出的桶中。根据步骤 7-案例 1 的指示,由于桶的局部深度 = global-depth ,目录扩展(加倍)与桶拆分一起发生。溢出桶中存在的元素将使用新的全局深度重新散列。现在,新的哈希表如下所示:
- 插入 26:全局深度为 3。因此,考虑 26(11 010 ) 的 3 个 LSB。因此,26 个最适合目录 010 指出的存储桶。
- 存储桶溢出,并且按照步骤 7-情况 2 的指示,由于存储桶的局部深度 < 全局深度 (2<3) ,目录不会加倍,而是只拆分存储桶并重新散列元素。
最后,获得对给定数字列表进行散列的输出。
- 这样就完成了 11 个数字的散列。
主要观察:
- 如果一个 Bucket 的局部深度小于全局深度,它将有多个指针指向它。
- 当桶中发生溢出情况时,桶中的所有条目都使用新的局部深度重新散列。
- 如果溢出桶的局部深度
- 数据插入过程开始后,桶的大小不能更改。
好处:
- 数据检索成本较低(在计算方面)。
- 由于存储容量动态增加,因此没有数据丢失的问题。
- 随着散列函数的动态变化,相关的旧值被重新散列 wrt 新的散列函数。
可扩展散列的限制:
- 如果在同一目录上散列多个记录同时保持记录分布不均匀,则目录大小可能会显着增加。
- 每个桶的大小是固定的。
- 当全局深度和局部深度差异变得很大时,指针会浪费内存。
- 这种方法编码起来很复杂。
用于实现的数据结构:
- B+树
- 大批
- 链表
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。